Java之继承

1. 继承

1.1 为什么要继承

在Java中我们定义猫类和狗类,如下

在这里插入图片描述

public class Cat {
    public String name;
    public int age;
    public String color;
    
    public void eat(){
        System.out.println(name + "正在吃饭");
    }
    
    public void sleep(){
        System.out.println(name + "正在睡觉");
    }
    
    public void bark(){
        System.out.println(name + "在喵喵叫");
    }
}

在这里插入图片描述

public class Dog {
    public String name;
    public int age;
    public String color;

    public void eat(){
        System.out.println(name + "正在吃饭");
    }

    public void sleep(){
        System.out.println(name + "正在睡觉");
    }

    public void bark(){
        System.out.println(name + "在汪汪叫");
    }
}

在这里插入图片描述
根据图片我们可以发现猫类和狗类有大量重复的代码,我框住的就是重复的地方。
在Java中,于是就提出了继承这个概念,用来抽取共性。

1.2 继承的概念

在Java中,继承是面向对象编程的重要特性之一,它允许一个类(子类)继承另一个类(超类)的属性和方法。通过继承,子类可以重用超类的代码,从而提高代码的可重用性和可维护性,即实现共性的抽取,代码复用

上述代码中,猫和狗都是动物,我们可以定义一个动物类将共性抽取,再让猫类和狗类都继承这个动物类实现代码复用

在这里插入图片描述

1.3 如何继承

继承需要用到 extends关键字

修饰符 class 子类 extends 父类 {
   // ...  
}

将上述代码用继承实现

public class Animal {
    public String name;
    public int age;
    public String color;

    public void eat(){
        System.out.println(name + "正在吃饭");
    }

    public void sleep(){
        System.out.println(name + "正在睡觉");
    }
}

public class Cat extends Animal {

    public void bark(){
        System.out.println(name + "在喵喵叫");
    }
}

public class Dog extends Animal{

    public void bark(){
        System.out.println(name + "在汪汪叫");
    }
}

子类继承父类之后,必须要新添加自己特有的成员,体现出与基类的不同,否则就没有必要继承了

1.4 父类成员的访问

那么子类怎么访问父类的成员变量和方法呢?

1.4.1 子类怎么访问父类的成员变量

子类访问父类的成员变量也分为两种情况,分别为不同名的成员变量如何访问、同名的成员变量如何访问。

1.4.1.1 子类和父类的成员变量不同名
public class Test1 {
    public int a = 1;
    public int b = 2;
}

public class Test2 extends Test1{
    public int c = 3;

    public void test(){
        System.out.println(a);//从父类中继承下来的a
        System.out.println(b);//从父类中继承下来的b
        System.out.println(c);//自己类的c
    }
}

public class Main {
    public static void main(String[] args) {
        Test2 test2 = new Test2();
        test2.test();
    }
}

//运行结果
1
2
3
1.4.1.2 子类和父类的成员变量同名
public class Test1 {
    public int a = 1;
    public int b = 2;
}

public class Test2 extends Test1{
    public int b = 5;
    public int c = 3;

    public void test(){
        System.out.println(a);//从父类继承下来的a
        System.out.println(b);//是访问父类继承下来的b还是自己类的b呢
        System.out.println(c);//自己类的c
    }
}

public class Main {
    public static void main(String[] args) {
        Test2 test2 = new Test2();
        test2.test();
    }
}

//执行结果
1
5
3

根据执行结果来看,访问的是自己类的b

如果我们将Test2的代码改动,如下

public class Test2 extends Test1{
    public int b = 5;
    public int c = 3;

    public void test(){
        System.out.println(a);
        System.out.println(b);
        System.out.println(c);
        System.out.println(d);
    }
}

在这里插入图片描述
访问父类和子类都没有的成员变量编译器会报错。

结论

  1. 如果访问的成员变量子类中有时,就访问子类自己的成员变量。
  2. 如果访问的成员变量子类中没有时,就访问父类中的成员变量,如果父类中也不存在该成员变量,编译器报错。
  3. 如果访问的成员变量子类中和父类中同名时,优先访问子类的

优先访问子类自己的,如果没有再去访问父类的。

1.4.2 子类怎么访问父类的成员方法

同上,分为不同名的成员方法如何访问、同名的成员方法如何访问。

