[Java基本语法] 继承与多态

🌸个人主页:https://blog.csdn.net/2301_80050796?spm=1000.2115.3001.5343

🏵️热门专栏:🍕 Collection与数据结构 (92平均质量分)https://blog.csdn.net/2301_80050796/category_12621348.html?spm=1001.2014.3001.5482

🧀线程与网络(96平均质量分) https://blog.csdn.net/2301_80050796/category_12643370.html?spm=1001.2014.3001.5482

🍭MySql数据库(93平均质量分)https://blog.csdn.net/2301_80050796/category_12629890.html?spm=1001.2014.3001.5482

🍬算法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12676091.html?spm=1001.2014.3001.5482

感谢点赞与关注~~~

目录

  • [1. 继承与多态](#1. 继承与多态)
    • [1.1 继承](#1.1 继承)
      • [1.1.1 为什么需要继承](#1.1.1 为什么需要继承)
      • [1.1.2 继承的概念](#1.1.2 继承的概念)
      • [1.1.3 继承的语法格式](#1.1.3 继承的语法格式)
      • [1.1.4 父类成员访问](#1.1.4 父类成员访问)
      • [1.1.5 super关键字](#1.1.5 super关键字)
      • [1.1.6 子类构造方法](#1.1.6 子类构造方法)
      • [1.1.7 super和this](#1.1.7 super和this)
      • [1.1.8 继承体系下的代码块](#1.1.8 继承体系下的代码块)
      • [1.1.9 再谈访问限定符](#1.1.9 再谈访问限定符)
      • [1.1.10 继承方式](#1.1.10 继承方式)
      • [1.1.11 final关键字](#1.1.11 final关键字)
      • [1.1.12 继承与组合](#1.1.12 继承与组合)
    • [1.2 多态](#1.2 多态)
      • [1.2.1 多态的概念](#1.2.1 多态的概念)
      • [1.2.2 多态实现的条件](#1.2.2 多态实现的条件)
      • [1.2.3 重写](#1.2.3 重写)
      • [1.2.4 向上转型和向下转型](#1.2.4 向上转型和向下转型)
      • [1.2.5 避免在构造方法中使用重写的方法](#1.2.5 避免在构造方法中使用重写的方法)

1. 继承与多态

1.1 继承

1.1.1 为什么需要继承

Java中使用类对现实中的事物进行描述的时候,由于世间事物错综复杂,事物之间难免会存在一些特定的关联 ,这就是程序设计时候所需要考虑的问题。

比如:猫和狗都是动物

我们使用Java语言就会有如下描述

java 复制代码
public class Dog{
	string name;
	int age;
	float weight;
	public void eat(){
		System.out.println(name + "正在吃饭");
	}
public void sleep(){
		System.out.println(name + "正在睡觉");
	}
void Bark(){
		System.out.println(name + "汪汪汪~~~");
	}
}
public class Cat{
	string name;
	int age;
	float weight;
	public void eat(){
		System.out.println(name + "正在吃饭");
	}
	public void sleep()
	{
		System.out.println(name + "正在睡觉");
	}
	void mew(){
		System.out.println(name + "喵喵喵~~~");
	}
}

通过上述代码发现,猫类和狗类中有大量重复的成员,它们都有自己的名字,年龄,体重,它们都会吃,会睡觉,只有叫声是不一样的。那么我们能否对这写共性进行抽取呢?面向对象的程序设计思想中提出了继承的概念,专门用来进行共性抽取,以实现代码的复用。

1.1.2 继承的概念

继承的机制:这是面向对象程序设计中使代码可以复用的最重要的手段,他允许程序员在保持一个类原有特征的同时进行扩展增加功能 ,这样产生的类,称为派生类 。很好地体现了面向对象程序设计的清晰的层次结构。总之,继承所解决的问题就是:共性的抽取,实现代码的复用。

如上图所示,Dog和Cat类都继承了Animal类,其中Animal类称为父类,基类,或者超类,Dog和Cat类称为子类或者派生类,继承之后,子类就可以复用父类中的成员,子类在实现时只关心自己的成员即可。

1.1.3 继承的语法格式

在Java中入如果要表示继承关系,需要使用extends关键字,具体格式如下:

java 复制代码
修饰符 class 子类 extends 父类{
//
}

现在,我们使用继承语法对猫类和狗类进行重新设计:

java 复制代码
public class Animal{
	String name;
	int age;
	public void eat(){
		System.out.println(name + "正在吃饭");
	}
	public void sleep(){
		System.out.println(name + "正在睡觉");
	}
}
public class Dog extends Animal{
	void bark(){
		System.out.println(name + "汪汪汪~~~");
	}
}
public class Cat extends Animal{
	void mew(){
		System.out.println(name + "喵喵喵~~~");
	}
}

注意事项

  • 子类会将父类的成员变量成员方法继承到子类中
  • 子类继承父类之后,必须要新添加自己特有的成员,否则就没必要继承了
  • static修饰的成员不可以继承,因为他们不属于对象,他们属于类(这时候可以返回上一篇文章看看删掉的那一行了)

1.1.4 父类成员访问

在继承体系中,既然子类把父类的方法和字段都继承下来了,那子类怎样去访问父类中的成员呢?

1.1.4.1子类中访问父类的成员变量
  1. 子类和父类不存在同名的情况
java 复制代码
public class Base {
	int a;
	int b;
}
public class Derived extends Base{
	int c;
	public void method(){
		a = 10; // 访问从父类中继承下来的a
		b = 20; // 访问从父类中继承下来的b
		c = 30; // 访问子类自己的c
	}
}

不同名的情况下,可直接访问父类成员

  1. 子类和父类同名的情况
java 复制代码
public class Base {
	int a;
	int b;
	int c;
}
public class Derived extends Base{
	int a; // 与父类中成员a同名,且类型相同
	char b; // 与父类中成员b同名,但类型不同
	public void method(){
		a = 100; // 访问父类继承的a,还是子类自己新增的a?自己的
		b = 101; // 访问父类继承的b,还是子类自己新增的b? 自己的
		c = 102; // 子类没有c,访问的肯定是从父类继承下来的c
	}
}

子类中的成员访问遵循以下原则

  • 如果访问的成员变量中,子类里有,优先访问自己的成员变量
  • 如果访问的成员变量中,子类中没有,但父类中有,则访问父类继承下来的
  • 如果访问的成员变量子类和父类重名,优先访问自己的

成员变量的访问遵循就近访问原则,自己有优先用自己的,如果没有再从父类中去找

1.1.4.2 子类中访问父类的成员方法
  1. 成员方法不同名
java 复制代码
public class Base {
	public void methodA(){
		System.out.println("Base中的methodA()");
	}
}
public class Derived extends Base{
	public void methodB(){
		System.out.println("Derived中的methodB()方法");
	}
	public void methodC(){
		methodB(); // 访问子类自己的methodB()
		methodA(); // 访问父类继承的methodA()
	}
}
  1. 成员方法同名
java 复制代码
public class Base {
	public void methodA(){
		System.out.println("Base中的methodA()");
	}
	public void methodB(){
		System.out.println("Base中的methodB()");
	}
}
public class Derived extends Base{
	public void methodA(int a) {
		System.out.println("Derived中的method(int)方法");//重载
	}
	public void methodB(){
		System.out.println("Derived中的methodB()方法");
	}
	public void methodC(){
		methodA(); // 没有传参,访问父类中的methodA()
		methodA(20); // 传递int参数,访问子类中的methodA(int)
		methodB(); // 直接访问,则永远访问到的都是子类中的methodB(),基类的无法访问到
	}
}

总结

  • 通过子类对象访问父类与子类不同名的方法时,优先在子类中找,否则在父类中找
  • 通过子类对象访问父类与子类同名的方法时,若两种方法构成重载,根据调用参数的不同调用相应的方法,至于参数列表相同的情况,我们在后面解释到方法的重写的时候再详细解释

那么问题来了,如果在子类中,我偏要访问父类的成员呢?这时我们就需要引入下一个关键字。

1.1.5 super关键字

既然想要在子类中访问父类的成员,直接访问是无法做到的,这时,Java就为我们提供了super关键字 ,这个关键字的作用是:在子类方法中访问父类成员

java 复制代码
public class Base {
	int a;
	int b;
	public void methodA(){
		System.out.println("Base中的methodA()");
	}
	public void methodB(){
		System.out.println("Base中的methodB()");
	}
}
public class Derived extends Base{
	int a; // 与父类中成员变量同名且类型相同
	char b; // 与父类中成员变量同名但类型不同
	// 与父类中methodA()构成重载
	public void methodA(int a) {
		System.out.println("Derived中的method()方法");
	}
	// 与基类中methodB()构成重写(即原型一致,重写后序详细介绍)
	public void methodB(){
		System.out.println("Derived中的methodB()方法");
	}
	public void methodC(){
		// 对于同名的成员变量,直接访问时,访问的都是子类的
		a = 100; // 等价于: this.a = 100;
		b = 101; // 等价于: this.b = 101;
		// 注意:this是当前对象的引用
		// 访问父类的成员变量时,需要借助super关键字
		// super是获取到子类对象中从基类继承下来的部分
		super.a = 200;
		super.b = 201;
		// 父类和子类中构成重载的方法,直接可以通过参数列表区分清访问父类还是子类方法
		methodA(); // 没有传参,访问父类中的methodA()
		methodA(20); // 传递int参数,访问子类中的methodA(int)
		// 如果在子类中要访问重写的基类方法,则需要借助super关键字
		methodB(); // 直接访问,则永远访问到的都是子类中的methodA(),基类的无法访问到
		super.methodB(); // 访问基类的methodB()
		}
	}

注意事项

  • 只能在非静态方法中使用
  • 在子类方法中,访问父类的成员变量和方法会用到
  • super还有其他用法,后续介绍

1.1.6 子类构造方法

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

java 复制代码
public class Base {
	public Base(){
		System.out.println("Base()");
	}
}
public class Derived extends Base{
	public Derived(){
		// super(); // 注意子类构造方法中默认会调用基类的无参构造方法:super(),
		// 用户没有写时,编译器会自动添加,而且super()必须是子类构造方法中第一条语句,
		// 并且只能出现一次
		System.out.println("Derived()");
	}
}
public class Test {
	public static void main(String[] args) {
		Derived d = new Derived();
	}
}

在子类的构造方法中,并没有写任何关于父类的构造代码,但是在构造子类对象时,先执行了父类的构造方法,然后执行子类的构造方法,因为:子类对象中成员是有两部分组成的,基类继承下来的以及子类新增加的部分 。先有父再有子 ,所以在构造子类对象时候 ,先要调用基类的构造方法,将从基类继承下来的成员构造完整,然后再调用子类自己的构造方法,将子类自己新增加的成员初始化完整 。
注意

  • 如果父类,显式定义无参或者默认构造方法时,在子类构造方法的第一行有默认的super()调用,即调用父类的构造方法
  • 如果父类的构造方法是带有参数的,此时需要在子类构造方法中主动调用父类构造方法
  • 在子类构造方法中,super调用父类构造方法的语句必须放在子类构造方法的第一行
  • super只能在子类构造方法中出现一次,且不可以与this同时出现

1.1.7 super和this

【相同点】

  • 都是Java语言中的关键字
  • 只能在非静态方法中使用,用来访问非静态成员方法和字段
  • 在构造方法中调用时,必须是构造方法中的第一条语句,而且不能同时存在

【不同点】

  • this是指当前对象的引用,super相当于是子类对象从父类继承下来部分成员的引用
  • 在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性
  • 在构造方法中,==this调用本类的构造方法,super调用父类的构造方法,==但是两种方法不可以同时出现
  • 构造方法中一定会存在super调用,用户不写编译器会默认增加,但是this不写则没有

1.1.8 继承体系下的代码块

还记得我们之前讲的代码块吗,我们简单回顾,主要有两种代码块实例代码块和静态代码块 ,在没有继承关系下的执行顺序是:静态代码块>实例代码块

java 复制代码
class Person {
	public String name;
	public int age;
	public Person(String name, int age) {
		this.name = name;
		this.age = age;
		System.out.println("构造方法执行");
	}
	{
		System.out.println("实例代码块执行");
	}
	static {
		System.out.println("静态代码块执行");
	}
}
public class TestDemo {
	public static void main(String[] args) {
		Person person1 = new Person("zhangsan",10);
		System.out.println("============================");
		Person person2 = new Person("lisi",20);
	}
}
  • 静态代码块先执行,并且只执行一次,在类加载阶段执行
  • 有对象创建时,才会执行实例代码块,实例代码块执行完成后,最后执行构造方法
    (详细内容请参考前面的文章)

要是加入了继承关系呢?

java 复制代码
class Person {
	public String name;
	public int age;
	public Person(String name, int age) {
		this.name = name;
		this.age = age;
		System.out.println("Person:构造方法执行");
	}
	{
		System.out.println("Person:实例代码块执行");
	}
	static {
		System.out.println("Person:静态代码块执行");
	}
}
class Student extends Person{
	public Student(String name,int age) {
	super(name,age);
		System.out.println("Student:构造方法执行");
	}
	{
		System.out.println("Student:实例代码块执行");
	}
	static {
		System.out.println("Student:静态代码块执行");
	}
}
public class TestDemo4 {
	public static void main(String[] args) {
		Student student1 = new Student("zhangsan",19);
		System.out.println("===========================");
		Student student2 = new Student("lisi",20);
		}
}

执行结果:

Person:静态代码块执行

Student:静态代码块执行

Person:实例代码块执行

Person:构造方法执行

Student:实例代码块执行

Student:构造方法执行

===========================

Person:实例代码块执行

Person:构造方法执行

Student:实例代码块执行

Student:构造方法执行

经过上述分析,我们可以得出以下结论:

  • 父类静态>子类静态>父类实例>父类构造>子类实例>子类构造,原理就是,在java文件被写好之后,类就已经存在了,所以不管是父类还是子类,静态代码块都会被优先执行,且父类优先于子类,在之后实例化子类对象的时候,由于子类继承于父类,所以父类的实例代码块和构造方法优先被加载,创建一个对象的时候,先为对象分配内存空间,于是实例代码块被执行,之后调用构造方法,构造方法被执行,之后就是子类的实例代码块和构造方法
  • 第二次实例化的时候,父类和子类的静态代码块都不会再被执行,他们只执行一次,这是因为类只加载一次.

1.1.9 再谈访问限定符

再封装的章节,我们引入了访问限定符,主要限定:类或者类中成员能否再类外或者其他包中被访问

NO 范围 private default protected public
1 同一包中的同一类
2 同一包中的不同类 ×
3 不同包中的子类 × ×
4 不同包中的非子类 × × ×

这里记忆的时候,分为同一包和不同包,同一类和不同类,子类和非子类.

那么父类中不同访问权限的成员,在子类中的可见性怎么样呢?

java 复制代码
// extend01包中
public class B {
	private int a;
	protected int b;
	public int c;
	int d;
}
// extend01包中
// 同一个包中的子类
public class D extends B{
	public void method(){
		// super.a = 10; // 编译报错,父类private成员在相同包子类中不可见
		super.b = 20; // 父类中protected成员在相同包子类中可以直接访问
		super.c = 30; // 父类中public成员在相同包子类中可以直接访问
		super.d = 40; // 父类中默认访问权限修饰的成员在相同包子类中可以直接访问
	}
}
// extend02包中
// 不同包中的子类
public class C extends B {
	public void method(){
		// super.a = 10; // 编译报错,父类中private成员在不同包子类中不可见
		super.b = 20; // 父类中protected修饰的成员在不同包子类中可以直接访问
		super.c = 30; // 父类中public修饰的成员在不同包子类中可以直接访问
		//super.d = 40; // 父类中默认访问权限修饰的成员在不同包子类中不能直接访问
	}
}
// extend02包中
// 不同包中的类
public class TestC {
	public static void main(String[] args) {
		C c = new C();
		c.method();
		// System.out.println(c.a); // 编译报错,父类中private成员在不同包其他类中不可见
		// System.out.println(c.b); // 父类中protected成员在不同包其他类中不能直接访问
		System.out.println(c.c); // 父类中public成员在不同包其他类中可以直接访问
		// System.out.println(c.d); // 父类中默认访问权限修饰的成员在不同包其他类中不能直接访问
	}
}
  • 如上面的代码所示,想要在子类中访问到父类中的成员,在允许访问到的前提下,我们可以通过super访问到
  • 父类中的private成员虽然在子类中不可以访问到,但是也继承到了子类中
  • 注意在使用访问限定符时,要认证思考,不可以滥用访问限定符,考虑好类中的字段和方法提供给"谁"用,从而加上合适的访问限定符

1.1.10 继承方式

在Java中只支持以下几种继承方式

注意Java中不支持多继承
就像一个孩子不可以有多个父亲一样,但是一个父亲可以有多个孩子,就像上面的第三种

1.1.11 final关键字

我们有时不希望一个类被继承,我们希望在继承上进行限制,这时我们就需要用到final关键字

,而且final不仅可以修饰类,还可以用来修饰变量,成员方法

  1. 修饰变量或者字段,表示常量(即不可以被修改)
java 复制代码
final int a=10;
//a=20 error
  1. 修饰类,表示一个类不可以被继承,称为密封类
java 复制代码
final public class Animal{
//
}
//public class Cat extends Animal{
//
//} error
  1. 修饰方法:表示该方法不可以被重写

1.1.12 继承与组合

和继承类似,组合也是一种表达类之间关系的方式,也可以实现代码的复用。组合并没有用到什么关键字,也没有固定的语法格式,仅仅是将一个类的实例作为另外一个类的字段

继承表示的是一种is a 的关系,比如猫是动物

而组合表示的是一种has a 的关系,比如人有心脏

java 复制代码
class Heart{
//
}
class Brain{
//
}
class Lungs{
//
}
class Person{
   private Heart heart;
   private Brain brain;
   private Lungs lungs;
}

组合和继承都可以实现代码的复用,该继承还是组合,一般看实际的应用场景

1.2 多态

1.2.1 多态的概念

通俗来说就是:多种形态,具体一点就是去完成某个行为,当不同的对象去完成的时候会产生出不同的效果

总的来说:同一件事情发生在不同对象的身上,就会产生不同的结果

1.2.2 多态实现的条件

Java中想要实现多态,必须要满足以下几个条件,缺一不可:

  • 必须在继承体系下
  • 子类必须要对父类中的方法进行重写
  • 通过父类引用调用重写的方法
    多态实现:在代码运行时,当传递不同类对象时,会调用对应类中的方法
java 复制代码
public class Animal {
	String name;
	int age;
	public Animal(String name, int age){
		this.name = name;
		this.age = age;
	}
	public void eat(){
		System.out.println(name + "吃饭");
	}
}
public class Cat extends Animal{
	public Cat(String name, int age){
		super(name, age);
	}
@Override
	public void eat(){
		System.out.println(name+"吃鱼~~~");
	}
}
public class Dog extends Animal {
	public Dog(String name, int age){
		super(name, age);
	}
	@Override
	public void eat(){
		System.out.println(name+"吃骨头~~~");
	}
}
public class TestAnimal {
	// 编译器在编译代码时,并不知道要调用Dog 还是 Cat 中eat的方法
	// 等程序运行起来后,形参a引用的具体对象确定后,才知道调用那个方法
	// 注意:此处的形参类型必须时父类类型才可以
	public static void eat(Animal a){
		a.eat();
	}
	public static void main(String[] args) {
		Cat cat = new Cat("元宝",2);
		Dog dog = new Dog("小七", 1);
		eat(cat);
		eat(dog);
	}
}

当类的调用者在编写eat这个方法的时候,参数类型为Animal,此时在方法内部并不关注当前的a指向的是那个类型的实例,此时a这个引用调用eat方法可能会有多种不同的表现,这种行为称为多态

1.2.3 重写

  1. 概念
    重写:也称为覆盖。重写就是对非静态方法,非private修饰的方法,非final修饰的方法,非构造方法的实现过程进行重新编写 ,返回值和形参都不能改变,即外壳不变,实现核心重写,好处就是子类可以在父类的基础上实现自己的方法,定义特定于自己的行为
  2. 规则
  • 子类在重写父类的方法时,一般情况下返回值和形参都不能改变
  • 被重写的方法返回值类型有时可以不同,但是他们必须具有父子关系
  • 访问权限不可以比父类中被重写的方法更低,例如:父类方法被public修饰,则子类方法中重写该方法就不能声明为private
  • 父类方法不可以被static,final,private修饰,也不可以是构造方法,否者不可以被重写
  • 重写的方法,编译器会用"@override"来注解,可以检查合法性
  1. 重写和重载的区别
区别点 重写 重载
参数列表 一定不可以修改 必须修改
返回类型 一定不可以修改(除非有父子关系) 可以修改
访问限定符 一定不可以降低权限 可以修改
  1. 静态绑定与动态绑定
  • 静态绑定:也称为前期绑定,在编译时根据用户传递的参数类型就可以知道调用哪个方法,典型代表为方法的重载
  • 动态绑定:也称为后期绑定,在编译时,不可以确定方法的行为,无法知道调用哪个方法,需要等程序运行时才可以确定。

1.2.4 向上转型和向下转型

  1. 向上转型
    1. 概念:创建一个子类对象,把他当做父类对象来使用,就是范围从小到大的转换
    2. 语法格式:==父类类型 对象名=new 子类类型()
java 复制代码
Animal animal=new Cat("miaomiao",2);

我们就拿上面的代码来说明:

  1. 使用场景
  • 直接赋值

  • 方法传参

  • 方法返回

java 复制代码
public class TestAnimal {
// 2. 方法传参:形参为父类型引用,可以接收任意子类的对象
	public static void eatFood(Animal a){
		a.eat();
	}
// 3. 作返回值:返回任意子类对象
	public static Animal buyAnimal(String var){
		if("狗".equals(var) ){
			return new Dog("狗狗",1);
		}else if("猫" .equals(var)){
			return new Cat("猫猫", 1);
		}else{
			return null;
	}
}
public static void main(String[] args) {
	Animal cat = new Cat("元宝",2); // 1. 直接赋值:子类对象赋值给父类对象
	Dog dog = new Dog("小七", 1);
	eatFood(cat);
	eatFood(dog);
	Animal animal = buyAnimal("狗");
	animal.eat();
	animal = buyAnimal("猫");
	animal.eat();
	}
}

通过上述的代码,我们不难发现向上转型的有限,他可以使得代码更加简单灵活 ,但是缺点也很明显,不可以调用子类特有的方法,怎么办呢,我们这里便引出了向下转型

  1. 向下转型

    将一个子类对象经过向上转型之后当做了父类的对象使用,无法再调用到子类的方法,但是我们有时候会想要调用子类特有的方法,此时:将父类引用再还原为子类对象即可,即向下转型

    1. 语法格式:子类类型 对象名=(子类类名)经过向上转型的父类对象
      从上述语法格式来看,其实就是强制类型转换
    2. 安全性问题
      向下转型存在不安全的问题,不同于向上转型,是安全的,比如原来一个对象是狗类,向上转为了动物类,再向下转型的时候必须转回狗类,不可以转为猫类
    java 复制代码
    public class TestAnimal {
    	public static void main(String[] args) {
    		Cat cat = new Cat("元宝",2);
    		Dog dog = new Dog("小七", 1);
    		// 向上转型
    		Animal animal = cat;
    		animal.eat();
    		animal = dog;
    		animal.eat();
    		// 编译失败,编译时编译器将animal当成Animal对象处理
    		// 而Animal类中没有bark方法,因此编译失败
    		// animal.bark();
    		// 向上转型
    		// 程序可以通过编程,但运行时抛出异常---因为:animal实际指向的是狗
    		// 现在要强制还原为猫,无法正常还原,运行时抛出:ClassCastException
    		cat = (Cat)animal;
    		cat.mew();
    		// animal本来指向的就是狗,因此将animal还原为狗也是安全的
    		dog = (Dog)animal;
    		dog.bark();
    	}
    }
    1. 解决方案
      使用instanceof判断一个对象所属于的类是否属于一个类的父类
    java 复制代码
    public class TestAnimal {
    	public static void main(String[] args) {
    	Cat cat = new Cat("元宝",2);
    	Dog dog = new Dog("小七", 1);
    	// 向上转型
    	Animal animal = cat;
    	animal.eat();
    	animal = dog;
    	animal.eat();
    	if(animal instanceof Cat){
    		cat = (Cat)animal;
    		cat.mew();
    	}
    	if(animal instanceof Dog){
    		dog = (Dog)animal;
    		dog.bark();
    	}
    }
    }

1.2.5 避免在构造方法中使用重写的方法

下面展示一段有坑的代码

java 复制代码
class B {
	public B() {
		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 Test {
	public static void main(String[] args) {
		D d = new D();
	}
}

运行结果:D.func() 0

难道结果不应该是1吗,我们下面解释为什么不是1

在构造D对象的同时,会调用B的构造方法,在子类构造的时候,先构造父类。B的构造方法调用了func方法,此时会触发动态绑定 ,会调用D中的func,此时D还没有触发构造方法,D自身还没有构造,此时num处于未初始化状态 ,值为0,所以输出为0.

所以我们在构造方法中调用重写方法时一定要注意,在自己编程是尽量避免这种行为

相关推荐
XiaoLeisj2 小时前
【JavaEE初阶 — 多线程】单例模式 & 指令重排序问题
java·开发语言·java-ee
paopaokaka_luck2 小时前
【360】基于springboot的志愿服务管理系统
java·spring boot·后端·spring·毕业设计
dayouziei2 小时前
java的类加载机制的学习
java·学习
励志成为嵌入式工程师3 小时前
c语言简单编程练习9
c语言·开发语言·算法·vim
捕鲸叉3 小时前
创建线程时传递参数给线程
开发语言·c++·算法
A charmer3 小时前
【C++】vector 类深度解析:探索动态数组的奥秘
开发语言·c++·算法
Peter_chq3 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
Yaml44 小时前
Spring Boot 与 Vue 共筑二手书籍交易卓越平台
java·spring boot·后端·mysql·spring·vue·二手书籍
小小小妮子~4 小时前
Spring Boot详解:从入门到精通
java·spring boot·后端
hong1616884 小时前
Spring Boot中实现多数据源连接和切换的方案
java·spring boot·后端