黑马程序员全套Java教程_Java基础教程_继承(十七)

  • 1.1 继承概述
  • 1.2 继承的好处和弊端
  • 1.3 继承中变量的访问特点
  • 1.4 super
  • 1.5 继承中构造方法的访问特点
  • 1.6 继承中成员方法的访问特点
  • 1.7 super的内存图
  • 1.8 方法重写
  • 1.9 方法重写的注意事项
  • 1.10 继承的注意事项
  • 案例一:老师和学生
  • 案例二:猫和狗


1.1 继承概述



黑马java基础视频教程下载 黑马java课件_java


在这两个类中,有一部分内容是相同的,包括相同的成员变量和相同的方法,这部分相同的内容我们统称为相同的特征,假如我们把这些相同的特征提取出来,并且也用一个类表示,变成下面这样子。

黑马java基础视频教程下载 黑马java课件_开发语言_02


这个新的类还没有名字,里面有两个成员变量和对应的get/set方法。而另一边,学生类和老师类的效果已经和以前不一样了。有没有一种机制让学生类和老师类拥有这个不知道名字的类的内容,使他们之间产生联系呢?这种机制就是继承。

  • 继承是面向对象三大特征之一,可以使得子类具有父类的属性和方法,还可以在子类中重新定义,追加属性和方法。
  • 继承的格式:
    (1)格式:public class 子类名extends 父类名{}
    (2)范例:public class Zi extends Fu{}
    Fu即父类(基类、超类);Zi即子类(派生类)。
  • 继承中子类的特点:
    (1)子类可以有父类的内容。
    (2)子类还可以有自己特有的内容。
  • Demo.java
public class Demo {
    public static void main(String[] args) {
        Fu fu = new Fu();
        fu.show();

        Zi zi = new Zi();
        zi.method();
        zi.show();
    }
}

Fu.java

public class Fu {
    public void show(){
        System.out.println("show方法被调用");
    }
}

Zi.java

public class Zi extends Fu{
    public void method(){
        System.out.println("method方法被调用");
    }
}

运行结果

黑马java基础视频教程下载 黑马java课件_黑马java基础视频教程下载_03

1.2 继承的好处和弊端

  • 继承的好处:
    (1)提高了代码的复用性(多个类相同的成员可以放到同一个类中)
    (2)提高了代码的维护性(如果方法的代码需要修改,修改一处即可)
  • 继承的弊端:继承让类与类之间产生了关系,类的耦合性增加了,当父类发生变化时子类也不得不跟着变化,削弱了子类的独立性。
  • 什么时候使用继承:
    (1)继承体现的关系:is a,……是……的一种。
    (2)假设法:我们有两个类A和B,如果他们满足A是B的一种,或者B是A的一种,说明他们存在继承关系,这个时候就可以考虑使用继承来体现,否则就不能滥用继承。
    (3)举例:苹果和水果,猫和动物可以。而猫和狗就不可以。
  • 面向对象的四个特征之二——继承
    (1)是从已有类得到继承信息(包括已有类相同的属性和相同的方法)创建新类的过程。提供继承信息的类被称为父类(超类、基类);得到继承信息的类被称为子类(派生类)。
    (2)继承的作用:让变化中的软件系统有了一定的延续性,同时继承也是封装程序中可变因素的重要手段(如果不能理解请阅读阎宏博士的《Java与模式》或《设计模式精解》中关于桥梁模式的部分)。

1.3 继承中变量的访问特点

  • 在子类方法中访问一个变量,其查找变量时的查找顺序:
    (1)子类局部(方法内部)范围找;
    (2)子类成员变量范围找;
    (3)父类成员变量范围找;
    (4)如果都没有就报错(不考虑父亲的父亲…)。

Demo.java

public class Demo {
    public static void main(String[] args) {
        Zi zi = new Zi();
        zi.show();
    }
}

Fu.java

public class Fu {
    //年龄
    public int age = 40;
}

Zi.java

public class Zi extends Fu {
    //身高
    public int height = 175;
    //年龄
    public int age = 20;