1.4.2.1 子类和父类的成员方法不同名
public class Test1 {
    public void f1(){
        System.out.println("Test1的方法f1");
    }
}

public class Test2 extends Test1{
    public void f2(){
        System.out.println("Test2方法中的f2");
    }

    public void test(){
        f1();//访问父类中的该方法
        f2();//访问子类中该方法
    }
}

public class Main {
    public static void main(String[] args) {
        Test2 test2 = new Test2();
        test2.test();
    }
}

//运行结果
Test1的方法f1
Test2方法中的f2

1.4.2.2 子类和父类的成员方法同名
public class Test1 {
    public void f1(){
        System.out.println("Test1的方法f1");
    }

    public void f2(){
        System.out.println("Test1的f1方法");
    }
}

public class Test2 extends Test1{
    public void f1(int a){
        System.out.println("Test2的方法f1");
    }
    public void f2(){
        System.out.println("Test2的方法f2");
    }

    public void test(){
        f1();//没有传参,访问父类中的该方法
        f1(1);//传参,访问自己类中的该方法
        f2();//访问子类中的该方法
    }
}

public class Main {
    public static void main(String[] args) {
        Test2 test2 = new Test2();
        test2.test();
    }
}

//执行结果
Test1的方法f1
Test2的方法f1
Test2的方法f2

结论

  1. 如果访问的成员方法不同名时,优先在子类中找,找到则访问,找不到在父类中找;如果父类和子类都没有时,则编译器报错。
  2. 如果访问的成员方法同名时,优先访问自己的;如果父类和子类同名方法的参数列表不同(重载),根据调用
    方法适传递的参数选择合适的方法访问。

1.5 super关键字

如上述代码中,出现父类和子类的成员变量或成员方法同名的情况,但是想在子类中访问父类,于是Java提供了super关键字,即在子类中访问父类的成员变量或成员方法

如下:

public class Test1 {
    int a = 1;
    int b = 2;
    public void f1(){
        System.out.println("Test1的方法f1");
    }
    public void f2(){
        System.out.println("Test1的f1方法");
    }
}

public class Test2 extends Test1{
    int a = 3;
    int b = 4;
    public void f1(){
        System.out.println("Test2的方法f1");
    }
    public void f2(){
        System.out.println("Test2的方法f2");
    }
    public void test(){
        System.out.println(this.a);
        System.out.println(super.a);
        System.out.println(this.b);
        System.out.println(super.b);
        f1();
        super.f1();
    }
}

public class Main {
    public static void main(String[] args) {
        Test2 test2 = new Test2();
        test2.test();
    }
}

//执行结果
3
1
4
2
Test2的方法f1
Test1的方法f1

注意
super关键字与this关键字一样,都不能在静态方法中使用。
在这里插入图片描述

1.6 子类的构造方法

在创建子类的对象时,会调用子类的构造方法,在调用子类的构造方法时,会先调用父类的构造方法,然后再调用子类的构造方法

public class Animal {
    public String name;
    public int age;
    public String color;

    public Animal(){
        System.out.println("Animal");
    }

    public void sleep(){
        System.out.println(name + "在睡觉");
    }

    public void eat(){
        System.out.println(name + "在吃饭");
    }
}

public class Cat extends Animal{
    public Cat(){
        super();
        System.out.println("Cat");
    }
    public void bark(){
        System.out.println(name + "喵喵叫");
    }
}

public class Main {
    public static void main(String[] args) {
        Cat cat = new Cat();
    }
}

在之前的代码中,我们并没有在子类中实现构造方法,并没有报错,如果我们在父类中实现有参数的构造方法呢?

当我们在父类中实现带参数的构造方法

public Animal(String name,int age,String color){
        this.name = name;
        this.age = age;
        this.color = color;
        System.out.println("Animal");
    }

在这里插入图片描述
编译器会报错,那为什么在父类中实现有参数的构造方法就报错呢?

我们知道,当我们没有给父类实现构造方法时,Java会自动提供一个无参数的构造方法,而子类也提供了一个无参数的构造方法,并且该构造方法第一行默认有隐含的super调用,所以编译器不会报错;一旦我们在父类中自己实现了一个构造方法,Java便不会提供,上述父类的代码中实现了带参数的构造方法,而子类中Java提供的构造方法没有参数,所以参数列表长度不同,编译器报错。

所以应该向子类的代码中提供一个带参数的构造方法。

