【JavaSE】_6.继承与多态

目录

1.继承

1.1  继承的概念

1.2 继承语法

1.3 父类成员的访问

1.3.1 子类中访问父类的成员变量

1.3.2 子类中访问父类的成员方法

1.4 super关键字

1.5 子类构造方法

1.6 再谈初始化

1.7 protected 关键字

1.8 继承方式

1.9 final 关键字

1.9.1 修饰变量或字段

1.9.2 修饰类

1.10 继承与组合

2.多态

2.1 多态的概念

2.2 多态实现的条件

2.3 重写

2.3.1 方法重写的规则:

2.3.2 重写与重载

2.3.3 重写设计的原则

2.4 向上下转型与动静态绑定

2.4.1 向上转型

2.4.2 动态绑定

2.4.3 静态绑定

2.4.4 向下转型

2.5 多态的优缺点

2.5.1 多态的优点

2.6 避免在构造方法中调用重写的方法


1.继承

1.1  继承的概念

继承机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加新功能,这样产生的新的类称为派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。

继承主要解决的问题是:共性的抽取,实现代码复用;

1.2 继承语法

在java中,表示类的继承关系需要使用到exends关键字

修饰限定符 子类 extends 父类{
   //...
}
class Animal{
    public String name;
    public int age;
    public void  fun1(){
        System.out.println("It is an animal.");
    }
}
class Dog extends Animal{
    public void Bark(){
        System.out.println("The dog named " +name+" is barking.");
    }
}
class Cat extends Animal{
    public void Meow(){
        System.out.println("The cat named "+ name+" is meowing.");
    }
}
public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.fun1();
        dog.name = "Mike";
        dog.Bark();
        Cat cat = new Cat();
        cat.fun1();
        cat.name= "Mary";
        cat.Meow();
    }
}

 输出结果为:

 注:(1)子类会将父类中的成员变量或成员方法继承到子类中;

(2)子类继承父类之后,必须要新增自己特有的成员以体现与父类的不同,否则不需要继承;

1.3 父类成员的访问

1.3.1 子类中访问父类的成员变量

1. 子类和父类不存在同名成员变量

class Base{
    public int a = 10;
    public int b = 20;
    }
    class Derived extends Base{
    public int c = 30;
    public void fun(){
        System.out.println("a = "+a);
        System.out.println("b = "+b);
        System.out.println("c = "+c);
    }
    }
public class Main {
    public static void main(String[] args) {
        Derived derived  = new Derived();
        derived.fun();
    }
}

输出结果为:

  

结论1:当子类当中不存在与父类同名的属性,在子类内读写属性时,如果子类中存在该属性则直接读取,如果子类当中不存在该属性就去父类当中寻找; 

2. 子类和父类成员变量同名

class Base{
    public int a = 10;
    public int b = 20;
    }
    class Derived extends Base{
    public int a = 100;
    public int b = 200;
    public int c = 30;
    public void fun(){
        System.out.println("a = "+a);
        System.out.println("b = "+b);
        System.out.println("c = "+c);
    }
    }
public class Main {
    public static void main(String[] args) {
        Derived derived  = new Derived();
        derived.fun();
    }
}

输出结果为:

结论2:当子类当中存在与父类同名的属性,当在子类内读写属性时,会优先读取子类的属性; 

如果需要指定访问父类中的属性,则需使用super关键字

class Base{
    public int a = 10;
    public int b = 20;
    }
    class Derived extends Base{
    public int a = 100;
    public int b = 200;
    public int c = 30;
    public void fun(){
        System.out.println("a = "+ super.a);
        System.out.println("b = "+ super.b);
        System.out.println("c = "+c);
    }
    }
public class Main {
    public static void main(String[] args) {
        Derived derived  = new Derived();
        derived.fun();
    }
}

输出结果为:

注:结论2:当在子类内读写属性时,如果依次在子类、父类中都查询无果,则会编译报错;

1.3.2 子类中访问父类的成员方法

1. 成员方法名不同

class Base{
    public int a = 10;
    public int b = 20;
    public void methodBase(){
        System.out.println("Base function.");
    }
    }
    class Derived extends Base{
    public int a = 100;
    public int b = 200;
    public int c = 30;
    public void methodDerived(){
        System.out.println("Derived function.");
    }
    public void fun1(){
        methodBase();
        methodDerived();
    }
    }
public class Main {
    public static void main(String[] args) {
        Derived derived  = new Derived();
        derived.fun1();
    }
}