    public void show(){
        int age = 30;
        System.out.println(age);
        System.out.println(height);
    }
}

输出结果:30,如果没有int age = 30则输出结果为20。

1.4 super

  • super关键字的用法和this关键字的用法相似:
    (1)this:代表本类对象的引用(this关键字指向调用该方法的对象,一般我们是在当前类中使用
    this关键字,所以我们常说this代表本类对象的引用);
    (2)super:代表父类存储空间的标识(可以理解为父类对象引用)。

关键字

访问成员变量

访问构造方法

访问成员方法

this

this.成员变量,访问本类成员变量

this(…),访问本类构造方法

this.成员方法(…),访问本类成员方法

super

super.成员变量,访问父类成员变量

super(…),访问父类构造方法

super.成员方法(…),访问父类成员方法

public class Zi extends Fu {
    //身高
    public int height = 175;
    //年龄
    public int age = 20;

    public void show(){
        int age = 30;
        System.out.println(age);
        System.out.println(height);
        //如果要访问本类的成员变量age,怎么办呢?
        System.out.println(this.age);
        //如果要访问父类的成员变量age,怎么办呢?
        System.out.println(super.age);
    }
}

1.5 继承中构造方法的访问特点


public class Demo {
    public static void main(String[] args) {
        //创建对象
        Zi zi = new Zi();
    }
}
public class Fu {
    public Fu(){
        System.out.println("Fu中无参构造方法被调用");
    }

    public Fu(int age){
        System.out.println("Fu中带参构造方法被调用");
    }
}
public class Zi extends Fu {
    public Zi(){
        System.out.println("Zi中无参构造方法被调用");
    }

    public Zi(int age){
        System.out.println("Zi中带参构造方法被调用");
    }
}

运行结果一:父类和子类的无参构造方法都被调用了

黑马java基础视频教程下载 黑马java课件_后端_04

public class Demo {
    public static void main(String[] args) {
        //创建对象
        Zi zi = new Zi();
        Zi zi2 = new Zi(20);
    }
}

运行结果二:父类中的无参构造方法被调用了一次,子类中的有参、无参构造方法都被调用了一次

黑马java基础视频教程下载 黑马java课件_黑马java基础视频教程下载_05


两个运行结果引出两个问题:(1)创建子类对象的时候为什么会访问父类的方法?(2)为什么访问的都是父类的无参构造方法?

  • 子类中所有的构造方法默认都会访问父类中无参的构造方法,原因:
    (1)子类会继承父类中的数据,可能还会使用父类的数据。所以,子类初始化之前,一定要先完成父类数据的初始化,而访问父类的构造方法,就是为了完成父类数据的初始化。——解决第一个问题
    (2)每一个子类构造方法的第一条语句默认都是:super(),这是自带的不管我们自己有没有写。——解决第二个问题
  • 如果父类中只有带参构造方法而没有无参构造方法,子类的构造方法第一条语句super()无法执行,就会报错。在不手写无参构造方法的情况下,怎么解决?
    解决方法:直接手写super的带参构造方法。
public class Zi extends Fu {
    public Zi(){
        super(20);
        System.out.println("Zi中无参构造方法被调用");
    }

    public Zi(int age){
        super(20);
        System.out.println("Zi中带参构造方法被调用");
    }
}

1.6 继承中成员方法的访问特点


public class Demo {
    public static void main(String[] args) {
        Zi zi = new Zi();
        zi.method();
        zi.show();
    }
}
public class Fu {
    public void show(){
        System.out.println("Fu中show()方法被调用");
    }
}
public class Zi extends Fu {
   public void method(){
       System.out.println("Zi中的method()方法被调用");
   }

    public void show(){
       super.show();
       System.out.println("Zi中show()方法被调用");
    }
}

运行结果:

黑马java基础视频教程下载 黑马java课件_开发语言_06

  • 通过子类对象访问一个方法,其查找方法时的查找顺序:
    (1)子类成员方法范围找;
    (2)父类成员方法范围找;
    (3)如果都没有就报错(不考虑父亲的父亲…)