完整代码如下:

public class Animal {
    public String name;
    public int age;
    public String color;

    public Animal(String name,int age,String color){
        this.name = name;
        this.age = age;
        this.color = color;
        System.out.println("Animal");
    }

    public void sleep(){
        System.out.println(name + "在睡觉");
    }

    public void eat(){
        System.out.println(name + "在吃饭");
    }
}


public class Cat extends Animal{
    public Cat(String name, int age, String color) {
        super(name, age, color);
        System.out.println("Cat");
    }

    public void bark(){
        System.out.println(name + "喵喵叫");
    }
}

public class Main {
    public static void main(String[] args) {
        Test2 test2 = new Test2();
        test2.test();
    }
}

注意事项

  1. 如果父类中没有定义构造方法或者定义了无参数的构造方法,在子类的构造方法中第一行默认有隐含的super( )来调用父类的构造方法
  2. 如果父类中的构造方法是带参数的,子类的构造方法也要是带出参数的,并使用super( )选择合适的父类构造方法调用。
  3. 在子类的构造方法中,super( )必须在第一行
  4. 在子类的构造方法中,super( )只能出现一次,并且不能和 this( )同时出现

1.7 super和this

superthis的相同点和不同点

相同点

  1. 都可以访问成员变量和成员方法
  2. 都不能在静态方法中使用
  3. 在构造方法中都必须在第一行,但是不能同时出现

不同点
4. this是当前对象的引用,当前对象即调用实例方法的对象,super是子类对象中从父类继承下来部分成员的引用。
5. 在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性
6. 在构造方法中:this(…)用于调用本类构造方法,super(…)用于调用父类构造方法,两种调用不能同时在构造方法中出现
7. 构造方法中一定会存在super(…)的调用,用户没有写编译器也会增加,但是this(…)用户不写则没有

1.8 再谈初始化

还记得在之前的文章中我们讲过的代码块吗,我们知道了静态代码快和实例化代码块以及构造方法的执行顺序,现在我们基于继承的条件下再试一下

代码如下:

public class Animal {
    public String name;
    public int age;
    public String color;

    {
        System.out.println("Animal的实例化代码块");
    }
    static {
        System.out.println("Animal的静态代码快");
    }
    public Animal(String name,int age,String color){
        this.name = name;
        this.age = age;
        this.color = color;
        System.out.println("Animal的构造方法");
    }
}

public class Cat extends Animal{
    {
        System.out.println("Cat的实例化代码块");
    }
    static {
        System.out.println("Cat的静态代码快");
    }
    public Cat(String name, int age, String color) {
        super(name,age,color);
        System.out.println("Cat的构造方法");
    }
}

public class Main {
    public static void main(String[] args) {
        Cat cat1 = new Cat("mimi1",1,"black");
        Cat cat2 = new Cat("mimi2",2,"white");
    }
}

//执行结果
Animal的静态代码快
Cat的静态代码快
Animal的实例化代码块
Animal的构造方法
Cat的实例化代码块
Cat的构造方法
Animal的实例化代码块
Animal的构造方法
Cat的实例化代码块
Cat的构造方法

结论

  1. 先执行父类的静态代码块,然后再执行子类的静态代码块。
  2. 静态代码快执行完后,再执行父类的实例化代码块,紧接着执行父类的构造方法。
  3. 父类的执行完后,执行子类的实例化代码块,紧接着执行子类的构造方法。
  4. 静态代码快只执行一次。第二次实例化子列对象,父类和子类的静态代码块都不会再执行。

1.9 继承的方式

在Java中,分为三种继承,分别为:

在这里插入图片描述

public class A{

}

public class B extends A{

}

在这里插入图片描述

public class A{

}

public class B extends A{

}

public class C extends B{

} 

在这里插入图片描述

public class A{

}

public class B extends A{

}

public class C extends A{

}

1.10 final 关键字

final 可以修饰变量、成员方法、类。

  1. final 修饰变量
final int a = 1;
a = 2;

//编译错误
//java: 无法为最终变量a分配值

被 final 修饰的变量不可以被修改

  1. final 修饰类

被 final 修饰的类无法被继承

当我们用 final 来修饰 Animal
在这里插入图片描述
运行后的编译报错:java: 无法从最终com.company.Animal进行继承

  1. 被 final 修饰的方法不能被重写
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值