输出结果为:

2. 成员方法名相同

class Base{
    public int a = 10;
    public int b = 20;
    public void methodBase(){
        System.out.println("Base function(methodBase).");
    }
    }
    class Derived extends Base{
    public int a = 100;
    public int b = 200;
    public int c = 30;
    public void methodDerived(){
        System.out.println("Derived function.");
    }
    public void methodBase(){
        System.out.println("Derived function(method Base).");
    }
    public void fun1(){
        methodBase();
    }
    }
public class Main {
    public static void main(String[] args) {
        Derived derived  = new Derived();
        derived.fun1();
    }
}

输出结果为:

在子类中访问父类方法与在子类中访问父类属性的规则相同,就近访问子类的方法,如果该方法在子类中不存在就去父类中查询,如果两个类都查询无果则报错;同样,在子类访问与父类同名的方法时如需指定访问父类方法则需使用super关键字:

class Base{
    public int a = 10;
    public int b = 20;
    public void methodBase(){
        System.out.println("Base function(methodBase).");
    }
    }
    class Derived extends Base{
    public int a = 100;
    public int b = 200;
    public int c = 30;
    public void methodDerived(){
        System.out.println("Derived function.");
    }
    public void methodBase(){
        System.out.println("Derived function(method Base).");
    }
    public void fun1(){
        super.methodBase();
    }
    }
public class Main {
    public static void main(String[] args) {
        Derived derived  = new Derived();
        derived.fun1();
    }
}

输出结果为:

注:

class Base{
    public void methodBase(){
        System.out.println("Base function(methodBase).");
    }
    }
    class Derived extends Base{
    public void methodBase(int a){
        System.out.println("Derived function(method Base).");
    }
    public void fun1(){
        methodBase();
    }
}

在上文代码中:

分别位于父类和子类中的methodBase方法构成重载

② 由于子类中的该方法需要一个int型参数,而在调用时并未传参,故而无需使用super.methodBase,也调用的是父类的该方法:

  

在java官方文档中关于重载的定义如下:

详见: https://docs.oracle.com/javase/specs/index.html

1.4 super关键字

在上文已介绍,super关键字的主要功能是:在子类中指定访问父类的属性或方法:

super.data:在子类当中访问父类的成员属性;

super.func():在子类当中访问父类的成员方法;

注:① 只能在非静态方法内使用;

② 在子类方法中访问父类的属性或方法;

1.5 子类构造方法

子类对象构造时需要先调用其父类的构造方法,然后再执行子类的构造方法;

class Animal{
    public String name;
    public int age;
    public void  fun1(){
        System.out.println("It is an animal.");
    }
    public Animal(){
    }
    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
class Dog extends Animal{
    public String breed;
    public Dog(String name,int age,String breed){
        super(name,age);
        //显式调用父类构造方法对父类成员进行初始化
        this.breed = breed;
    }
    public void Bark(){
        System.out.println("The dog named " +name+" is barking.");
    }