1.7 super的内存图


public class Demo {
    public static void main(String[] args) {
        Zi zi = new Zi();
        zi.show();
        zi.method();
    }
}

首先程序从main方法开始执行,main方法加载到栈内存。
接着执行main方法里面的第一句:Zi z = new Zi():左边的动作“Zi z”会加载到main方法里面。
而右边的“new Zi()”,首先会在堆内存申请一个空间,并进行子类数据的初始化,得age的值为20。空间的起始地址001最终赋值给main方法中的变量zi。

public class Zi extends Fu{
	public int age = 20;

	public Zi(){
		System.out.println(age);
	}

	public void show(){
		int age = 30;
		System.out.println(age);
		System.out.println(this.age);
		System.out.println(super.age);
	}
}

黑马java基础视频教程下载 黑马java课件_开发语言_07


接下来我们调用Zi类的构造方法Zi(),构造方法就会加载到栈内存。

黑马java基础视频教程下载 黑马java课件_黑马java基础视频教程下载_08


进入到构造方法内,由于子类中所有的构造方法默认都会访问父类中无参的构造方法(每一个子类构造方法的第一条语句默认都是super()),所以我们看到Fu类里面。

调用Fu的无参构造方法前,要先进行Fu类内容的初始化,在堆内存申请一个super空间用于存储父类初始化的数据,空间内age的值为40。

public class Fu{
	public int age = 40;

	public Fu(){
		System.out.println("Fu类无构造方法被调用");
	}

	public void method(){
		System.out.println("Fu类method方法被调用");
	}
}

黑马java基础视频教程下载 黑马java课件_黑马java基础视频教程下载_09


接着我们调用Fu类的构造方法,构造方法加载到栈内存。在控制台输出“Fu类无参构造方法被调用”。Fu类无参构造方法执行完毕后从栈内存消失。

黑马java基础视频教程下载 黑马java课件_java_10


接着我们回到Zi类里面,Zi的构造方法访问完父类的无参构造方法之后接着往下执行,在控制台输出“Zi类无参构造方法被调用”。Zi类无参构造方法执行完毕后栈内存消失。

黑马java基础视频教程下载 黑马java课件_java_11


接着执行main方法的第二个语句:zi.show()。我们看到Zi类里面的show方法。show()方法加载到栈内存,

此时shou()的调用者的地址是001,则this的地址也是001。在show()方法里面定义了一个age=30,这个age就会在栈内存的show()方法中存在。此时直接输出show方法的值,会输出30;通过this访问age的值,会访问地址为001的age的值20;通过super方法age的值,首先会找到调用者,通过调用者找到super,通过super访问下面age的值得40。

黑马java基础视频教程下载 黑马java课件_黑马java基础视频教程下载_12


show方法执行完毕,从栈内存消失。

黑马java基础视频教程下载 黑马java课件_构造方法_13


接下来回到主函数,执行第三个语句:zi.method(),首先我们发现Zi类中并没有method方法,所以我们其继承的Fu类中去找,将父类的method方法加载到栈内存。在控制台输出“Fu类method方法被调用”。method方法从栈内存消失。main方法执行完毕,从栈内存中消失。

黑马java基础视频教程下载 黑马java课件_后端_14

1.8 方法重写

  • 方法重写:子类中出现了和父类中一模一样的方法声明。
  • 方法重写的应用:当子类需要父类的功能,而功能主体子类有自己特有内容时,可以重写父类中的方法,这样,即沿袭了父类的功能,又定义了子类特有的内容。
  • 练习:手机类和新手机类
    Phone.java
public class Phone {
    public void call(String name){
        System.out.println("给" + name + "打电话");
    }
}

NewPhone.java

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

PhoneDemo.java

public class PhoneDemo {
    public static void main(String[] args) {
        Phone phone = new Phone();
        phone.call("林青霞");
        NewPhone newPhone = new NewPhone();
        newPhone.call("林青霞");
    }
}
  • @Override是一个注解(后面会学),可以帮助我们检查重写方法声明的正确性。

