Java继承

2021/11/13 20:40:18

本文主要是介绍Java继承,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

继承

      • 1 继承的含义
      • 2 继承的特点
      • 3 继承中成员变量的关系
      • 4 继承中构造方法的关系
      • 5 继承中成员方法的关系
      • 6 继承中静态成员的关系
      • 7 this、super的比较和重载、重写的比较
      • 8 练习(针对以上内容)
      • 9 final关键字

1 继承的含义

继承:(界门纲目科属种)
   格式
      class 子类名 extends 父类名 {}
      子类可以有自己的新特性(属性, 行为)
   术语:
      父类, 超类, 基类
      子类, 衍生类

继承的好处:
   1. 提高了代码的复用性
   2. 提高了代码的维护性(分情况而定)
   3. 让类与类之间产生了关系,是多态的前提
      其实这也是继承的一个弊端:类的耦合性增强

设计原则:
   高内聚, 低耦合
   内聚: 就是自己完成某件事情的能力。
   耦合: 就是类与类之间的关系。

    举例:人的职责(分清自己的职责),不要多做事,也不要少做事。

ExtendsDemo1:

class People {
    String name;
    int age;

    public void eat() {
        System.out.println("我要吃小龙虾");
    }

    public void sleep() {
        System.out.println("我要睡觉,不要打扰我");
    }
}

class Student extends People {
    public void study() {
        System.out.println("我爱学习,学习爱我");
    }
}

class Teacher extends People {
    public void teach() {
        System.out.println("我爱学生,学生爱我");
    }
}

public class ExtendsDemo1 {
    public static void main(String[] args) {
        Student s = new Student();
        s.name = "Henson_z";
        s.age = 18;
        System.out.println(s.name + " " + s.age); // Henson_z 18
        s.eat(); // 我要吃小龙虾
        s.sleep(); // 我要睡觉,不要打扰我

        System.out.println("-----------------------");

        Teacher t = new Teacher();
        t.name = "刘亦菲";
        t.age = 16;
        System.out.println(t.name + " " + t.age); // 刘亦菲 16
        t.eat(); // 我要吃小龙虾
        t.sleep(); // 我要睡觉,不要打扰我

        // 以上打印的结果都是从父类继承而来
    }
}

2 继承的特点

java中继承的特点:
    a. 只支持单继承,不支持多继承(C++),即一个类最多只能有一个父类,不能有多个父类;
    b. 但是支持多层继承。

注意事项:
    a. 子类只能继承父类的非私有成员(实际上私有成员被隐式继承了,即子类中不能访问,在后面的反射中会介绍),打破了封装性(继承不能有private,打破封装)。
    b. 子类不能继承父类的构造方法(如果可以,则子类中有public Father(){...},构造方法命名没有和类名Son相同,也没有返回值,所以不是成员方法,所以在语法上就错误),但是可以通过关键字super去访问父类的构造方法。
    c. 不要为了部分功能而去使用继承,比如:
        class A {
            m1();
            m2();
            m3();
            m4();
        }
        现在需要一个类B,具有以下功能:m1(), m2(), m3(), m5(),如果使用继承:
        class B extends A {
            m5();
        }
        但其实这样是不推荐的,因为B中也会继承m4(),这对B来说是多余的,所以此时不能使用继承,但可以使用复合。
     d. 什么时候使用继承呢?
        继承中体现的关系:"is a"的关系
        人:
            学生,老师
        水果:
            香蕉,水果姐(x)

ExtendsDemo2:

class Super1 {
}

class Super2 extends Super1 {
    private int a = 10;
    int b = 20;

    Super2() {
        System.out.println("无参构造方法");
    }

    Super2(int a, int b) {
        this.a = a;
        this.b = b;
    }
}

// 报错,因为java中只能单继承
// class Sub extends Super1, Super2 {
// }

class Sub extends Super2 {
    // Sub继承Super2,Super2继承Super1,支持多层继承。
}

public class ExtendsDemo2 extends Super2 {
    public static void main(String[] args) {
        ExtendsDemo2 demo = new ExtendsDemo2();
        // System.out.println(demo.a); // 报错,私有成员a被隐式继承,子类不能访问。

        // ExtendsDemo2 demo2 = new ExtendsDemo2(1, 2); // 子类不能继承父类的构造方法,所以子类中没有有参构造方法,所以报错。
        System.out.println(demo.b); // 20,非私有成员可以被正常继承。

    }
}

