继承的基本语法继承快速入门案例继承的深入讨论/细节问题继承的本质分析(重要)
本文节选自我的另一篇文章,大家有兴趣可以看一看: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=400 test400
总结:
私有属性和方法不能在子类直接访问,要通过父类提供公共的方法去访问
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…
内存机制如下图所示:



