JavaSE学习之--继承和多态

news2024/11/22 14:47:30

 

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

作者: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”;

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

补充:

        其实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;
    }

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();
    }
}

总结:

        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;
    }

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("子类的构造方法!!!");
    }
}

 

注意事项:

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

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

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

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调用当前类的其他构造方法该怎么办?答案是没有办法,如果同时拥有会冲突,只能保证他们不能同时存在!

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

 

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();
    }
}

执行结果: 

 

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

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

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

先看代码: 

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();
    }
}

执行结果:

执行顺序:

 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成员可以跨包访问
    }
}

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

总结:

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

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

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

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修饰的

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();
}

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方法中调用该对象,让父类引用指向该对象,在方法内部实现了向上转型!

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

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();// 输出汪汪叫!
    }
}

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

所以,为了避免这种错误,需要利用关键字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.重写与重载的区别:

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

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

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

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();
        }
    }
}

 

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1050482.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【1++的Linux】之进程(四)

👍作者主页:进击的1 🤩 专栏链接:【1的Linux】 文章目录 一,进程创建二,进程等待三,进程终止 一,进程创建 在Linux中我们通过fork来创建一个新的进程。新创建的进程叫做子进程&…

No149.精选前端面试题,享受每天的挑战和学习

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…

No147.精选前端面试题,享受每天的挑战和学习

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…

logback.xml springboot 项目通用logback配置,粘贴即用,按日期生成

<configuration scan"false" scanPeriod"10 seconds"><!-- 定义日志存放的根目录 --><property name"log.dir" value"./logs" /><!-- 彩色日志依赖的渲染类 --><conversionRule conversionWord"clr&q…

求职面试,如何赢得面试官的满意赞许?

在面试的时候&#xff0c;我们会遇到很多突发事件&#xff0c;这些事件让我们不能很好地将面试进行下去。那么&#xff0c;在出现事情的时候&#xff0c;我们应该如何让面试官满意&#xff1f; 1、选择得体的服装 在面试的时候&#xff0c;想要面试官更好地选择自己&#x…

后端基础php

虚拟机安装网络方面名词介绍快速自建web环境&#xff08;phpstudy&#xff09;前端基础mysql语法前端【展示】----后端【功能实现】标准php 【ASP / ASPX / PHP / JSP】0基础 --->php入门编程--->代码 对逻辑要求高变量--->会改变的量 php---->$aHello…

C理解(二):指针,数组,字符串,函数

指针 int *p; 未绑定:*表示p为指针变量,占4字节 int a 1;p &a; 绑定:p与a地址绑定即p中存放a的地址 *p *p 1; 解引用:p间接访问a的存储空间 左值与右值 int a 1; 左值&#xff1a;变量对应的内存空间 右值&#xff1a;内存空间存储的数 野指针 野指针:指针变量定…

2023下半年信息系统集成设计师

选择题 第一章 信息基础知识第二章 信息系统集成第三章 专业技能知识第四章 项目管理一般知识第五章 项目立项管理第六章 项目整体管理第七章 项目范围管理第八章 项目进度管理第九章 项目成本管理第十章 项目质量管理第十一章 项目管理干系人第十三章 合同管理第十五章 配置管…

秋招应届毕业生求职 如何通过在线测评?

对于应届毕业生来说&#xff0c;求职应聘必定会遇到在线测评的环节&#xff0c;很多同学都会疑惑&#xff0c;不知道怎么应对&#xff0c;也有些同学说我已经很认真了&#xff0c;为何我别刷了下来&#xff1f; 这里面到底有何玄机&#xff1f; 1、应聘前要多做性格测试 …

Django模板加载与响应

前言 Django 的模板系统将 Python 代码与 HTML 代码解耦&#xff0c;动态地生成 HTML 页面。Django 项目可以配置一个或多个模板引擎&#xff0c;但是通常使用 Django 的模板系统时&#xff0c;应该首先考虑其内置的后端 DTL&#xff08;Django Template Language&#xff0c;D…