3 继承中成员变量的关系

继承中成员变量的关系:
    私有成员变量被隐式继承,其它成员变量都被正常继承。

子类的成员变量隐藏了父类的成员变量:就近原则
    此时可通过super关键字访问父类的成员变量。
    super关键字是一片内存空间的表示,不能表示父类对象。

ExtendsDemo3:

class Father {
    int a = 10;
    int b = 20;
}

class Son extends Father {
    int a = 100;
    int b = 200;

    public void show() {
        System.out.println(a);
        System.out.println(b);
        System.out.println(super.a);
        System.out.println(super.b);
    }
}

public class ExtendsDemo3 {
    public static void main(String[] args) {
        Son son = new Son();
        son.show(); // 运行结果:100, 200, 10, 20
    }
}

内存图分析:

super关键字

**总结:**this和super均可以表示一片内存空间的标识;this还可以表示当前对象,但是super不可以表示父类对象。

4 继承中构造方法的关系

继承中构造方法的关系:
    a.子类不会继承父类的构造方法;
    b.子类中所有的构造方法默认都会访问父类的无参构造方法。

为什么子类的构造方法要访问父类的构造方法?
    父类中定义的成员变量,子类会继承,因此需要调用父类的构造方法,对这些数据进行初始化。

如果父类中没有无参构造方法呢?
    1.为父类添加一个无参构造方法;
    2.子类显示调用父类的有参构造方法,通过super关键字;
    3.子类通过this关键字调用本类中定义的其它构造方法,但是其它构造方法最终也会调用父类构造方法。
        this(); // 调用本类的构造方法
        super(); // 调用父类的构造方法

注意事项:
    a.创建子类对象时,必须要先访问父类的构造方法(有参、无参均可),默认访问的是父类无参构造方法,如果访问不到,则报错。
    b.this(), super()调用构造方法只能放在构造器的第一行,所以this()和super()不能同时出现。

ExtendsDemo4:

class Father3 {
    // 1.为父类添加一个无参构造方法
    /*Father3() {
        System.out.println("Father");
    }*/

    Father3(int a) {
        System.out.println("Father: " + a);
    }
}

class Son3 extends Father3 {
    Son3() {
        // 3.通过this关键字调用本类中定义的其它构造方法,但是其它构造方法最终也会调用父类构造方法。
        this("深蓝");
        System.out.println("Son");
    }

    Son3(String name) {
        // 2.通过super关键字手动调用父类构造方法
        super(100);
        System.out.println("Son: " + name);
    }
}

public class ExtendsDemo4 {
    public static void main(String[] args) {
        // 默认会访问父类的无参构造方法
        new Son3(); // 运行结果:Father, Son
        new Son3("Heson_z"); // Father, Son: Heson_z

        // 如果把父类无参构造方法注释,则静态编译会出错,因为父类没有提供默认的无参构造方法,子类的构造方法也没有手动调用父类的构造方法。
        // 添加this()和super()之后,上面运行的结果:
        // Father: 100, Son: 深蓝, Son // new Son3()运行结果
        // Father: 100, Son: Heson_z  // new Son3("Heson_z")运行结果
    }
}

5 继承中成员方法的关系

继承中成员方法的关系:
    当子类中定义了一个方法签名一模一样的方法时,就会隐藏父类中定义的方法。
	通过super.method() 去访问父类中定义的方法

方法的重载:overload
   同一个类中, 方法名字一样, 参数列表不一样(方法签名不同), 和返回值类型无关。

方法的重写:override
   在子父类中, 方法签名一样,
   返回值类型:
      基本数据类型必须一致
      引用数据类型:父类的类型必须兼容子类的类型(父类的类型必须大于等于子类的类型)
   访问权限:
      子类的方法的访问权限必须大于等于父类方法的访问权限

注意事项:
   a. 父类中的私有方法不能被重写,因为方法重写的前提是继承,私有方法被隐式继承(或者说不能被继承),所以不能被重写。
   b. 静态方法按照重写的形式,但不能算作方法重写,且不能添加@Override注解,在多态中会讲解。

