JavaSE学习之--继承和多态

 

 "越是不思考的人,越不愿倾听别人说话。"

作者:Mylvzi 

 文章主要内容:JavaSE学习之--继承和多态 

目录

一.继承(inheritance)

1.为什么要有继承

2.继承的概念

3.继承的语法及代码实现 

4.在子类中访问父类成员-->super关键字

  1.子类中访问父类成员

11.不存在同名成员变量

1.2存在同名成员变量

1.3 那如何在子类中访问父类的成员呢?通过super关键字! 

1,4子类的构造方法

注意事项:

1.super只能放在第一行,放在其他行就报错

2.如果父类的构造方法带参数,则在子类中调用父类的构造方法时也要带参数!(可以快速创建)

3.在子类的构造方法中super和this不能同时出现,且super只能出现一次

1.5super和this的比较

5.再谈初始化

6.protected关键字

1.同一个包不同类中成员的访问

2.不同包不同类中成员的访问 

7.Java中的继承关系(三种)

8.final关键字

1.修饰变量会使变量不能被修改(成为常量)

2.修饰类,代表此类无法被继承 

3.修饰方法,此方法无法被重写 

9.继承与组合

二.多态

1.向上转型

1.向上转型的定义:

2.实现向上转型的三种方式

1.直接赋值

2.方法传参

3.方法返回

3.向上转型的优点:

4.向上转型的缺点:

2.向下转型:

3.动态绑定

4.方法重写

1.重写的规则:

2.重写与重载的区别:

5.多态

1.多态的优点:

1.降低圆圈复杂度

2.可拓展性强

2.多态的缺陷:代码运行效率低

3.多态的注意事项:


 

一.继承(inheritance)

1.为什么要有继承

    我们前面学过类,类是对现实事物的抽象化处理,而通过类实例化的对象则可以用来表示事物。在现实生活中,事物与事物之间有着千丝万缕的联系,最常见的一种就是“父子关系”,儿子会继承父亲的很多特征,而自己又有自己独特的特征;再比如狗和猫都是动物,都具有动物的一些属性,如年龄,性别,毛发颜色等等等等;为了在计算机中表示这种关系,创建了“继承”!

2.继承的概念

  继承(inheritance):就是对事物共性的抽取,从而实现代码复用;

从和实际生活的联系来讲,继承就是将事物的共同属性抽取出来,用一个大类来表示,比如一个父亲有多个孩子,这些孩子都具有某些共性,如年龄,性别,将这些共同属性抽取出来,创建一个新类“Child”;

从代码实现来讲,继承关系的存在实现了代码的复用,子类继承了父类的属性和方法,大大提高效率;

JavaSE学习之--继承和多态_第1张图片

补充:

        其实Java中有很多这种“对共性进行抽取从而达到代码复用”的思路,比如前面学过的类变量,就是所有对象的共同属性的抽取 

对象的共性-->静态成员变量

类的共性-->继承extends

3.继承的语法及代码实现 

// 父类
class Animal {
    String name;
    int age;

    public void eat() {
        System.out.println(name+"i am eating!!!");
    }

    public void sleep() {
        System.out.println(name+"i am sleeping!!!");
    }
}

// 子类
// 通过关键字extends表示此类继承于父类
class Dog extends Animal {
    public void bark() {
        System.out.println(name+"汪汪叫!!!");
    }
}

class Cat extends Animal {
    public void mew() {
        System.out.println(name+"喵喵叫!!!");
    }
}
public class Test {
    public static void main(String[] args) {
        Dog dog1 = new Dog();
        dog1.name = "初一";
        dog1.age = 3;
        
        // dog1的成员变量中并没有name和age,但却能引用,则一定继承于父类
        System.out.println(dog1.name);
        System.out.println(dog1.age);

        dog1.eat();
        dog1.sleep();
        dog1.bark();
    }
}

总结:

1. 继承关键字-->extends,格式为:class 子类 extends 父类

2.子类会继承父类的成员和方法,实例化对象之后直接通过对象就能访问

3.子类继承父类的成员和方法之后,还需要有不同于父类的成员或方法(比如Dog类中的bark)

4.在子类中访问父类成员-->super关键字

  1.子类中访问父类成员

11.不存在同名成员变量

class Base {
    int a;
    int b;

    public void method1() {
        System.out.println("hello1");
    }
}

