继承的基本语法继承快速入门案例继承的深入讨论/细节问题继承的本质分析(重要)
本文节选自我的另一篇文章,大家有兴趣可以看一看:Java总结六:面向对象编程(中)
继承可以解决代码复用,让我们的编程更加靠近人类思维.当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过 extends 来声明继承父类即可。
class 子类 extends 父类 {}
其中:
子类就会自动拥有父类定义的属性和方法
父类又叫超类,基类。
子类又叫派生类。
☕️Extends01类:
package com.hj.第八章面向对象编程.继承;public class Extends01 { public static void main(String[] args) { Pupil pupil = new Pupil(); pupil.name = "银角大王~"; pupil.age = 11; pupil.testing(); pupil.setScore(50); pupil.showInfo(); System.out.println("======="); Graduate graduate = new Graduate(); graduate.name = "金角大王~"; graduate.age = 23; graduate.testing(); graduate.setScore(80); graduate.showInfo(); }}
☕️Student类:(父类,是 Pupil 和 Graduate 的父类)
package com.hj.第八章面向对象编程.继承;//父类,是 Pupil 和 Graduate 的父类public class Student { //共有属性 public String name; public int age; private double score;//成绩 //共有的方法 public void setScore(double score) { this.score = score; } public void showInfo() { System.out.println("学生名 " + name + " 年龄 " + age + " 成绩 " + score); }}
☕️Pupil类:(Pupil 继承 Student 类)
package com.hj.第八章面向对象编程.继承;//让 Pupil 继承 Student 类public class Pupil extends Student { public void testing() { System.out.println("小学生 " + name + " 正在考小学数学.."); }}
☕️Graduate类:(Graduate 继承 Student 类)
package com.hj.第八章面向对象编程.继承;public class Graduate extends Student { public void testing() {//和 Pupil 不一样 System.out.println("大学生 " + name + " 正在考大学数学.."); }}
输出结果:
小学生 银角大王~ 正在考小学数学..学生名 银角大王~ 年龄 11 成绩 50.0=======大学生 金角大王~ 正在考大学数学..学生名 金角大王~ 年龄 23 成绩 80.0
##继承给编程带来的便利
代码的复用性提高了代码的扩展性和维护性提高了继承的深入讨论/细节问题
1.子类继承了所有的属性和方法,非私有的属性和方法可以在子类直接访问, 但是私有属性和方法不能在子类直接访问,要通过父类提供公共的方法去访问。
本细节分析:
现在提供两个类:
☕️base类:父类
package com.hj.第八章面向对象编程.继承细节;public class base { //父类 //4 个属性 public int n1 = 100; protected int n2 = 200; int n3 = 300; private int n4 = 400; public base() { //无参构造器 System.out.println("父类 base()构造器被调用...."); } public void test100() { System.out.println("test100"); } protected void test200() { System.out.println("test200"); } void test300() { System.out.println("test300"); } private void test400() { System.out.println("test400"); }}
☕️Sub类:子类
package com.hj.第八章面向对象编程.继承细节;public class Sub extends base { //子类 public Sub() {//无参构造器 System.out.println("子类 Sub()构造器被调用...."); } public void sayOk(){ }}
1.1 非私有的属性和方法可以在子类直接访问, 但是私有属性和方法不能在子类直接访问
在Sub类sayOk方法中添加输出语句访问父类的属性n1、n2、n3、n4:
package com.hj.第八章面向对象编程.继承细节;public class Sub extends base { //子类 public Sub() {//无参构造器 System.out.println("子类 Sub()构造器被调用...."); } public void sayOk(){ //我们可以发现父类的非private属性和方法都可以在子类访问 System.out.println(n1+" "+n2+" "+" "+n3+" "+n4); test100(); test200(); test300(); test400(); }}
这时会报错:
总结:说明了父类的非private属性和方法都可以在子类访问。
1.2私有属性和方法不能在子类直接访问,要通过父类提供公共的方法去访问
☕️在base类中增加公共方法getN4() :
//父类提供一个public的方法,返回了n4 public int getN4() { return n4; } //call:调用,调用test400方法。 public void callTest400(){ test400(); }
☕️在子类中调用公共方法getN4()以输出私有属性n4:
package com.hj.第八章面向对象编程.继承细节;public class Sub extends base { //子类 public Sub() {//无参构造器 System.out.println("子类 Sub()构造器被调用...."); } public void sayOk(){ //要通过父类提供公共的方法去访问 System.out.println("n4=" + getN4()); callTest400(); }}
增加一个类用于调用:
☕️ExtendsDetail类:
package com.hj.第八章面向对象编程.继承细节;public class ExtendsDetail { public static void main(String[] args) { Sub sub=new Sub(); sub.sayOk(); }}
☕️输出结果:
父类 base()构造器被调用....子类 Sub()构造器被调用....n4=400test400
总结:
私有属性和方法不能在子类直接访问,要通过父类提供公共的方法去访问
2、子类必须调用父类的构造器, 完成父类的初始化
本细节分析:
看上面的代码和分析的输出结果可以看到。
父类 base()构造器被调用....子类 Sub()构造器被调用....
里面的机制其实是下面代码:
public Sub() {//无参构造器 //super(); //默认调用父类的无参构造器 System.out.println("子类 Sub()构造器被调用....");}
子类无参构造器默认有一个语句:super();,不管写不写它都存在,它的作用是默认调用父类的无参构造器。
3、当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器,如果父类没有提供无参构造器,则必须在子类的构造器中用 super 去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不会通过。
本细节分析:
3.1: 当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器。
☕️在子类Sub下增加一个有参构造器:
package com.hj.第八章面向对象编程.继承细节;public class Sub extends base { //子类 public Sub() {//无参构造器 //super(); //默认调用父类的无参构造器 System.out.println("子类 Sub()构造器被调用...."); } //当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器 public Sub(String name){//有参构造器 //什么都不写 System.out.println("子类 Sub(String name)构造器被调用...."); }}
☕️创建子类对象sub2:
package com.hj.第八章面向对象编程.继承细节;public class ExtendsDetail { public static void main(String[] args) { System.out.println("===创建第一个对象==="); Sub sub=new Sub();//创建子类对象sub //sub.sayOk(); System.out.println("===创建第二个对象==="); Sub sub2=new Sub("jack");//创建子类对象sub2 }}
☕️运行结果:
父类 base()构造器被调用....子类 Sub()构造器被调用....===创建第二个对象===父类 base()构造器被调用....子类 Sub(String name)构造器被调用....
可以看到运行后两个对象都会首先调用父类的无参构造器。
3.2: 如果父类没有提供无参构造器,则必须在子类的构造器中用 super 去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不会通过。(前提是父类有有参构造器)
☕️修改父类代码如下:
package com.hj.第八章面向对象编程.继承细节;public class base { //父类// public base() { //无参构造器// System.out.println("父类 base()构造器被调用....");// } public base(String name,int age){ System.out.println("父类 base(String name,int age)构造器被调用...."); }}
此时子类就会报错:
在‘com.hj.第八章面向对象编程.继承细节.base’中没有可用的默认构造函数
这里还要说明一点:
父类是必须要有有参构造器才会有以上错误的,如果没有有参构造器,子类的构造器就不会出现错误,因为创建一个类后,类是默认含有无参构造器的。这一点要非常的注意
解决方法:
☕️指定父类调用有参构造器:
public class Sub extends base { //子类 public Sub() {//无参构造器 //super(); //默认调用父类的无参构造器 super("hj",20); System.out.println("子类 Sub()构造器被调用...."); } public Sub(String name){//有参构造器 super("CLN",20); System.out.println("子类 Sub(String name)构造器被调用...."); }}
☕️运行结果:
===创建第一个对象===父类 base(String name,int age)构造器被调用....子类 Sub()构造器被调用....===创建第二个对象===父类 base(String name,int age)构造器被调用....子类 Sub(String name)构造器被调用....
4、如果希望指定去调用父类的某个构造器,则显式的调用一下 : super(参数列表)
本细节分析
1.若想用子类的有参构造器Sub(String name,int age)调用父类无参构造可使用以下代码:
public class Sub extends base { //子类 public Sub() {//无参构造器 super("hj",20); System.out.println("子类 Sub()构造器被调用...."); } //当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器 public Sub(String name){//有参构造器 super("CLN",20); System.out.println("子类 Sub(String name)构造器被调用...."); } public Sub(String name,int age){ //1、要调用父类的无参构造器, 如下或者 什么都不写,默认就是调用 super() super(); }}
public class ExtendsDetail { public static void main(String[] args) { Sub sub3=new Sub("z",20); }}
☕️运行结果为:
父类 base()构造器被调用....
2.若想用子类的有参构造器Sub(String name,int age)调用父类有参构造base(String name)可使用以下代码:
public Sub(String name,int age){ //1、要调用父类的无参构造器, 如下或者 什么都不写,默认就是调用 super() //super(); //2、要调用父类的 base(String name) 构造器 super("hj"); }
☕️运行结果为:
父类 base(String name)构造器被调用....
3.同理若想用子类的有参构造器Sub(String name,int age)调用父类有参构造base(String name,int age)可使用以下代码:
public Sub(String name,int age){ //1、要调用父类的无参构造器, 如下或者 什么都不写,默认就是调用 super() //super(); //2、要调用父类的 base(String name) 构造器 //super("hj"); //3、要调用父类的 base(String name, int age) 构造器 super("king", 20);}
5、super 在使用时,必须放在构造器第一行(super 只能在构造器中使用)
若super不在构造器第一行则会报错:
大家想一想为什么必须放在构造器第一行?
因为子类构造器必须先要调用父类构造器,等父类构造器使用完以后子类构造器才能够继续执行,现有”父“才有”子“。
6、super() 和 this() 都只能放在构造器第一行,因此这两个方法不能共存在一个构造器(注意不是不能用this,是不能用this())
7、java 所有类都是 Object的子类,Object类是所有类的基类。
8、父类构造器的调用不限于直接父类!将一直往上追溯直到 Object 类(顶级父类)
9、子类最多只能继承一个父类(指直接继承),即 java 中是单继承机制。
思考:
如何让 A 类继承 B 类和 C 类?
答案:A 继承 B, B 继承 C
10、不能滥用继承,子类和父类之间必须满足 is-a 的逻辑关系
分析下面案例:
public class ExtendsTheory { public static void main(String[] args) { Son son = new Son();//内存的布局 //?-> 这时请大家注意,要按照查找关系来返回信息 //(1) 首先看子类是否有该属性 //(2) 如果子类有这个属性,并且可以访问,则返回信息 //(3) 如果子类没有这个属性,就看父类有没有这个属性(如果父类有该属性,并且可以访问,就返回信息..) //(4) 如果父类没有就按照(3)的规则,继续找上级父类,直到 Object..、 System.out.println(son.name);//返回就是大头儿子 //System.out.println(son.age);//返回的就是 39 //System.out.println(son.getAge());//返回的就是 39 System.out.println(son.hobby);//返回的就是旅游 }}class GrandPa { //爷爷类 String name = "大头爷爷"; String hobby = "旅游";}class Father extends GrandPa {//父类 String name = "大头爸爸"; private int age = 39; public int getAge() { return age; }}class Son extends Father { //子类 String name = "大头儿子";}
运行结果:
旅游
这里为什么是旅游呢?
因为:
要按照查找关系来返回信息
(1) 首先看子类是否有该属性
(2) 如果子类有这个属性,并且可以访问,则返回信息
(3) 如果子类没有这个属性,就看父类有没有这个属性(如果父类有该属性,并且可以访问,就返回信息…)
(4) 如果父类没有就按照(3)的规则,继续找上级父类,直到 Object…
内存机制如下图所示: