继承的好处: 1、提高了代码的复用性 2、让类与类之间产生了关系,为多态提供了前提 简单的继承图解: ![图片](https://i-blog.csdnimg.cn/blog_migrate/f47120e17fa942f9edbb6973bcee67a0.gif) Java中支持单继承,不直接支持多继承,但对c++中的多继承机制进行了改良(多重继承和多实现) 单继承:一个子类只能有一个直接父类 多继承:一个子类可以有多个直接父类 Java为什么不支持多继承? 因为如果两个父类中有相同的方法时,jvm不知道运行哪一个。会产生不确定性。 但是支持多重继承:B 继承 A,C 继承 B,间接的实现了多继承 这样就会出现继承体系。 当要使用一个继承体系时: 1、查看该体系中的顶层类,了解该体系的基本功能 2、创建体系中的最子类对象,完成功能的使用。 什么时候定义继承呢? 当类与类之间存在着所属关系的时候,就定义继承。如:Xxx是yyy中的一种,xxx extends yyy 所以判断所属关系,可以简单看,如果继承后,被继承的类中的功能,都可以被该子类所具备,那么继承成立。如果不是,不可以继承。 成员变量: 当本类的成员和局部变量同名用this区分。 当子父类中的成员变量同名用super区分父类 This与super的用法类似 This代表本类对象的引用,super:代表一个父类空间。 ![图片](https://i-blog.csdnimg.cn/blog_migrate/890112df65574d27e2c5e01ba8c16cc6.gif) 成员函数: 特点:当子父类中出现成员函数一模一样的情况,会运行子类的函数,这种现象,称为覆盖操作 函数两个特性: 1、重载 :同一个类中() 2、覆盖 :子父类,覆盖也称为重写,覆写 覆盖注意事项: 1、子类方法覆盖父类方法时,子类权限必须要大于等于父类权限 2、静态只能覆盖静态
public class Test{ public static void main(String[] args) { Zi z = new Zi(); z.show(); } } //在继承中,相同的变量定义,调用是就近原则,不存在覆盖一说 //而相同的函数名定义,叫做覆盖,运行子类永远都是运行覆写过的方法 //一样可以用super进行调用父类的函数和变量 class Fu{ int x = 5; public void show(){ System.out.println(x + " ~~~~~ 父类"); } } class Zi extends Fu{ int x = 10; public void show(){ System.out.println(x + " ~~~~~ " + super.x); } } | 这样的情况不属于覆盖
class Fu{ int x = 5; private void show(){ //父类私有化了,在eclipse中也有黄线提示。 System.out.println(x + " ~~~~~ 父类"); } } Static 只有一方有的话,也不能覆盖。 class Zi extends Fu{ int x = 10; public void show(){ // System.out.println(x + " ~~~~~ " + super.x); } } | 什么时候使用覆盖操作:
当对一个类进行子类的扩展时,子类需要保留父类的功能声明,但是要定义子类中该功能的特有内容时,就使用覆盖操作完成。 | 子父类中的构造函数的特点: (在子类构造对象时,发现:访问子类构造函数时,父类也运行了。) 原因是:在子类的构造函数中第一行有一个默认的隐式语句,super(); 子类的实例化过程: 子类中所有的构造函数默认都会访问父类中的空参构造函数
public class Test{ public static void main(String[] args) { Zi z = new Zi(); } } class Fu{ Fu(int x){//有参数的构造函数存在。空参构造将不存在,须手动创建显示构造空参 System.out.println("fu"); } } class Zi extends Fu{ Zi(){ //super() //隐式语句,调用空参构造,如果父类没有空参构造,则编译出错 //没有空空参?默认是没有其他构造函数的话,就有一个空参。如果有其他构造函数。则隐式空参构造失效,须手动添加 System.out.println("zi"); } } | Implicit super constructor Fu() is undefined. Must explicitly invoke another constructor 不明确的父类构造Fun()是未定义的,必须显示的创建一个构造函数 | 为什么子类实例化的时候要访问父类中的构造函数呢? 因为:子类继承了父类,获取到了父类中的内容(属性),所以在使用内容之前要先看父类是如何对自己的内容进行初始化的。 如果父类中没有空参数构造函数,那么子类的构造函数必须用super明确要调用父类中哪个构造函数。 注意:super语句必须要定义在构造函数的第一行,因为,父类的初始化要先完成 注意:如果使用了this()调用了本类构造函数时,那么super就没有了。因为super和this都只能定义在第一行 如果想要同时使用 this() 和super(),可以间接的分开调用本类其他构造函数 子类实例化过程 ---- 图解
![图片](https://i-blog.csdnimg.cn/blog_migrate/206391de17f979d1aa6b44381c97cea0.gif) public class Test { public static void main(String[] args) { new Zi().show(); /* * 1:new zi() 发现有父类,则先加载父类的空参构造函数,并初始化。 * 2:父类中有 show();但是子类继承了父类,并且是由子类触发的,所以this.show();并且该方法已经被覆盖,所以会执行子类的show; * 此时,子类,成员变量并未完成显示初始化(有默认初始化,局部变量没有默认初始化),所以,第一次打印结果 num = 0; * 3:父类初始化完成之后,回到子类构造函数,此时已经完成了初始化,对象创建成功,执行 show();,所以 num = 5 * */ } } class Fu{ Fu(){ show(); } public void show(){ System.out.println("~~~~~~~~~~~~父类"); } } class Zi extends Fu{ int num = 5; Zi(){ show(); } public void show(){ System.out.println("子类~~~~~~~~" + num); } } | |
一个对象实例化过程:
Person p = new Person();
1、JVM会读取指定的路径下的Person.class 文件,并加载进内存
(并会先加载Person的父类(如果有直接的父类的情况下))
2、在堆内存中开辟空间,分配地址。
3、并在对象空间中,对对象中的属性进行默认初始化
4、调用对应的构造函数进行初始化
5、在构造函数中,第一行会先调用父类中的构造函数进行初始化。
6、父类初始化完毕后,再对子类的属性进行显示初始化
7、再进行子类构造函数的特定初始化
8、初始化完毕后,将地址赋值给引用变量