class Derived extends Base {
    int c;// 类特有的
    public void method2() {
        a = 30;// 访问父类继承下来的变量
        b = 20;// 访问父类继承下来的变量
        c = 10;// 访问子类自己的变量
    }

1.2存在同名成员变量

class Base {
    int a;
    int b;

    public void method1() {
        System.out.println("hello1");
    }
}

class Derived extends Base {
    int a;// 和父类同名
    int c;// 子类特有的

    public void method2() {
        a = 30;// 究竟是给父类赋值,还是给子类赋值?
        b = 20;
        c = 10;
    }

JavaSE学习之--继承和多态_第2张图片

1.3在子类中访问成员方法

class Base {
    int a;
    int b;

    public void method1() {
        System.out.println("hello1");
    }
}

class Derived extends Base {

    int c;
    // 和父类的方法构成了重载
    public void method1() {
        System.out.println("hello2");
    }

    // 子类特有的方法
    public void method2() {
        System.out.println("hello2");
    }
}
public class TestDemo1 {
    public static void main(String[] args) {
        Derived p1 = new Derived();
        p1.method1();
        p1.method2();
        // 输出hello1  hello2
        // 证明先执行子类的方法
        
        // 既不属于子类,又不属于父类,编译失败
//        p1.method3();
    }
}

JavaSE学习之--继承和多态_第3张图片

总结:

        1.父类,子类存在同名的成员变量,成员方法,优先访问子类的,子类中没有再去父类中寻找,寻找不到就编译报错

        2.子类和父类可构成方法的重载,将参数列表设置为不同,在调用时通过传参调用不同的方法!

1.3 那如何在子类中访问父类的成员呢?通过super关键字! 

  由于设计不好或者特殊场景需要,需要在子类中访问父类成员,但如果出现重名编译器会优先访问子类中的,并不会访问父类中的成员变量,那该怎么解决呢?Java中提供了关键字super,该关键字的作用是-->在子类方法中访问父类成员变量

class Base {
    int a;
    int b;

    public void method1() {
        System.out.println("hello1");
    }
}

class Derived extends Base {
    int a;
    int b;
    int c;

    public void method1() {
        a = 10;// 等价于this.a
        b = 20;// 等价于this.b

        // 通过super关键字来访问父类成员
        super.a = 30;
        super.b = 40;
    }

JavaSE学习之--继承和多态_第4张图片

super访问方法: 

class Derived extends Base {
    int a;
    int b;
    int c;

    public void method1() {
        System.out.println("hello2 这是子类方法");
    }