1.9 方法重写的注意事项

  • 私有方法不能被重写(父类私有成员子类是不能继承的)
  • 子类方法访问权限不能更低(public>默认>私有)

1.10 继承的注意事项

  • Java中类只支持单继承,不支持多继承,如下列写法会报错:
public class Son extends Father, Mother{
}
  • Java中类支持多层继承,如下列写法为正确的:
    Granddad.java
public class Granddad(){
	public void drink(){
		System.out.println("爷爷爱喝酒");
	}
}
public class Father extends Granddad(){
	public void somke(){
		System.out.println("爸爸爱抽烟");
	}
}
public class Son extends Father(){
}

案例一:老师和学生

  • 需求:定义老师类和学生类,然后写代码测试;最后找到老师类和学生类当中的共性内容,抽取出一个父类,用继承的方式改写代码,并进行测试。
  • 实现思路:
    (1)定义老师类(姓名,年龄,教书())
    (2)定义学生类(姓名,年龄,学习())
    (3)定义测试类,写代码测试
    (4)共性抽取父类,定义人类(姓名,年龄)
    (5)定义老师类,继承人类,并给出自己特有的方法:教书()
    (6)定义学生类,继承人类,并给出自己特有的方法:学习()
    (7)定义测试类,写代码测试
  • 前三步略
    Person.java
public class Person {
    private String name;
    private int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    .......

Teacher.java

public class Teacher extends Person {

    public Teacher() {
    }

    public Teacher(String name, int age) {
        super(name, age);
    }

    public void teach(){
        System.out.println("开始上课");
    }
}

Student.java

public class Student extends Person {
    public Student() {
    }

    public Student(String name, int age) {
        super(name, age);
    }

    public void study(){
        System.out.println("开始学习");
    }
}

Demo.java

public class Demo {
    public static void main(String[] args) {
        Teacher teacher1 = new Teacher();
        teacher1.setName("林青霞");
        teacher1.setAge(30);
        System.out.println(teacher1.getName() + "," + teacher1.getAge());
        teacher1.teach();

        Teacher teacher2 = new Teacher("风清扬", 33);
        System.out.println(teacher2.getName() + "," + teacher2.getAge());
        teacher2.teach();
    }
}

值得注意的是,若要使用有参数的构造方法,我们要在Teacher类和Student类里面另写。

案例二:猫和狗

  • 需求:请采用继承的思想实现猫和狗的案例,并在测试类中进行测试
  • 分析:
    (1)猫
    成员变量:姓名,年龄
    构造方法:无参,带参
    成员方法:get/set方法,抓老鼠()
    (2)狗
    成员变量:姓名,年龄
    构造方法:无参,带参
    成员方法:get/set方法,看门()
    (3)共性
    成员变量:姓名,年龄
    构造方法:无参,带参
    成员方法:get/set方法
  • 实现:
    (1)定义动物类(Animal)
    成员变量:姓名,年龄
    构造方法:无参,带参
    成员方法:get/set方法
public class Animal {
    private String name;
    private int age;

    public Animal() {
    }

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

(2)定义猫类,继承动物类
构造方法:无参,带参
成员方法:抓老鼠()

public class Cat extends Animal{

    public Cat() {
    }

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

    public void seize(){
        System.out.println(this.getName() + "找Jerry玩");
    }
}

(3)定义狗类,继承动物类
构造方法:无参,带参
成员方法:看门()

public class Dog extends Animal{
    public Dog() {
    }

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

    public void guard(){
        System.out.println("EDG NB!");
    }
}

(4)定义测试类(AnimalDemo),写代码测试

public class AnimalTest {
    public static void main(String[] args) {
        Cat tom = new Cat("Tom", 2);
        tom.seize();

        Cat catKing = new Cat();
        catKing.setName("猫皇");
        catKing.setAge(25);
        catKing.seize();
    }
}