目录
一、继承的基本介绍
继承可以解决代码复用,让我们的编程更加靠近人类思维.当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过 extends 来声明继承父类即可。
二、继承给编程带来的便利
- 代码的复用性提高了
- 代码的扩展性和维护性提高了
三、继承使用的细节问题
(1) 子类继承了所有的属性和方法,非私有的属性和方法可以在子类直接访问, 但是私有属性和方法不能在子类直接访问,要通过父类提供公共的方法去访问
(2) 子类必须调用父类的构造器, 完成父类的初始化
Base类是一个父类,里面有无参构造,Sub类继承了Base类,此时new一个Sub对象的时候,先调用父类的构造器,然后调用Sub的构造器
(3) 当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器,如果父类没有提供无参构造器,则必须在子类的构造器中用 super 去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不会通过(怎么理解。) [举例说明]
当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器
这里有一个父类Base
public class Base { private int n1 = 100; private int n2 = 200; private int n3 = 300; public Base() { System.out.println("父类Base()构造器被调用..."); } }
子类Sub
public class Sub extends Base{ public Sub() { System.out.println("子类Sub()构造器被调用"); } public Sub(String name){ System.out.println("子类Sub(String name)构造器被调用"); } }
测试类
public class Test { public static void main(String[] args) { Sub sub = new Sub(); System.out.println("======第二个对象======"); Sub sub1 = new Sub("jack"); } }
如果父类没有提供无参构造器,则必须在子类的构造器中用 super 去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不会通过
在Base加个有参构造器,然后把无参构造器删除,这时候我们写的构造器就把默认的无参构造器覆盖了,无参构造器就没有了
这时候去保存代码的时候,会发现,子类的构造器报错了
那么此时需要用带参数构造器,用到super
(4)如果希望指定去调用父类的某个构造器,则显式的调用一下 : super(参数列表)
public class Base { private int n1 = 100; private int n2 = 200; private int n3 = 300; public Base() { System.out.println("父类Base()构造器被调用..."); } public Base(String name, int age) { System.out.println("父类Base(String name,int age)构造器被调用..."); } public Base(String name) { System.out.println("父类Base(String name)构造器被调用..."); } }
public class Sub extends Base{ public Sub() { super("smith",10); System.out.println("子类Sub()构造器被调用"); } public Sub(String name,int age) { //1.调用父类的无参构造器,或者什么都不写,默认就是调用父类的无参构造器 super(); System.out.println("子类Sub(String name,int age)构造器被调用"); } public Sub(String name){ super("tom",30); System.out.println("子类Sub(String name)构造器被调用"); } }
public class Test { public static void main(String[] args) { System.out.println("======第三个对象======"); Sub sub2 = new Sub("king",10); } }
如果此时我们要调用父类的Base(String name)这个构造器,此时我们这样做
(5) super 在使用时,必须放在构造器第一行(super 只能在构造器中使用)
(6) super() 和 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();
}
}
class GrandPa {
String name = "大头爷爷";
String hobby = "旅游";
}
class Father extends GrandPa {
String name = "大头爸爸";
int age = 39;
}
class Son extends Father {
String name = "大头儿子";
}
当我们去new一个Son的时候,在内存里面发生了什么?
这里我们new一个Son,Son的父类是Father,而Father的父类是GrandPa,因为是先加载父类。在加载Son类信息的时候,会先查找父类,先加载父类信息,但是GrandPa也有一个父类,首先加载的是Object类,然后加载GrandPa,然后是Father类,最后加载Son。并且它们之间还有一种关联关系。
这些东西好了之后,就在堆中分配地址空间,那么这个空间里面到底有什么属性??
它首先会在GrandPa类分配属性,那么这里面由于是有值的,大头爷爷是放在常量池的,name的值用一个地址来表示,指向常量池,hobby也是。
接下来是Father类,我们会发现Father有name属性,GrandPa也有name属性,这样是不会冲突的,因为还会开辟一个空间,里面存放Father的属性值,name的属性值:大头爸爸,也是存放在常量值,然后用一个地址来指向常量池。
最后就是Son这个类了,又开辟一个空间,里面存放属性。
那么当我们用son去访问name、age、hobby的时候,规则又是什么?
System.out.println(son.name);
这个本类中有,直接返回大头儿子
这时候我们就要按照查找关系来返回信息
(1) 首先查看子类是否有该属性
(2) 如果子类有这个属性,并且可以访问,则返回信息
(3) 如果子类没有这个属性,就看父类有没有这个属性(如果父类有该属性,并且可以访问,就返回信息)
(4) 如果父类没有,就按照(3)的规则,继续找上级父类,直到Object
System.out.println(son.age);
这个Son类中没有,所以往上找自己的父类,父类里有age属性,那么这个age就是39。
System.out.println(son.hobby)
Son类没有,往Father找,Father也没有,去找GrandPa,这里面有,那么返回的就是旅游
public static void main(String[] args) {
Son son = new Son();
System.out.println(son.name); //大头儿子
System.out.println(son.age); //39
System.out.println(son.hobby); //旅游
}
那么现在又有一个问题,当Father类中的age属性,我们用private去修饰的时候,这时候new了对象过后,在内存里面还有没有这个age?
答案是这个age属性还是有的,但是我们不能去访问,私有只能在本类访问,我们可以写一个公有方法来使用
![]()
但是这里又会有一个有意思的问题,当Father类里的age属性值是私有的时候,此时GrandPa类有个public的age属性值,我们通过:son.age的时候,会不会跳过Father类这个私有的,到GrandPa里面去找这个公有的Age呢?
答案是不会的,因为在Father里面已经找到了这个age,只是被设为私有不能访问而已。
五、继承练习题
Computer.java
public class Computer {
private String cpu;
private int memory;
private int disk;
public Computer(String cpu, int memory, int disk) {
this.cpu = cpu;
this.memory = memory;
this.disk = disk;
}
public String getCpu() {
return cpu;
}
public void setCpu(String cpu) {
this.cpu = cpu;
}
public int getMemory() {
return memory;
}
public void setMemory(int memory) {
this.memory = memory;
}
public int getDisk() {
return disk;
}
public void setDisk(int disk) {
this.disk = disk;
}
public String getDetails() {
return "cpu="+cpu+" memory="+memory+" disk="+disk;
}
}
PC.java
public class PC extends Computer{
private String brand;
public PC(String cpu, int memory, int disk, String brand) {
super(cpu, memory, disk);
this.brand = brand;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public void printInfo(){
System.out.println("PC信息如下:");
System.out.println(getDetails()+" brand="+brand);
}
}
因为继承Computer,这里生成构造器的时候,会自动把父类的另外属性给你,子类的属性brand由自己初始化,父类由父类初始化,也就是super(cpu,memory,disk)
NotePad.java
public class NotePad extends Computer{
private String color;
public NotePad(String cpu, int memory, int disk, String color) {
super(cpu, memory, disk);
this.color = color;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public void printInfo(){
System.out.println(getDetails()+" color="+color);
}
}
Test.java
public class Test2 {
public static void main(String[] args) {
PC pc = new PC("Intel",32,500,"IBM");
pc.printInfo();
NotePad notePad = new NotePad("AMD", 64, 1000, "white");
notePad.printInfo();
}
}
六、super关键字
6.1 基本介绍
super 代表父类的引用,用于访问父类的属性、方法、构造器
6.2 基本语法
public class A {
public int n1=100;
protected int n2=200;
int n3=300;
private int n4=400;
public void test100(){}
protected void test200(){}
void test300(){}
private void test400(){}
}
public class B extends A {
public void hi() {
System.out.println(super.n1+" "+super.n2+" "+super.n3);
}
public void ok(){
super.test100();
super.test200();
super.test300();
}
}
6.3 super 给编程带来的便利/细节
6.4 Super使用细节
6.4.1 演示访问方法的规则
这里就举个例子,这里有A类和B类
public class A { public void sum(){ System.out.println("B类的sum()方法..."); } public void cal(){ System.out.println("A类的cal()方法..."); } }
public class B extends A { public void sum(){ System.out.println("B类的sum()方法..."); cal(); } }
我们想调用A类的cal()这个方法的时候,有3种办法:
(1)直接写cal(),它的查找顺序就是先从本类进行查找,如果没有则向上找父类,如果再没有还是继续往上找,直到Object
(2)可以直接写this.cal(),这个效果和直接写cal()一样,也是从本类开始找
(3)可以写成super.cal(),这个是没有查找本类的过程,而是直接进入到父类里面去找super的cal()方法,如果本类有cal()方法,这里不会去找。
为了演示第三种方法,此时我们在B类也增加一个cal方法
public void cal(){ System.out.println("B类的cal()方法..."); }
这里输出的是B类的cal()方法。
6.4.2 演示访问属性的规则
属性的访问规则和方法也是一样的,这里也举2个类举例一下
public class A { public int n1=100; protected int n2=200; int n3=300; private int n4=400; public void test100(){} protected void test200(){} void test300(){} private void test400(){} public void sum(){ System.out.println("B类的sum()方法..."); } public void cal(){ System.out.println("A类的cal()方法..."); } }
public class B extends A { public void hi() { System.out.println(super.n1 + " " + super.n2 + " " + super.n3); } public void ok() { super.test100(); super.test200(); super.test300(); } public void sum() { System.out.println("B类的sum()方法..."); super.cal(); //演示访问属性的规则 //n1 和 this.n1 查找的规则是 //(1) 先找本类,如果有,则调用,此时本类是有的 System.out.println(n1); //100 System.out.println(this.n1); //100 System.out.println(super.n1); //100 } public void cal() { System.out.println("B类的cal()方法..."); } }
这时候,B类中没有n1属性,此时调用sum()会打印出3个100,如果本类中加个n1=888,此时输出的顺序就是888,888,100
主要注意的是用super调用的时候,会直接去调用父类的方法和属性,而不考虑本类的。
基于刚刚的例子,我们多加一个Base类
public class Base { public int n1 = 999; public int age = 111; public void cal() { System.out.println("Base类的cal()方法..."); } public void eat() { System.out.println("Base类中的eat()方法..."); } }
此时我们写在B类中写test方法
//编写测试方法 public void test(){ System.out.println("super.n1="+super.n1); //直接定位到A类去找,输出:100 super.cal(); //定位到A类去找,因为A类中有cal()方法 }
如果A类中没有cal()方法,那么就会去Base类中去找cal()方法,这里遵循就近原则