面向对象三大巨头知识点----继承
有一个大黄猫类:
属性有:名字,年龄,颜色
方法有:吃鱼,跳,跑
还有一个大白猫类:
属性有:名字,年龄,颜色
方法有:吃猫罐头,跳,跑
可以看到除了吃的方法不一样,其他的几乎都是一样的,那么如果使用代码写出来,就会重复的工作,代码耦合度较高。两只猫或许浪费的时间不多,但如果是成百上千个这种猫类,就比较繁琐。
因此,为了缓解代码的耦合度,可以使用继承这个特性来解决。
继承可以解决代码复用,让编程更接近人类思维,当多个类存在相同的属性和方法时,可以从这些类中抽象出父类,在父类中定义相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过extends关键字来声明继承符类即可
class 子类名 extends 父类名{
}
继承示意图
开头说的那个案例:我们可以将耦合度高的属性和方法抽象出来,重新定义一个父类Cat类,后面所有的猫类不管是黄猫 白猫 小猫 大猫,都继承Cat类。这样就不用再次定义重复的属性和方法了

C可以再往下继承,属性方法继续叠加。注意不同分支继承下去的,属性不会继承(例如P类不会有C类的属性)
以开头说到的猫类问题,使用继承解决代码复用性高的问题
有一个大黄猫类:
属性有:名字,年龄,颜色
方法有:吃鱼,跳,跑
还有一个大白猫类:
属性有:名字,年龄,颜色
方法有:吃猫罐头,跳,跑
根据以上信息发现可抽象出来的属性有:名字,年龄,颜色 方法有跳,跑
因此可以定义一个父类 Cat类 然后创建大黄猫类和大白猫类继承Cat类
//父类
public class Cat {
String name;
int age;
String color;
public void run(){
System.out.println("正在跑~~");
}
public void jump(){
System.out.println("正在跳~~");
}
}
//子类
public class WhiteCat extends Cat{
public void eat(){
System.out.println("吃罐头");
}
}
//子类2
public class YellowCat extends Cat{
public void eat(){
System.out.println("吃鱼");
}
}
//测试类
public class TestExtends {
public static void main(String[] args) {
WhiteCat xiaobai = new WhiteCat();
xiaobai.name = "大白猫";
xiaobai.age = 4;
xiaobai.color = "白色";
System.out.println("白猫信息如下:"+xiaobai.name+xiaobai.color+xiaobai.age+"岁");
xiaobai.eat();
xiaobai.run();
YellowCat xiaohuang = new YellowCat();
xiaohuang.name = "大黄猫";
xiaohuang.age = 5;
xiaohuang.color = "黄色";
System.out.println("黄猫信息如下:"+xiaobai.name+xiaobai.color+xiaobai.age+"岁");
xiaohuang.eat();
xiaohuang.run();
}
}
输出结果:

可以看到,虽然两只猫类没有定义属性,还是可以输出赋值,这就是因为继承了Cat的属性和方法。同时也定义了自己的方法eat。
public class TestExtends {
public static void main(String[] args) {
WhiteCat xiaobai = new WhiteCat();
YellowCat xiaohuang = new YellowCat();
System.out.println(xiaobai.name);
xiaobai.name = "白色";
System.out.println(xiaobai.name);
System.out.println(xiaohuang.name);
}
}
输出结果

可以看到没有自己修改前,输出的值是父类自己的值。修改后才变成了自己的。没有修改的黄猫的name还是父类的值
因此得出结论:子类继承父类的属性连同赋的值会随着变量一起copy到对象中,需要自己修改。
对于父类的的私有属性,或者是子类没有权限访问的属性。都可以通过跟封装一样的set/get方法来获取和进行修改。
重点要素:子类继承父类时,必须先调用一次父类的构造器(默认调用父类的无参构造器)
//父类
public class Boos {
public Boos(){//父类的无参构造器
System.out.println("父类的无参构造器被调用");
}
}
//子类
public class Ceo extends Boos{
public Ceo(){
System.out.println("子类Ceo的无参构造器被调用");
}
}
//测试类
public class Test02 {
public static void main(String[] args) {
Ceo ceo = new Ceo();
}
}
输出效果