引入一个注解:@Override
   @Override: 能够判断一个方法是不是重写父类中定义的方法。
   提醒注意: 这是在重写父类中定义的方法。

方法重写有什么意义?
   父类中提供方法, 相当于默认行为。父类的默认行为不能满足子类的需求,就要进行方法的重写。

ExtendsDemo5:

class Father4 {
    Father4 show() {
        System.out.println("Father.show()");
        return new Father4();
    }

    private void show1() {
        System.out.println("Father.show1()");
    }

    public static void show2() {
        System.out.println("Father.show2()");
    }
}

class Son4 extends Father4 {
    // 子类中自己定义的方法
    public void method() {
        System.out.println("Son.method()");
        show(); // 访问自己的show()方法
        super.show(); // 通过super访问父类的show()方法
    }

    @Override // 对父类方法的重写,返回值为引用数据类型需要被父类兼容(返回值为基本数据类型,需要和父类一致),访问权限大于等于父类。
    public Son4 show() {
        System.out.println("Son.show()");
        return new Son4();
    }

    // @Override // 私有方法不能被重写,此时子类中的show1()方法相当于子类中自己定义的方法,不是继承的,所以不是重写。
    void show1() {
        System.out.println("Son.show1()");
    }

    // @Override // 静态方法即使按照重写的形式,添加@Override时会报错,所以不是方法的重写,在多态中讲解。
    public static void show2() {
        System.out.println("Son.show2()");
    }
}

public class ExtendsDemo5 {
    public static void main(String[] args) {
        Son4 son = new Son4();
        son.method(); // 运行结果:Son.method(), Son.show(), Father.show()
        son.show1(); // Son.show1()
    }
}

6 继承中静态成员的关系

继承中静态成员(静态变量、静态方法)的关系:
    a.静态成员也会被继承。
    b.如果出现同名的静态成员,子类静态成员会隐藏父类静态成员,那么父类中定义的静态成员可以通过类名来访问,也可以通过父类对象来访问。
    c.静态方法中不能使用this, super关键字,因为这两个关键字都是非静态的。
    d.静态方法即使按照重写形式,也不能算作方法的重写,添加@Override会出错。

ExtendsDemo6:

class Father2 {
    static int a = 10;
    static int b = 20;
}

class Son2 extends Father2 {
    static int a = 100;
    static int b = 200;

    public static void show() {
        System.out.println(a);
        System.out.println(b);
        // 静态方法中不能使用this, super关键字
        // System.out.println(super.a); // 报错
        // System.out.println(super.b); // 报错
        System.out.println(Father2.a);
        System.out.println(Father2.b);
    }
}

public class ExtendsDemo6 {
    public static void main(String[] args) {
        Son2.show(); // 运行结果:100, 200, 10, 20
    }
}

ExtendsDemo7:

class Father5 {
    public static Father5 show1() {
        System.out.println("Father.show1()");
        return new Father5();
    }
}

class Son5 extends Father5 {
    // @Override // 静态方法按照重写形式,不是方法的重写
    public static Father5 show1() {
        System.out.println("Son.show1()");
        return new Father5();
    }

    public static void method() {
        show1();
        // super.show1(); // 静态方法中不能使用非静态的super
        Father5.show1();
    }
}

public class ExtendsDemo7 {
    public static void main(String[] args) {
        Son5.method(); // 运行结果:Son.show1(), Father.show1()
    }
}

7 this、super的比较和重载、重写的比较

this和super的用法:
    a.访问成员变量
        this.成员变量   super.成员变量
    b.访问成员方法
        this.成员方法() super.成员方法()
    c.方法构造方法
        this()  super()
this:
    1.代表当前对象 (上面的用法a, b)
    2.代表一片内存空间的标识 (上面的用法c)
super:
    只能代表一片内存空间的标识,不能代表父类对象。(上面的用法a, b, c)


方法重写和重载的区别?方法重载能改变返回值类型吗?
	方法重载:
        同一个类中,方法名字相同,但是形参的类型或个数不同,与返回值无关。
    方法重写:
        在子父类中,方法名字相同,形参也相同(方法签名相同),但是实现不同。
        返回值类型:
            如果是基本数据类型,则必须一样;
            如果是引用数据类型,则父类的返回值类型必须要大于等于(兼容)子类的返回值类型。
        访问权限:
            子类方法的访问权限大于等于父类方法的访问权限。
            
	方法重载可以改变返回值类型。