【Java】复制数组的四种方式

1. System.arraycopy() 用来将一个数组的&#xff08;一部分&#xff09;内容复制到另一个数组里面去。 定义&#xff1a; void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);例&#xff1a; int[] arr1 { 1, 2, 3, 4, 5 }; int[] arr2 new…

青藏高原1-km分辨率生态环境质量变化数据集(2000-2020)

青藏高原平均海拔4000米以上&#xff0c;人口1300万&#xff0c;是亚洲九大河流的源头&#xff0c;为超过15亿人口提供淡水、食物和其他生态系统服务&#xff0c;被誉为地球第三极和亚洲水塔。然而&#xff0c;在该地区的人与自然的关系的研究是有限的&#xff0c;尤其是在精细…

1500*C. Kefa and Park(dfstree)

Kefa and Park - 洛谷 Problem - 580C - Codeforces Examples input 4 1 1 1 0 0 1 2 1 3 1 4 output 2 input 7 1 1 0 1 1 0 0 0 1 2 1 3 2 4 2 5 3 6 3 7 output 2 解析&#xff1a; dfs遍历&#xff0c;记录前一个结点权值是否为1&#xff0c;并且累计路径1的个数…

MySQL学习笔记23

逻辑备份&#xff1a; 1、回顾什么是逻辑备份&#xff1f; 逻辑备份就是把数据库、数据表或者数据进行导出&#xff0c;导出到一个文本文件中。 2、逻辑备份工具&#xff1a; mysqldump&#xff1a;提供全库级、数据库级别以及表级别的数据备份。 mysqldumpbinlog&#xff…

1200*A. Flipping Game(前缀和)

解析&#xff1a; 100数据量&#xff0c;两层遍历每个区间&#xff0c;然后前缀和计算1的个数&#xff0c;维护最大值即可。 #include<bits/stdc.h> using namespace std; #define int long long const int N110; int n,a[N],res,sum[N]; signed main(){scanf("%ll…

[Linux调查局] 编译过程

编译过程 引子编译阶段预处理汇编编译链接 链接详解release && debug 引子 一个程序的编译分为4个部分: 预处理 头文件的展开条件编译宏的展开去掉注释 编译 生成汇编汇编 生成计算机认识的机器指令, 即二进制文件链接 将程序和 库 链接 &#x1f5e8;️这里有一个疑…

《Reinforcement Learning: An Introduction》第8章笔记

文章目录 Chapter 8 Planning and Learning with Tabular Methods8.1 Models and Planning8.2 Dyna: Integrated Planning, Acting, and Learning8.3 When the Models Is Wrong8.4 Prioritized Sweeping8.5 Expected vs. Sample Updates8.6 Trajectory Sampling8.7 Real-time D…

南京大学【软件分析】13 Static Analysis for Security

文章目录 1. Information Flow Security2. Confidentiality and Integrity3. Explicit Flows and Covert/Hidden Channels4. Taint Analysis污点分析案例 1. Information Flow Security 引起安全问题最主要的两大原因是&#xff1a;injection errors&#xff08;2013-2019排名…

Disucz插件-免费最全Disucz插件大全

在网络世界里&#xff0c;拥有一个活跃并充满吸引力的社区论坛是许多网站管理员和品牌经营者的梦想。然而&#xff0c;要让一个论坛充满活力、吸引大量用户&#xff0c;需要大量的内容。这就是为什么许多人转向Disucz插件的原因。Disucz插件不仅可以帮助您创建一个互动性强大的…

数据响应式原理

面试题&#xff1a;请阐述vue2响应式原理 vue官方阐述&#xff1a;https://cn.vuejs.org/v2/guide/reactivity.html 响应式数据的最终目标&#xff0c;是当对象本身或对象属性发生变化时&#xff0c;将会运行一些函数&#xff0c;最常见的就是render函数。 在具体实现上&#x…