提示:一段简单的代码,了解回顾java的动态绑定机制和继承
一、继承
注意
- 类只允许单继承,不允许多继承,支持多重继承
- java.lang.Object 类是 Java 语言的根类,任何类都是 Object 类的子类 / 间接子类
子类可以访问的父类成员
- 可访问
public 或 protected 修饰的成员
同包中缺省访问权限修饰符的成员- 不可以直接访问
不同包中缺省访问权限修饰符的成员
private 修饰成员
继承的细节
1、子类继承了所有的属性和方法,非私有的属性和方法可以在子类直接访问, 但是私有属性和方法不能在子类直接访问,要通过父类提供公共的方法去访问
2 、子类必须调用父类的构造器, 完成父类的初始化
3 、当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器,如果父类没有提供无 参构造器,则必须在子类的构造器中用 super 去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译报错。
4、 如果希望指定去调用父类的某个构造器,则显式的调用一下 : super(参数列表)
5 、super() 在使用时,必须放在构造器第一行
6 、super() 和 this() 都只能放在构造器第一行,因此这两个方法不能共存在一个构造器
7 、java 所有类都是 Object 类的子类, Object 是所有类的基类.
8、 父类构造器的调用不限于直接父类!将一直往上追溯直到 Object 类(顶级父类)
9、 子类最多只能继承一个父类(指直接继承),即 java 中是单继承机制。
思考:如何让 A 类继承 B 类和 C 类? 【A 继承 B, B 继承 C】
10、 不能滥用继承,子类和父类之间必须满足 is-a 的逻辑关系
二、动态绑定机制
1、动态绑定是指在执行期间(非编译期)判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。程序运行过程中,把函数(或过程)调用与响应调用所需要的代码相结合的过程称为动态绑定。
(编译阶段是确保方法的存在性,保证程序能顺利、安全运行。)
2、可以理解为:在程序运行过程中,根据具体的实例对象去具体确定是哪个方法。
3、方法看运行,属性看编译。
4、向上转型时,用父类引用执行子类对象,并可以用父类引用调用子类中重写了的同名方法。但是不能调用子类中新增的方法。
三、代码示例分析
/**
* @Author HYL
* @Date 2022/8/20 9:57
* @Version 1.0
*/
class Base{
int i;
Base(){
add(1);
System.out.println(i);
}
void add(int v){
i+=v;
System.out.println(i);
}
}
class MyBase extends Base{
MyBase(){
System.out.println("MyBase");
add(2);
}
@Override
void add(int v){
System.out.println("MyBase Add");
i+=v*2;
System.out.println(i);
}
}
public class test01 extends MyBase {
public static void main(String[] args) {
// go(new MyBase());
MyBase base=new test01();
go(base);
}
static void go(Base b){
b.add(8);
}
}
运行结果:
分析:
MyBase base :编译类型
new test01() :运行类型
MyBase base=new test01(); //向上转型,父类引用指向子类对象。
对象的创建过程:
- 1、加载类信息,只加载一次。
- 2、在堆中分配空间(地址)。
- 3、完成对象的初始化:
1) 默认初始化(赋初值 0,null)
2)显示初始化(变量定义时的赋值)
3)构造器的初始化 - 4、返回对象在堆中的地址给引用。
在继承中:子类必须调用父类的构造器, 完成父类的初始化 。
(1)、所以在new test01();执行时,会先隐式调用其直接父类MyBase的无参构造器,而MyBase又继承了Base,Base又继承了Object,所以在构造方法执行时(Object——>Base——>MyBase——>test01)。
(2)、在执行Base的无参构造时,调用add(1)方法,而其子类重写了add()方法,由动态绑定机制,会调用子类MyBase的add方法:先输出 MyBase Add ;在运算i+=v2;在输出i的值,i=2;add(1)执行完了,再继续执行输出语句,此时i的值已改变,输出i=2。
(3)、在执行MyBase的无参构造:输出MyBase;在执行add(2),执行自己的add方法,输出MyBase Add,再计算i+=v2,此时的i在步骤(2)中已经变为了2,所以计算后i=6,打印输出。
(4)、最后轮到test01执行自己的无参构造方法。
(5)、执行完构造方法,执行go(base),仍由于动态绑定机制,执行的是MyBase的add方法。先输出MyBase Add;在计算i+=v*2,此时的i在步骤三中已经变为了6,所以计算出i=22,打印输出。
package cn.hyl.login.test;
/**
* @Author HYL
* @Date 2022/8/20 9:57
* @Version 1.0
*/
class Base{
int i;
Base(){
add(1);
System.out.println(i);
}
void add(int v){
i+=v;
System.out.println(i);
}
}
class MyBase extends Base{
MyBase(){
System.out.println("MyBase");
add(2);
}
/*
@Override
void add(int v){
System.out.println("MyBase Add");
i+=v*2;
System.out.println(i);
}
*/
}
public class test01 extends MyBase {
public static void main(String[] args) {
// go(new MyBase());
MyBase base=new test01();
go(base);
}
static void go(Base b){
b.add(8);
}
}
运行结果:
分析:
在MyBase注释了add方法后:
(1)、所以在new test01();执行时,会先隐式调用其直接父类MyBase的无参构造器,而MyBase又继承了Base,Base又继承了Object,所以在构造方法执行时(Object——>Base——>MyBase——>test01)。
(2)、在执行Base的无参构造时,调用add(1)方法,由动态绑定机制,会调用父类Base的add方法:先i+=v;在输出i的值,i=1;add(1)执行完了,再继续执行输出语句,此时i的值已改变,输出i=1。
(3)、在执行MyBase的无参构造:输出MyBase;在执行add(2),(继承机制)执行父类的add方法,先计算i+=v,此时的i在步骤(2)中已经变为了1,所以计算后i=3,打印输出。
(4)、最后轮到test01执行自己的无参构造方法。
(5)、执行完构造方法,执行go(base),执行add(8)仍由于动态绑定机制,执行的是父类的父类(多重继承)Base的add方法。先计算i+=v,此时的i在步骤三中已经变为了2,所以计算出i=11,打印输出。