一、为什么需要继承
我们编写了两个类,一个是Pupil类(小学生),还有一个是 Graduate(大学毕业生),发现两个类的属性和方法 有很多是相同的,于是便引出了 继承 可以减轻代码的冗余,提升代码的复用性
把多个类共有的方法提取到父类中去,多个类继承父类,可以直接使用父类中的方法
个人理解 类就是模板,父类就是模板的模板
二、继承基本介绍
当多个类存在相同的属性[变量]和方法时,可以从这些类中抽象出 父类,在父类中定义这些相同的属性和方法,只需要通过 extends 来 声明继承父类即可
继承示意图
三、继承的基本语法
class 子类 extends 父类{
}
1、子类会自动拥有父类定义的属性和方法
2、父类又叫 超类、 基类、
3、子类又叫派生类
四、快速入门案例
父类 Student 共有属性方法
package com.lmdedu.extend_.improve;
//父类,是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 和 Graduate 写特有属性和方法 并继承 父类Student的 属性方法
package com.lmdedu.extend_.improve;
//extends Student继承父类Student
public class Pupil extends Student {
//写独特属性和方法
public void testing(){
System.out.println("小学生" + name + "正在考小学数学..");
}
}
package com.lmdedu.extend_.improve;
//extends Student继承父类Student
public class Graduate extends Student{
public void testing(){
System.out.println("大学生" + name + "正在考大学数学..");
}
}
五、继承的细节和注意事项
1、子类 继承了所有的属性和方法,非私有的属性和方法可以在子类直接访问,但是私有属性不能在子类 直接访问,要通过父类提供的公共的方法去访问
父类 Base
package com.lmdedu.extend_;
public class Base {//父类
public int n1 = 100;
protected int n2 = 200;
int n3 = 300;
private int n4 = 400;
public Base(){//无参构造器
System.out.println("base()....");
}
//在父类提供一个public的方法 获取私有的 属性
public int getN4(){
return n4;
}
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");
}
//为了调用私有化 方法 test400,提供一个public方法
public void callTest400(){
test400();
}
}
子类 Sub
package com.lmdedu.extend_;
public class Sub extends Base{//子类Sub继承父类 Base
public Sub(){//无参构造器
System.out.println("Sub().....");
}
public void sayOk(){
//我们发现父类的 非private属性和方法 均可以调用
//属性
System.out.println(n1);
System.out.println(n2);
System.out.println(n3);
//方法
test100();
test200();
test300();
//但是私有属性不能在子类 直接访问,要通过父类提供的公共的方法去访问
System.out.println("n4 =" + getN4());
callTest400();
}
}
main主函数
package com.lmdedu.extend_;
public class ExtendsDetail {
public static void main(String[] args) {
Sub sub = new Sub();
sub.sayOk();
}
}
2、子类必须调用父类的构造器,完成父类的初始化
父类构造器
public Base(){//无参构造器
System.out.println("base()....");
}
子类构造器
public Sub(){//无参构造器
System.out.println("Sub().....");
}
可以看出 base()...先输出,说明 先进行 父类构造器的初始化,再完成子类构造器的初始化
3、当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器
若父类没有提供无参构造器,必须在子类的构造器中使用 super 去指定使用 父类的哪个构造 器完成父类的初始化,否则编译不通过
父类中未提供无参构造器 ,在子类的构造器中使用super进行指定父类的构造器
子类构造器 如下
public Sub(){//无参构造器
//默认调用父类的无参构造器,当父类没有无参构造器时,进行指定
super("lmd",20);
System.out.println("Sub().....");
}
4、若希望指定调用 父类的 某个构造器 需要显式的调用一下 super(参数列表);
5、super在使用时,必须放在构造器的第一行 (super只能在构造器中使用)
6、super() 和 this() 都只能放在构造器的第一行,因此不能共存在 一个构造器中(this()是在构造器中调用构造器)
7、java所有的类都是Object的子类(快捷键ctrl + h可以看到所有类的继承关系)
8、父类构造器的调用不限于直接父类!将一直向上追溯到Object类(顶级父类)
让Base继承 TopBase
TopBase 构造器
public TopBase(){
System.out.println("TopBase()构造器被调用");
}
所以会输出
9、子类 最多只能 直接继承 一个父类,java中是单继承机制
思考如何 让A类 继承B类和 C类
可以让A继承B,B继承C
10、不能滥用继承, 子类和父类之间必须满足 is-a 关系
Person is a Music?
Person Music
Music extends Person//不合理
Animal
Cat extends Animal //合理
六、继承本质详解
下面将通过一个案例分析,当子类继承父类,创建子类对象时,内存中到底发生了什么
结论:当子类对象创建好后,建立 查找的关系
package com.lmdedu.extend_;
/**
*讲解继承的本质
*/
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 = "大头儿子";
}
分析创建子类对象时,内存中到底发生了什么
Son son = new Son();
1、先加载Object类信息,再加载Grandpa类,再加载父类Father,再加载Son类信息
2、在堆中分配一个地址空间 给 对象son
3、地址空间中 有哪些属性
4、将内存空间地址 传给 对象son
属性的访问规则
public class ExtendsTheory {
public static void main(String[] args) {
Son son = new Son();//内存的布局
//看看会输出 哪个 name,
//按照查找规则返回数据
//1)首先看子类是否有该属性,
//2)若子类有这个属性,并且可以访问,则返回信息
//3)若子类没有这个属性,就看父类有没有这个属性(若父类有这个属性,并且可以访问,则返回信息)
//4)若父类没有这个属性,按照3)的规则,继续向上找
System.out.println(son.name);//输出 子类的name属性 大头儿子
System.out.println(son.age);//输出 父类的age属性 39
System.out.println(son.hobby);//输出 爷爷类的 hobby属性 旅游
}
}
思考若将 Father类中的 age属性改为private,则在对象son的存储空间中,是否还有age这个属性?
private int age = 39;
答案是 仍然有 ,只需让 Father 类提供一个公有的访问方法 就能用
思考 若在 Grandpa类中 添加 public age属性,然后将Father类中的 age属性 改为private,直接访问age时是否会跳过 Father类的private age属性,访问Grandpa类中 的 public age属性?
答案是 不会, 只要在查找过程中 ,被挡住了 不会跳过去 向上找
七、课堂练习
注意:有this 就没有super
输出答案
a
b name
b
输出
我是A类
name+"我是B类的有参构造"
”我是C类的有参构造“
”我是C类的无参构造“
package com.lmdedu.extend_;
//测试类
class Test{
//主方法
public static void main(String[] args) {
Pc pc = new Pc("i9","16G","1T","惠普");
System.out.println(pc.getPcDetail());
NotePad notePad = new NotePad("i9","16G","1T","black");
System.out.println(notePad.getNotePadDetail());
}
}
//父类Computer
class Computer{
private String cpu;
private String memory;
private String hardDisk;
public Computer(String cpu, String memory, String hardDisk) {
this.cpu = cpu;
this.memory = memory;
this.hardDisk = hardDisk;
}
public String getCpu() {
return cpu;
}
public void setCpu(String cpu) {
this.cpu = cpu;
}
public String getMemory() {
return memory;
}
public void setMemory(String memory) {
this.memory = memory;
}
public String getHardDisk() {
return hardDisk;
}
public void setHardDisk(String hardDisk) {
this.hardDisk = hardDisk;
}
public String getDetail(){
return "cpu为" + cpu +"内存为" + memory + "硬盘为" + hardDisk;
}
}
//子类Pc
class Pc extends Computer{
String brand;
//这里也体现出:继承设计的基本思想
//父类的构造器完成 父类属性初始化
//子类的构造器完成 子类属性初始化
public Pc(String cpu, String memory, String hardDisk, String brand) {
super(cpu, memory, hardDisk);
this.brand = brand;
}
public String getPcDetail(){
return getDetail() + "品牌为" + brand;
}
}
class NotePad extends Computer{
String color;
public NotePad(String cpu, String memory, String hardDisk, String color) {
super(cpu, memory, hardDisk);
this.color = color;
}
//打印信息
public String getNotePadDetail(){
return getDetail() + "颜色为" + color;
}
}