    public void method2() {
        a = 10;// 等价于this.a
        b = 20;// 等价于this.b

        // 通过super关键字来访问父类成员
        super.a = 30;
        super.b = 40;

        method1();// 输出hello2
        super.method1();// 输出hello1

总结:

1.super关键字的作用就是在子类方法中访问父类成员变量和方法!

2.super只能在子类的非静态方法中使用,因为super主要用来访问并实例化父类中的实例变量,而实例变量必须依赖于对象,静态方法不依赖于对象,所以super不能在子类的静态方法中使用! (super可以访问父类的类变量,但是不建议,更建议通过类名)

3.super只是关键字,提升了代码的可读性,并不代表对父类成员的引用!

1,4子类的构造方法

  构造方法是用来初始化成员变量的一种特殊方法,其在对象的创建过程中就会被调用,而且即使你不写构造方法,编译器会默认添加一个不带参数的构造方法,给你的成员变量赋初始值;在子类中,既有从父类继承过来的成员变量,又有自己独有的成员变量,那在构造方法中赋值的顺序有没有要求呢?答案是有的,先上结论:
  必须先对父类成员变量进行初始化才能对子类成员变量进行初始化!

也就是说在子类的构造方法中会先调用对父类的构造方法,再对子类的成员变量进行初始化。

class Base {
    int a;
    int b;

    // 父类的构造方法

    public Base() {
        System.out.println("父类的构造方法!!!");
    }
}

class Derived extends Base {
    int a;
    int b;
    int c;

    public Derived() {
        // 会默认有一个不带参数的super()
        // 代表父类的初始化
        System.out.println("子类的构造方法!!!");
    }
}

 JavaSE学习之--继承和多态_第5张图片

注意事项:

super()就代表调用父类的构造方法!!!

和this();的含义一样!!!

1.super只能放在第一行,放在其他行就报错

JavaSE学习之--继承和多态_第6张图片

2.如果父类的构造方法带参数,则在子类中调用父类的构造方法时也要带参数!(可以快速创建)
    // 带参数的父类的构造方法
    public Base(int a, int b) {
        this.a = a;
        this.b = b;
        System.out.println("这是父类构造方法");
    }

    // 子类中的构造方法
    // 必须先对父类成员变量进行初始化

public Derived(int a, int b, int a1, int b1, int c) {
        super(a, b);// 必须放在第一行
                    // 且必须要传参数
        this.a = a1;
        this.b = b1;
        this.c = c;

        System.out.println("这是子类构造方法");
    }

注意:如果父类提供的是一个显式的构造方法,则在子类中必须利用super进行传参!!! 

如果父类的构造方法不带参数,在子类的构造方法中可以不写,因为系统会自动提供一个不带参数的super();

3.在子类的构造方法中super和this不能同时出现,且super只能出现一次

    在构造方法那一节我们学过,对于一个类的构造方法,如果要使用this来调用当前类的其他构造方法,则this只能放在第一行,那如果我既想通过super来初始化父类的成员变量,又想通过this调用当前类的其他构造方法该怎么办?答案是没有办法,如果同时拥有会冲突,只能保证他们不能同时存在!

  为什么只能出现一次呢?原因在于继承结构的逻辑链!在继承结构中,子类会先调用父类的构造方法,再执行自身的初始化逻辑,这种执行顺序是线性的,不需要多次进行父类的初始化!

 JavaSE学习之--继承和多态_第7张图片

1.5super和this的比较

  我们学过super和this,发现他们有很多相同点,比如都是通过.操作符来访问变量或方法,那他们有哪些异同点呢?下面来总结一下:

相同点:

1.都是Java中的关键字。

2.都只能在非静态方法中访问非静态成员变量或非静态方法

3.在构造方法中调用时,都必须写在第一行,所以super和this不能同时使用; 

不同点:

1.this代表当前对象的引用,用于访问当前对象的成员变量及方法;super是用来访问从父类继承的成员或方法。或者说super是引用对象从父类继承部分的引用

2.在构造方法中,super会有默认的对父类的构造方法(先有父后有子),而this并没有默认的构造方法。

3.this(......) 代表当前对象的构造方法,super(.......)代表父类的构造方法,两者不能同时出现

5.再谈初始化

  先回顾一下静态代码块,实例代码块,构造方法之间执行的先后顺序!

class Test {
    int a;

    // 构造方法
    public Test() {
        System.out.println("这是构造方法!!!");
    }

    // 静态块
    static {
        System.out.println("这是静态代码块!!!");
    }

    // 实例代码块
    {
        System.out.println("这是实例代码块!!!");
    }
}



public class Test1 {
    public static void main(String[] args) {
        Test test1 = new Test();
        System.out.println("=============");
        Test test2= new Test();
    }
}

执行结果: 

 JavaSE学习之--继承和多态_第8张图片

总结:执行顺序-->静态代码块-->实例代码块-->构造方法 

且静态代码块只会被执行一次

那如果既有父类的代码块和构造方法,又有子类的代码块和构造方法,执行顺序是怎样的呢?

先看代码: 

class Test {
    int a;

    // 父类的构造方法
    public Test() {
        System.out.println("这是构造方法!!!");
    }

    // 父类的静态块
    static {
        System.out.println("这是静态代码块!!!");
    }

    // 父类的实例代码块
    {
        System.out.println("这是实例代码块!!!");
    }
}


class DTest extends Test {
    // 子类的构造方法
    public DTest() {
        System.out.println("这是子类的构造方法!!!");
    }

    // 子类的静态代码块
    static {
        System.out.println("这是子类的静态代码块!!!");
    }

    // 子类的代码块
    {
        System.out.println("这是子类的代码块!!!");
    }
}



public class Test1 {
    public static void main(String[] args) {
        DTest test1 = new DTest();
        System.out.println("=============");
        DTest test2= new DTest();
    }
}

执行结果:

JavaSE学习之--继承和多态_第9张图片

执行顺序:

 1.父类,子类的静态代码块

2.父类实例代码块,构造方法

3.子类的实例代码块,构造方法

所有的静态代码块无论实例化多少个对象都只会被执行一次!!!

6.protected关键字

  为了实现封装特性,引入了访问修饰限定符,不同的关键字有不同的权限

  protected是“受保护的”,它的权限是可以在同包不同类访问,也可以在不同包但是此包的类是另一个包中类的子类,也就是两个包之间的类存在继承关系!

1.同一个包不同类中成员的访问

class B {
    private int a;
    int b;
    protected int c;
    public int d;
}

class C extends B {
    // 同一个包不同类
    public void method() {
        super.a = 20;// err,private成员不能跨类访问
        super.b = 20;// 默认权限的成员可以跨类访问
        super.c = 20;// protected成员可以跨类访问
        super.d = 20;// public成员可以跨类访问
    }
}

2.不同包不同类中成员的访问 

class B {
    private int a;
    int b;
    protected int c;
    public int d;
}

// 不同包但存在继承关系之间成员的访问
class C extends B {
    public void method2() {
        super.a = 20;// err private成员不能跨类访问
        super.b = 20;// err 默认权限成员不能跨包访问
        super.c = 20;// protected成员在子类中可以访问
        super.d = 20;// public成员可以跨包访问
    }
}

JavaSE学习之--继承和多态_第10张图片

注意不要忘记引入父类的包:

总结:

1.继承是可以跨包的,如果另一个包中类是另一个包中的类的子类,则存在继承关系;子类会继承父类所有的成员和方法(但是不能全部访问,这取决于父类中成员或方法的访问权限)

2.我们有这么多的权限,那什么时候用什么权限呢?前期学习,大家都会使用一种粗暴的方法,把所有的成员设置为private,所有的方法设置为public,在前期对权限理解不深的时候我们可以这样设置,但使用哪个权限还是要具体问题具体分析,这取决于你对成员或方法的需求! 

7.Java中的继承关系(三种)

JavaSE学习之--继承和多态_第11张图片

8.final关键字

  final关键字可以修饰变量,方法,类

1.修饰变量会使变量不能被修改(成为常量)

        final int b = 20;
        b = 30;// err b是常量,无法修改常量的值

2.修饰类,代表此类无法被继承 

final class B {
    private int a;
    int b;
    protected int c;
    public int d;
}

// 此时B就无法被继承
class C extends B {
}

平常我们使用的String类就是被final修饰的

JavaSE学习之--继承和多态_第12张图片

3.修饰方法,此方法无法被重写 

9.继承与组合

  继承反映的是类与类之间的关系,同样的,组合 也是一种反应类与类关系的一种思想!

它代表一个类可以由多个类组成,比如一个Person类可以由Leg,Nose,Head类组成,不同于继承的是组合并没有关键字,只需在类中包含其他类就可以实现类的组合,请看下面代码:

class Nose{};
class Head{};
class Leg{};

class Person{
    private Nose nose;// 可以复用Nose类中的方法和属性
    private Head head;// 可以复用Head类中的方法和属性
    private Leg leg;// 可以复用Leg类中的方法和属性
}

class Stu extends Person{
    // 继承了人的鼻子,头,腿
};

二.多态

多态就是通过父类引用不同对象,调用同一个重写的方法时,所表现的行为不同(所产生的结果不同),这种现象就叫做多态! 

要理解多态,需要理解几个基本概念:向上转型,重写,向下转型,动态绑定 

1.向上转型

1.向上转型的定义:

    父类引用引用子类对象就叫做向上转型(子类对象被父类引用)

class Animal{};
class Dog extends Animal{};
class Cat extends Animal{};
public class Test {
    public static void main(String[] args) {
        // 向上转型
        Animal animal1 = new Dog();
        Animal animal2 = new Cat();
}

JavaSE学习之--继承和多态_第13张图片

2.实现向上转型的三种方式

1.直接赋值

2.方法传参

3.方法返回 

1.直接赋值

        Animal animal1 = new Dog();
        Animal animal2 = new Cat();

2.方法传参

    将方法形参设置为父类引用,实参是子类对象,通过调用方法实现将父类引用指向子类对象!

class Animal{
    public void makeSound() {
        System.out.println("动物发出叫声!");
    }
};
class Dog extends Animal{
    @Override
    public void makeSound() {
        System.out.println("小狗在汪汪叫!");
    }
};
//class Cat extends Animal{};
public class Test {
    public static void animalMakeSound(Animal animal) {
        animal.makeSound();
    }
    public static void main(String[] args) {
        Animal mydog = new Dog();
        animalMakeSound(mydog);
    }

        在main方法中我们创建了一个Dog对象,并将其向上转型为Animal类型,接着在animalMakeSound方法中调用该对象,让父类引用指向该对象,在方法内部实现了向上转型!

JavaSE学习之--继承和多态_第14张图片

    通过上述代码我们可以看出,将形参设置为父类引用最大的好处就是增加了代码的通用性!因为实参可以是任意子类对象

3.方法返回

    方法的返回类型可以是类 ,通过将方法的返回值设置为父类引用,让返回的对象被父类引用引用!!

// 父类
class Animal{
    public void eat() {
        System.out.println("eating");
    }
};

class Cat extends Animal{
    String name;
    int age;

    public Cat(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public void eat() {
        System.out.println("cat is eating!");
    }
}
class Dog extends Animal{
    String name;
    int age;

    public Dog(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public void eat() {
        System.out.println("dog is eating!");
    }
};
//class Cat extends Animal{};
public class Test {
    public static void eat() {
        System.out.println("我在吃饭");
    }

    // 父类引用可以做返回值,还是让父类引用指向返回的对象
    public static Animal buyAnimal(String var) {
        if("狗".equals(var)) {
            return new Dog("小狗",2);
        } else if ("猫".equals(var)) {
            return new Cat("小米",20);
        }else {
            return null;
        }
    }

    public static void main(String[] args) {
        Animal animal1  =buyAnimal("狗");
        Animal animal2  =buyAnimal("猫");

        animal1.eat();
        animal2.eat();
    }

3.向上转型的优点:

1.实现代码的通用性,实现多态性

  通过父类引用子类对象,我们可以创建出更通用的代码,不需要去关注具体子类的类型

2.可以避免很多类型检查和强制类型转换

class Animal {
    public void makeSound() {};
}

class Cat extends Animal {
    @Override
    public void makeSound() {
        System.out.println("喵喵叫!");
    }
}

class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("汪汪叫!");
    }
}


public class Test1 {
    public static void main(String[] args) {
        Animal[] animals = new Animal[2];
        animals[0] = new Cat();
        animals[1] = new Dog();

        for (Animal animal: animals) {
            // 不适用向上转型
            // 需要进行类型检查和强制类型转换
            if (animal instanceof Dog) {
                Dog dog = (Dog) animal;
                dog.makeSound();
            } else if (animal instanceof Cat) {
                Cat cat = (Cat) animal;
                cat.makeSound();
            }
        }

        // 使用向上转型
        // 不需要关注具体的子类对象,代码更通用
        for (Animal animal2: animals) {
            animal2.makeSound();
        }
    }
}

对于这个代码,在 animals[0] = new Cat();实际上就发生了向上转型,animals[0]代表一个父类引用,new Cat()代表创建了一个新的Cat对象

4.向上转型的缺点:

无法使用子类特有的方法

 如果想通过父类引用去使用子类特有的方法,就要进行“向下转型”,也就是使父类引用发生强制类型转换!

2.向下转型:

    在很多时候子类有其特有的方法,而又不能通过重写来调用,这时候就需要通过向下转型来调用子类特有的方法,向下转型就是将父类引用强制类型转化为子类对象

class Animal{};
class Dog extends Animal{
    public void bark() {
        System.out.println("汪汪叫!");
    }
}
public class testdemo1 {
    public static void main(String[] args) {
        Animal animal1 = new Dog();
        // 向下转型
        Dog dog1 = (Dog)animal1;
        dog1.bark();// 输出汪汪叫!
    }
}

    但向下转型是一个很“危险”的操作,因为父类引用是一个较大的范围,你强制转化的类型是一个小范围,会出现意想不到的错误;同时,如果多个类继承于一个父类,要注意向下转型时不要转错类型!如果转换错误,编译器会报错:

JavaSE学习之--继承和多态_第15张图片

所以,为了避免这种错误,需要利用关键字instanceof来检验当前引用所指向的类型 

class Animal{};
class Dog extends Animal{};
class Cat extends Animal{};
public class Test {
    public static void main(String[] args) {
        // 向上转型
        Animal animal1 = new Dog();
        // 父类引用            引用子类类型
        if(animal1 instanceof Dog) {// 利用instanceof运算符进行引用类型检查
            // 强制类型转化
            Dog dog = (Dog)animal1;
            System.out.println("类型检查成功");
        }else {
            System.out.println("ClassCastException!!!");
        }
    }
}

3.动态绑定

   动态绑定是面向对象编程非常重要的一个概念,也被叫做运行时绑定或运行多态性。他是指在运行时才确定对象的实际类型,并根据实际类型调用相应的方法,在通俗一点说就是只有在运行时我才知道引用的对象是谁,我该调用的方法是谁,这之前谁都不知道!!!

class Animal {
    public void makeSound() {};
}

class Cat extends Animal {
    @Override
    public void makeSound() {
        System.out.println("喵喵叫!");
    }
}

class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("汪汪叫!");
    }
}


public class Test1 {
    public static void main(String[] args) {
        Animal animal = new Dog();// 动态绑定
        animal.makeSound();// 实际调用的是Dog的方法  只有在运行时才知道实际对象是Dog
    }
}

4.方法重写

  重写(override):是子类对父类非static,final,private修饰,非构造方法的方法重写;通过重写,可以在子类内部实现属于子类的行为;

1.重写的规则:

1.被重写的父类方法不能被static,final,private修饰,不能是构造方法

2.重写的方法必须要和父类方法保持一致,即返回值,方法名,参数列表都要完全相同

3.子类的重写方法的权限要>=父类被重写方法的权限,比如父类方法的权限是public,那子类重写的方法就不能是protected!

4.子类中重写的方法可以被@Override 注解来显式指定,有了这个注解,可以多一层检验,比如父类的方法名是eat,而在子类中却写成ate,此时编译器就会报错(因为要求子类的方法名要和父类被重写方法的名称一致!)

2.重写与重载的区别:

JavaSE学习之--继承和多态_第16张图片

重载是类方法多态性的体现,重写是子类与父类之间多态性的体现

方法重载是一种静态绑定,即在编译过程中就知道对应的方法(根据用户的传参)

方法重写是一种动态绑定,只有在运行时才能明确调用的方法 

5.多态

  再说回多态,以上内容都是为了理解多态而阐述的,所谓多态就是子类调用同一个父类方法时所产生的行为不同的思想,以下是多态的条件:

1.必须存在父类与子类的继承关系

2.必须有方法的重写

3.必须通过父类的引用来调用方法

多态的体现:在程序运行时,引用对象的不同,所产生的结果也不同!

1.多态的优点:

1.降低圆圈复杂度

  圆圈复杂度是反应一段代码理解难易程度的表示,如果一段代码平铺直叙,那他的圆圈复杂度就很低,此代码易于理解,但如果代码中含有大量的条件语句,循环语句,那么此代码的圆圈复杂度就很高,不易于理解(要判断的东西太多)

class Shape {
    public void drawMap() {};
}

class Rect extends Shape {
    @Override
    public void drawMap() {
        System.out.println("矩形");
    }
}

class Flower extends Shape {
    @Override
    public void drawMap() {
        System.out.println("❀");
    }
}

class Cycle extends Shape {
    @Override
    public void drawMap() {
        System.out.println("⚪");
    }
}

public class Test2 {
    // 依次打印⚪矩形⚪矩形花

    public static void main(String[] args) {
        // 1.不使用多态  要判断子类的具体类型
        Rect rect = new Rect();
        Flower flower = new Flower();
        Cycle cycle = new Cycle();

        String[] shapes = {"cycle","rect","cycle","rect","flower"};
        for (String shape:shapes) {
            if(shape.equals("cycle")) {
                cycle.drawMap();
            } else if (shape.equals("rect")) {
                rect.drawMap();
            }else {
                flower.drawMap();
            }
        }

         // 2.使用多态
        Shape[] shapes = {new Cycle(),new Rect(),new Cycle(),new Rect(),new Flower()};
        for (Shape shape: shapes) {
            shape.drawMap();
        }
    }
}

 JavaSE学习之--继承和多态_第17张图片

2.可拓展性强

  比如对于shape类,现在有一个新的子类Triangle,可以直接新创建一个Triangle类,让其包含drawMap方法;如果不使用多态,则需要在打印时多添加一个if-else语句 

2.多态的缺陷:代码运行效率低

3.多态的注意事项:

1.属性不具有多态性,通过父类引用只能访问到父类的属性,而无法访问到子类的属性

2.构造方法没有多态性

先来看如下代码:


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

// 执行结果:D.func() 0

  实例化d对象时,会先调用父类的构造方法,父类的构造方法中调用了func方法,此时会触发动态绑定,调用子类的func方法,而此时 private int num = 1;这段代码并未被执行,也就是说num还未被初始化,未被初始化则被赋值为0,所以打印:D.func() 0

结论: "用尽量简单的方式使对象进入可工作状态", 尽量不要在构造器中调用方法(如果这个方法被子类重写, 就会触 发动态绑定, 但是此时子类对象还没构造完成), 可能会出现一些隐藏的但是又极难发现的问题.

你可能感兴趣的:(学习)