8 练习(针对以上内容)

练习一:看程序写结果 (Test1)

class Father {
    int a = 10;
}

class Son extends Father {
    int a = 20;

    public void show() {
        int a = 30;
        System.out.println(a); // 30
        System.out.println(this.a); // 20
        System.out.println(super.a); // 10
    }
}

public class Test1 {
    public static void main(String[] args) {
        Son son = new Son();
        son.show(); // 30 20 10
    }
}

练习二:看程序写结果 (Test2)

class Fu {
    static {
        System.out.println("静态代码块Fu");
    }
    // 构造代码块只是一个语法糖, 反编译之后会放到构造方法前面
    {
        System.out.println("构造代码块Fu");
    }

    public Fu() {
        System.out.println("构造方法Fu");
    }
}

class Zi extends Fu {
    static {
        System.out.println("静态代码块Zi");
    }

    {
        System.out.println("构造代码块Zi");
    }

    public Zi() {
        System.out.println("构造方法Zi");
    }
}

public class Test2 {
    public static void main(String[] args) {
        new Zi();
        // 运行结果:
        /*
        静态代码块Fu
        静态代码块Zi
        构造代码块Fu
        构造方法Fu
        构造代码块Zi
        构造方法Zi*/
    }
}
总结:Java中如果有继承关系,对象的初始化是分层初始化
    1.父类按照层次依次加载,然后再加载子类。
    2.成员变量是按照层次进行初始化的,先初始化父类中定义的数据,再初始化子类中定义的数据。

练习三:看程序写结果 (Test3)

class X {
    Y b = new Y();
    X() {
        System.out.print("X");
    }
}

class Y {
    Y() {
        System.out.print("Y");
    }
}

class Z extends X {
    Y y = new Y();
    Z() {
        System.out.print("Z");
    }
}

public class Test3 {
    public static void main(String[] args) {
        new Z(); // YXYZ
    }
}
总结:
   父类的显示初始化 --> 父类的构造方法 --> 子类的显示初始化 --> 子类的构造方法

练习四:(Test4)

手机类和新式手机类。(加入好听的彩铃功能,基本功能由手机提供)
手机:
   属性:品牌, 价格, 颜色...
   行为:打电话, 发短信, 玩游戏...
新式手机:
   属性:品牌, 价格, 颜色...
   行为:打电话(有彩铃), 发短信, 玩游戏...

重写可以加强父类的方法, 也可以重新写一个完全不一样的方法。
class Phone {
    String brand;
    double price;
    String color;

    public void call(String name) {
        System.out.println("给" + name + "打电话");
    }

    public void sendMessage(String name) {
        System.out.println("给" + name + "发短信");
    }

    public void play(String game) {
        System.out.println("玩" + game + "游戏");
    }
}

class NewPhone extends Phone {
    @Override // 重写加强父类的方法
    public void call(String name) {
        System.out.println("彩铃播放中...");
        // System.out.println("给" + name + "打电话");
        super.call(name);
    }
}

public class Test4 {
    public static void main(String[] args) {
        NewPhone phone = new NewPhone();
        phone.call("刘亦菲"); // 彩铃播放中... 给刘亦菲打电话
        phone.sendMessage("Henson_z"); // 给Henson_z发短信
        phone.play("王者荣耀"); // 玩王者荣耀游戏
    }
}

练习五:猫狗案例 (Test5)

现实生活中:
    猫:
        属性:brand, 颜色, 年龄
        行为:叫, 吃, 抓老鼠...
    狗:
        属性:brand, 颜色, 年龄
        行为:叫, 吃, 看门...
    动物:
        属性:brand, 颜色, 年龄
        行为:叫, 吃

代码世界:
    Animal:
        Field: brand, color, age
        Method: speak(), eat()
    Cat extends Animal
        Field:
        Method: catchMouse()
    Dog extends Animal
        Field:
        Method: watchHouse()
class Animal {
    String brand;
    String color;
    int age;

    public Animal() {
    }

    public Animal(String brand, String color, int age) {
        this.brand = brand;
        this.color = color;
        this.age = age;
    }