    @Override
    public String toString() {
        return "Dog{" +
                "breed='" + breed + '\'' +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
class Cat extends Animal{
    public String color;
    public Cat(String name,int age,String color){
        //super();
        //当父类拥有无参构造方法时,编译器会默认给子类增加一个父类的构造方法
        // 此时可以不再显式书写super();
        super(name,age);    //为传参完整,此处以带参父类构造方法为例
        this.color = color;
    }
    public void Meow(){
        System.out.println("The cat named "+ name+" is meowing.");
    }

    @Override
    public String toString() {
        return "Cat{" +
                "color='" + color + '\'' +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog("Mike",5,"Labrador");
        System.out.println(dog);
        Cat cat = new Cat("Mary",5,"grey");
        System.out.println(cat);
    }
}

输出结果为:

注:(1)子类调用父类构造方法时必须将super语句放在子类构造函数的第一行,否则就会报错;

(2)super(...)只能在子类构造方法中出现一次且与this不能同时出现;

(3)当没有在父类中显式书写带参构造方法时,编译器会默认增加一个无参的构造方法,故而建议在编写类尤其是有可能发生继承拥有子类时,重载一个无参的构造方法与一个带参的构造方法;

1.6 再谈初始化

class Animal{
    public String name;
    public int age;
    //静态代码块
    static{
        System.out.println("Animal static{}");
    }
    //实例代码块
    {
        System.out.println("Animal{}");
    }
    //无参构造方法
    public Animal(){
        System.out.println("Animal(){}");
    }
}
class Dog extends Animal{
    public String breed;
    //无参构造方法
    public Dog(){
        System.out.println("Dog(){}");
    }
    //静态代码块
    static{
        System.out.println("Dog static{}");
    }
    //实例代码块
    {
        System.out.println("Dog {}");
    }
}
public class Main {
    public static void main(String[] args) {
        Dog dog1 = new Dog();
        System.out.println("------------");
        Dog dog2 = new Dog();
    }
}

输出结果为:

1.7 protected 关键字

创建如下包与类:

   

(1)同一包中的不同类:

试运行以下代码:

 以上代码可以正常运行,说明protected修饰的属性在同一包中的不同类中可以进行访问;

(2)不同包中的子类:

注:(1)上文代码的前提是TestDemo1类是public修饰的;

(2)private修饰的成员不是没有被继承,而是继承了无法访问;

1.8 继承方式

(1)在Java中只支持以下几种继承方式:

单继承(B继承A),多继承(C继承B,B继承A),不同类继承同一个类(B和C都继承A);

(2)C++支持多继承,但Java不支持多继承(C继承A和B);

(3)一般我们不希望有超过三层的继承关系,如果想通过语法进行限制,则需使用final关键字;

1.9 final 关键字

1.9.1 修饰变量或字段

final int A= 10;  
A=20;

报错如下:

final修饰变量或字段表示常量,代表不能被修改,并建议名称改为大写;

1.9.2 修饰类

运行如下代码:

final class Animal{
    public String name;
    public int age;
    public Animal(){
        System.out.println("Animal(){}");
    }
    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
class Dog extends Animal{
    public String breed;
    public Dog(String name,int age,String breed){
        super(name,age);
        //显式调用父类构造方法对父类成员进行初始化
        this.breed = breed;
    }
    public Dog(){
        System.out.println("Dog(){}");
    }
    public void Bark(){
        System.out.println("The dog named " +name+" is barking.");
    }
}
class Cat extends Animal{
    public String color;
    public Cat(String name,int age,String color){
        super(name,age); 
        this.color = color;
    }
    public void Meow(){
        System.out.println("The cat named "+ name+" is meowing.");
    }

}
public class Main {
    public static void main(String[] args) {
        Dog dog1 = new Dog();
        Dog dog2 = new Dog();

    }
}

报错如下:

final修饰类表示该类不能再被继承;

注:① 如果需要将成员变量定义为final,语法规定必须同时给定一个初始值;

② 运行如下代码:

    public static void main(String[] args) {
        final int[] array = {1,2,3};
        array = new int[10];
    }

报错如下:

因为此时final修饰的是引用array,即array保存的值(指向的对象)是不可以更改的,但是array引用指向对象的内容是可以改变的,即以下代码可以正常运行:

    public static void main(String[] args) {
        final int[] array = {1,2,3};
        array[0]=2;
    }

1.10 继承与组合

与继承类似,组合也是一种表示类与类之间关系的方式(a part of);

以教师、学生、学校以及学院类为例:

class Students{   //学生类
}
class Teachers{   //教师类
}
class Campus{
    public Students[] students = new Students[3];
    public Teachers[] teachers = new Teachers[3];
    //将另外一个类作为当前类的一个属性称为组合
}
class academy extends Campus{ //学院类继承学校类
}
public class Main {
    public static void main(String[] args) {
        Campus campus = new Campus();
    }
}

内存分布:

2.多态

2.1 多态的概念

多态即多种形态,具体就是去完成某个行为,当不同的对象去完成时会有不同的状态;

2.2 多态实现的条件

(1)必须在继承体系下;

(2)子类必须要对父类中方法进行重写;

(3)通过父类的引用调用重写的方法(向上转型);

多态的体现:在代码运行时,当传递不同类的对象时会调用对应类中的方法;

2.3 重写

2.3.1 方法重写的规则:

(1)方法名、参数列表等相同;

(2)被重写的方法可以返回值可以不同,但必须具有父子关系;

(3)子类的访问修饰限定权限必须大于等于父类的权限;

(4)父类被private、static以及final修饰的方法都不能被重写;

(final修饰的方法称为密封方法)

(5)重写方法可以使用@override显式注解,此时如果方法名与参数列表等与父类不同,编译器就会帮助检查并报错。

也可由编译器自动生成:Alt+Insert选中Override Methods...选择需要重写的方法即可;

2.3.2 重写与重载

区别点 重写 重载
参数列表 一定不能修改 必须修改
返回类型 一定不能修改 可以修改
访问限定符 一定不能作更严格地限定 可以修改

2.3.3 重写设计的原则

对于已经投入使用的类尽量不要修改,最好的方式是重新定义一个类,来重复利用其中共性的内容,并且添加或者改动新的内容;

2.4 向上下转型与动静态绑定

2.4.1 向上转型

形式1:直接赋值:

class Animal{
    public String name;
    public int age;
    public void fun(){
        System.out.println("It is an animal.");
    }
}
class Dog extends Animal{
    public void Bark(){
        System.out.println("The dog named "+name+" is barking.");
    }
}
public class Main {
    public static void main(String[] args) {
        //1.
        Dog dog = new Dog();
        dog.name = "Mike";
        dog.age = 5;
        dog.Bark();
        Animal animal1 = dog;  //子类给父类:向上转型
        System.out.println("--------------");
        //2.
        Animal animal2 = new Dog();
        animal2.name = "Mary";
        animal2.fun();
    }
}

输出结果为:

还有两种向上转型的形式:(基于以上Animal、Dog、Bird类)

形式2:方法传参:

   public static void fun1(Animal animal){
        
    }
    public static void main(String[] args) {
        Dog dog = new Dog();
        fun1(dog);
    }

形式3:方法返回值:

    public static Animal fun2(){
        return new Dog();
    }
    public static void main(String[] args) {
        Dog dog = new Dog();
        fun2();
    }

注:(1)向上转型时,不能通过父类对象调用子类的成员,只能通过父类的引用访问父类的成员; 

(2)向上转型的优点:让代码实现更加灵活

(3)向上转型的缺点:不能调用到子类特有的方法,除非实现了重写

2.4.2 动态绑定

动态绑定也称为后期绑定,即在编译时不能确定方法的行为,需要在程序运行时才能够确定具体运行哪个类的方法;

代码示例1:

class Animal{
    public String name;
    public int age;
    public void Species(){
        System.out.println(name+" is an animal.");
    }
}
class Dog extends Animal{
    public void Bark(){
        System.out.println("The dog named "+name+" is barking.");
    }
    @Override  //注解
    public void Species() {
        System.out.println(name + " is a dog.");
    }
}
class Bird extends Animal{
    public String wing;
    public void Fly(){
        System.out.println("The bird named "+name+" is flying.");
    }
    @Override  //注解
    public void Species(){
        System.out.println(name+" is a bird.");
    }
}
public class Main {
    public static void main(String[] args) {
        Animal animal1 = new Dog();
        animal1.name = "Mike";
        animal1.age = 5;
        animal1.Species();
        System.out.println("--------------");
        Animal animal2 = new Bird();
        animal2.name = "Mary";
        animal2.age = 4;
        animal2.Species();
    }
}

输出结果为:

此时会发现:形式上虽是Animal的对象调用Species()方法,但输出的却是其new类的同名方法,是因为发生了动态绑定:

动态绑定需要三个前提条件:① 向上转型;② 构成重写;③ 通过父类引用调用重写的方法;

动态绑定是多态的基础; 

代码示例2:

class Animal{
    public String name;
    public int age;
    public void Species(){
        System.out.println(name+" is an animal.");
    }
}
class Dog extends Animal{
    public void Bark(){
        System.out.println("The dog named "+name+" is barking.");
    }
    @Override  //注解
    public void Species() {
        System.out.println(name + " is a dog.");
    }
}
class Bird extends Animal{
    public String wing;
    public void Fly(){
        System.out.println("The bird named "+name+" is flying.");
    }

    @Override
    public void Species() {
        System.out.println(name + " is a bird.");
    }
}
public class Main {
    public static void fun2(Animal animal) {
        animal.Species();
        
    }
    public static void main(String[] args) {
        Animal animal1 = new Dog();
        animal1.name = "Mike";
        animal1.age = 5;
        fun2(animal1);
        System.out.println("--------------");
        Animal animal2 = new Bird();
        animal2.name = "Mary";
        animal2.age = 4;
        fun2(animal2);
    }
}

使用方法传参方式更能体现多态,在未传递引用调用fun2方法时,在fun2方法内部是无法确定待调方法的,只有当对象的引用作为参数传递给fun2方法时,fun2方法内部才确定调用的是同名方法中的哪一个;

即:当父类引用 引用的对象不一样时,调用的方法是不一样的,这种现象就称为多态;

2.4.3 静态绑定

静态绑定也称为前期绑定,在编译时就确定即将调用的方法称为静态绑定,如java中调用重载的方法就是静态绑定;

2.4.4 向下转型

class Animal{
    public String name;
    public int age;
    public void Species(){
        System.out.println(name+" is an animal.");
    }
}
class Dog extends Animal{
    public void Bark(){
        System.out.println("The dog named "+name+" is barking.");
    }
    @Override  //注解
    public void Species() {
        System.out.println(name + " is a dog.");
    }
}
class Bird extends Animal{
    public String wing;
    public void Fly(){
        System.out.println("The bird named "+name+" is flying.");
    }

    @Override
    public void Species() {
        System.out.println(name + " is a bird.");
    }
}
public class Main {
    public static void main(String[] args) {
        Animal animal1 = new Dog();
        //向下转型
        Dog dog = (Dog)animal1;   //强转
        dog.name = "Jack";
        dog.Bark();
    }
}

输出结果为:

但向下转型是非常不安全的,基于以上Animal、Dog、Bird类,运行如下代码:

    public static void main(String[] args) {
        Animal animal1 = new Dog();
        //向下转型
        Bird bird = (Bird)animal1;
        bird.name = "Alice";
        bird.Fly();
    }

在编译时编译器并不会报错,而当运行时就会出现错误:

当new语句的赋值类范围大于接受类时,如上文示例,所有的动物都是猫,这显然是错误的;

可以增加判断程序:

if(animal1 instanceof Bird) {
   Bird bird = (Bird) animal1;
   bird.name = "Alice";
   bird.Fly();
}

该语句用于判断animal1是否引用了一个Bird对象,如果为假则不执行一系列语句,故程序不再报错,也不打印任何内容;

2.5 多态的优缺点

2.5.1 多态的优点

(1)可以降低代码的“圈复杂度”,避免使用大量的“if-else”:

class Shape{
    public void draw(){
        System.out.println("Drawing.");
    }
}
class Rectangle extends Shape{
    @Override
    public void draw() {
        System.out.println("Drawing a rectangle.");
    }
}
class Circle extends Shape{
    @Override
    public void draw() {
        System.out.println("Drawing a circle.");
    }
}
class Flower extends Shape{
    @Override
    public void draw() {
        System.out.println("Drawing a flower.");
    }
}
public class Main {
    public static void drawMap(Shape shape){
        shape.draw();
    }
    public static void main(String[] args) {
        Rectangle rectangle1 =  new Rectangle();
        drawMap(rectangle1);
        Circle circle1 = new Circle();
        drawMap(circle1);
        Flower flower1 = new Flower();
        drawMap(flower1); 
    }
}

或写为:

class Shape{
    public void draw(){
        System.out.println("Drawing.");
    }
}
class Rectangle extends Shape{
    @Override
    public void draw() {
        System.out.println("Drawing a rectangle.");
    }
}
class Circle extends Shape{
    @Override
    public void draw() {
        System.out.println("Drawing a circle.");
    }
}
class Flower extends Shape{
    @Override
    public void draw() {
        System.out.println("Drawing a flower.");
    }
}
public class Main {
    public static void drawMap1(){
        Rectangle rectangle = new Rectangle();
        Circle circle = new Circle();
        Flower flower  = new Flower();
        Shape[] shapes = {rectangle,circle,rectangle,circle,flower};//向上转型
        for(Shape shape:shapes){
            shape.draw();
        }
    }
    public static void main(String[] args) {
        drawMap1();
    }
}

(2)可扩展能力强:若要新增一种新的形状, 使用多态的方式代码改动成本也较低:

class Triangle extends Shape{
    @Override
    public void draw() {
        System.out.println("Drawing a triangle.");
    }
}

2.6 避免在构造方法中调用重写的方法

class B{
    public B(){
        func();
    }
    public void func(){
        System.out.println("B.func()");
    }
}
class D extends B{
    private int num = 1;
    public void func(){
        System.out.println("D.func() "+num);
    }
}
public class Main {
    public static void main(String[] args) {
       D d = new D();
    }
}

输出结果为:

当在父类构造方法中调用与子类的同名的方法时,会执行子类的该方法;

并且由于d对象本身还未完成构造,num还处于未初始化的状态,故而值为0;

在实际编写程序时应该避免这样的编写方法;

猜你喜欢

转载自blog.csdn.net/m0_63299495/article/details/129814488