所以为了解决这个问题有两种方法:
案例演示:
//父类
public class Boos {
public String name;
public Boos(String name){//父类的有参构造器
this.name = name;
System.out.println("父类的有参构造器被调用");
}
}
//子类
public class Ceo extends Boos{
public Ceo(){
super("张三");
System.out.println("子类Ceo的无参构造器被调用");
}
}
//测试类
public class Test02 {
public static void main(String[] args) {
Ceo ceo = new Ceo();
}
}
因为子类继承父类时,通过super触发了父类的有参构造器。所以父类的name被赋值了张三,然后再传给子类Ceo所有属性和方法,因此ceo的name也是张三
//父类
public class Boos {
public String name;
public Boos(String name){//父类的有参构造器
this.name = name;
System.out.println("父类的有参构造器被调用");
}
2.子类同时也写出子类的有参构造器用于构造器修改属性
//子类
public class Ceo extends Boss{
public Ceo(String name){
super("张三");
System.out.println("子类此时的name是:"+this.name);
this.name = name;
System.out.println("子类Ceo的有参构造器被调用");
}
}
public class Tesst03 {
public static void main(String[] args) {
Ceo c1 = new Ceo("李四");
System.out.println(c1.name);
}
}
输出结果:

通过输出结果和debug可以看到,通过实例化子类,首先调用的是父类的构造器,将参数穿入父类的构造器,变成张三,然后再调用子类的构造器传入参数变成李四。
通过以上案例测试可以清晰的发现,实例化子类传入的实参( Ceo c1 = new Ceo(“李四”);)是传入子类的构造器的,而父类实际的实参需要在子类的构造器中由super定义。不通用
因为super的这个特性和this关键字在构造器中调用另一条构造器的特性相同,都只能是第一条语句,因此this();和super();只能存在一个,否则会冲突。
那么 可以实验以下super关键字不显写出来,而是让子类默认的去调用父类无参构造器,然后再写this看看会不会冲突。
//父类
public class Boos {
public String name;
public Boos(){//父类的有参构造
System.out.println("父类的无参构造器被调用");
}
}
//子类
public class Ceo extends Boos{
public Ceo(){
this("张三");
System.out.println("子类Ceo的无参构造器被调用");
}
public Ceo(String name){
this.name = name;
System.out.println("子类Ceo的有参构造器被调用");
}
}
//测试类
public class Test02 {
public static void main(String[] args) {
Ceo ceo = new Ceo();
System.out.println(ceo.name);
}
}
输出结果

可以看到没有编译错误,也就是说如果要在子类构造器中使用this调用其他构造器,那么就不能写super指定调用父类构造器,只能让编译器默认调用父类的无参构造器。

现在有三个类 A类 B类 C类
要求让 B类同时有C类A类的属性和方法
根据java中单继承的规定,不可以直接B类继承C类的同时又继承A类,所以我们可以A类继承C类,然后B类继承C类。这样就实现了要求

ps:虽然java不允许多继承,但是可以实现多个接口
例如 Person is a Muisc 人是一个音乐,显然是错误的 Cat is a Animal 猫是一个动物,就满足了is a逻辑 Cat类就可以继承Animal类
当实例化一个子类的时候内存发生了什么?
先看以下代码
Son - extends-Father-extends-GrandPa
//爷爷类
public class GrandPa {
String name = "大头爷爷";
String hobby = "旅游";
}
//父类
public class Father extends GrandPa{
String name = "大头爸爸";
int age = 31;
}
//子类
public class Son extends Father{
String name = "大头儿子";
}
//测试类实例化子类
Son son = new Son();
此时在内存中依次执行了哪些操作:

Son son = new Son();
System.out.println(son.name);
输出结果为 大头儿子。
在继承中,查找每个属性是按照查找关系来返回信息的
步骤会如下:


以下代码会输出什么:
class A{
A(){
System.out.println("a");
}
}
class B extends A{
B(){
this("abc");
System.out.println("b");
}
B(String name){
System.out.println("b name");
}
}
//测试类
B b = new B();
a
b name
b
练习当子类中有this调用其他构造器时super的位置
看下面代码会输出什么
class A{
A(){
System.out.println("A类的无参构造");
}
}
class B extends A{
B(){
System.out.println("B类的无参构造");
}
B(String name){
System.out.println("B类的有参构造");
}
}
class C extends B{
C(){
this("hha");
System.out.println("C类的无参构造器");
}
C(String name){
super("554");
System.out.println("C类的有参构造器");
}
}
//测试类
C c = new C();
执行步骤分析
1.进入c的构造器,此时发现this调用的另一个构造器中有super指向B类的有参构造器。
2.跟着this到C的有参构造器,通过super到B类的有参构造器在执行B类的有参构造之前再跟随默认的super到A类的无参构造器
3.到A类的无参构造器 输出—A类的无参构造
4.再返回到B类的有参构造器 输出 ----B类的有参构造
5.再返回到C类的有参构造器输出—C类的有参构造器
6.this执行完毕返回到C类的无参构造器输出—C类的无参构造器
所以结果为:
A类的无参构造
B类的有参构造
C类的有参构造
C类的无参构造器
总之:实例化子类时,依次从顶级父类开始依次往下执行构造器,当父类重载了构造器时,根据子类构造器中的super参数匹配执行具体的构造器,子类没有传入参数,默认执行无参构造器。
编写Computer类,包含Cpu 内存,硬盘等属性,创建getDetails方法用于输出Computer的详细信息
编写EtPc子类继承Computer类,添加特有属性-品牌brand
编写RcPc子类继承Computer类,添加特有属性-颜色color
编写Test04类在main方法中创建EtPC和RcP对象,分别给对象中特有的属性赋值,且给Computer类继承来的属性赋值,并使用方法打印输出信息。
//Computer类
public class Computer {
String Cpu;
String Ram;
String Disk;
Computer(String Cpu,String Ram,String Disk){
this.Cpu = Cpu;
this.Ram = Ram;
this.Disk = Disk;
}
public String getDetails(){
return "CPU="+Cpu+"内存="+Ram+"硬盘="+Disk;
}
}
//EtPc子类
public class EtPC extends Computer{
private String brand;
EtPC(String Cpu,String Ram,String Disk,String brand){
super(Cpu,Ram,Disk);
setBrand(brand);
}
public String printInfo(){
return getDetails()+"品牌="+brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
}
//RcPc子类
public class RcPc extends Computer{
private String color;
RcPc(String Cpu,String Ram,String Disk,String color){
super(Cpu,Ram,Disk);
setColor(color);
}
public String printInfo(){
return getDetails()+"颜色=" + color;
}
public void setColor(String color) {
this.color = color;
}
}
//测试类
public class Test04 {
public static void main(String[] args) {
EtPC e1 = new EtPC("i9-12600K","32g","1t","联想");
System.out.println(e1.printInfo());
RcPc r1 = new RcPc("i5-12600k","16g","500g","黑色");
System.out.println(r1.printInfo());
}
}