    public void speak() {
        // 不同的动物叫法不同,这里不提供实现
    }

    public void eat() {
        System.out.println("朕要吃好吃的");
    }

    public void show() {
        System.out.println("brand: " + brand + ", color: " + color + ", age: " + age);
    }
}

class Cat extends Animal {
    public Cat() {
    }

    public Cat(String brand, String color, int age) {
        super(brand, color, age);
    }

    @Override
    public void speak() {
        System.out.println("喵喵喵");
    }

    public void catchMouse() {
        System.out.println("抓老鼠");
    }
}

class Dog extends Animal {
    public Dog() {
    }

    public Dog(String brand, String color, int age) {
        super(brand, color, age);
    }

    @Override
    public void speak() {
        System.out.println("汪汪汪");
    }

    public void watchHouse() {
        System.out.println("看门狗");
    }
}

public class Test5 {
    public static void main(String[] args) {
        Cat cat = new Cat("橘猫", "橘色", 3);
        cat.show(); // brand: 橘猫, color: 橘色, age: 3
        cat.eat(); // 朕要吃好吃的
        cat.speak(); // 喵喵喵
        cat.catchMouse(); // 抓老鼠

        System.out.println("----------------------");

        Dog dog = new Dog("哈士奇", "黑白", 2);
        dog.show(); // brand: 哈士奇, color: 黑白, age: 2
        dog.eat(); // 朕要吃好吃的
        dog.speak(); // 汪汪汪
        dog.watchHouse(); // 看门狗
    }
}

9 final关键字

如果对继承不加以限制,可能会导致一些严重的事故发生。

Demo:

class A {
    public void func() {
        System.out.println("这是绝密文件");
    }
}

class B extends A {
    @Override
    public void func() {
        System.out.println("这是毫无价值的文件"); // 通过继承来重写方法
    }
}

public class Demo {
    public void show(A a) {
        a.func();
    }

    public static void main(String[] args) {
        Demo demo = new Demo();
        B b = new B();
        demo.show(b); // 这是毫无价值的文件 (涉及到多态知识)
    }
}

final关键字:

final: 最终的。
   修饰类:该类就不能被继承 (做了绝育手术)
   修饰方法:该方法不能被重写, (禁止子类重写该方法)
   修饰变量:自定义常量

注意事项:
   a.修饰变量时,该变量有且只能被赋值一次。
   b.对于成员变量来说, 即使常量为默认的0值,也必须得赋值一次。
class AA {
    public void func() {
        System.out.println("这是绝密文件");
    }
}

class BB extends AA {
    @Override
    public void func() {
        System.out.println("这是毫无价值的垃圾");
    }
}

public class FinalDemo1 {
    // final int num; // 错误
    final int num = 0; // b.对于成员变量来说, 即使常量为默认的0值,也必须得赋值一次。上面的写法错误。

    public void show(A a) {
        a.func();
    }

    public static void main(String[] args) {
        final int a = 10;
        System.out.println("a = " + a); // 10
        // a = 20; // a.修饰变量时,该变量有且只能被赋值一次。
        // a = 10; // 同上,即使值不变也报错。

        FinalDemo1 demo = new FinalDemo1();
        System.out.println(demo.num); // 0
        // demo.num = 20; // 同上
    }
}

练习六:final修饰基本数据类型与引用数据类型的区别

final修饰:
    a.基本数据类型:值不能发生改变
    b.引用数据类型:引用地址不能发生改变(只能指向同一个对象)
class Animal {
    int a;
    int b;

    Animal(int a, int b) {
        this.a = a;
        this.b = b;
    }
}

public class Test6 {
    public static void main(String[] args) {
        final int i = 100;
        System.out.println("i = " + i); // i = 100
        // i = 200; // a.基本数据类型:值不能发生改变

        final Animal animal = new Animal(1, 2);
        System.out.println(animal.a + " " + animal.b); // 1 2
        // animal的值始终没有发生变化,所以正确。
        animal.a = 10;
        animal.b = 20;
        System.out.println(animal.a + " " + animal.b); // 10 20

        // animal = new Animal(10, 20); // 出错 b.引用数据类型:引用地址不能发生改变(只能指向同一个对象)
    }
}


这篇关于Java继承的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程