张小飞的Java之路——第九章

写在前面:

视频是什么东西,有看文档精彩吗?

视频是什么东西,有看文档速度快吗?

视频是什么东西,有看文档效率高吗?


诸小亮:“封装说完后,我们接着看面向对象的第二大特征——继承

张小飞:“跟我们生活中继承是一样的?”

诸小亮:“当然,计算机中的一切都是从生活中来的”

1. 介绍

1. 生活中的继承

诸小亮:“首先,我们看下生活中的继承”

张小飞:“这有啥了解的,不就是儿子继承老爹的家产么”

“…,我说的继承不是这个”

“嗯?那你说的是?”

“生活中有:小学生、大学生、小学老师、大学老师这些类别”

“何止啊,还有:男人、女人、好人、坏人这些呢”

“…,不要插嘴,听我说:小学生、大学生 拥有很多共同点,可以统称为:学生”

“嗯嗯,学生是一个更大的分类”

“同样的,小学老师、大学老师 也拥有很多共同点,可以统称为:老师”

“师兄,照你这么说,老师、学生 也有很多共同点,可以统称为:人”

“你说的很对,如果用一张图来表示它们之间的关系:”
image.png
所谓继承:不同分类可以向上抽取共同点,形成一个更大的分类(也叫:父类)

张小飞:“原来这就是继承”

2. Java中的继承

诸小亮:“Java 中的继承也一样,先看两个类:”

class Student{
    int age;
}

class Teacher{
    int age;
}

张小飞:“很明显:这两个类的共同点是:年龄”

诸小亮:“没错,所以可以向上抽取,创建一个新的类:”

class Person{
    int age;
}

张小飞:“那,怎么表示继承关系呢?”

诸小亮:“在 Java 中用 extends 来表示继承关系,比如:”
image.png
“这时,Student称为:子类,Person称为:父类”

张小飞:“原来如此”

2. 继承的特点

张小飞:“但是这个继承有什么用呢?”

诸小亮:“java中,一旦两个类出现继承关系,子类自动继承父类的所有成员

class Person{
    int age;

    void show(){
        System.out.println(this.age);
    }
}

//继承Person后,不需要声明任何属性和方法,继承父类属性和方法
class Student extends Person{
	
}

public class Demo{
    public static void main(String[] args){
        //这时候,s 即是 Student 类型,也是 Person 类型
        Student s = new Student();
        
        //子类对象可以直接使用父类中的属性和方法
        s.age = 18;
        s.show();
    }
}

诸小亮:“你觉得这样有什么好处?”

张小飞:“好处嘛,提高代码的复用性?”

诸小亮:“你说的不错,就是这样”

诸小亮:“但是需要注意:Java中,只有单继承,没有多继承

张小飞:“这是什么意思?”

诸小亮:“也就是说:一个类只能有一个父类,比如:”
image.png
张小飞:“这是为啥?”

诸小亮:“这还用问?你有俩爹吗?”

“…,我是想问Java中为什么不能多继承”

“如果一个类有两个父类,且这两个父类又都有 show 方法,你觉得子类应该继承的是哪个?”

“嗯…,这个,不能同时继承吗?”

“你这样问,就是还没明白什么是继承?继承父类的成员,就相当于再子类中定义了这些成员一样”

class Person{
    int age;

    void show(){
        System.out.println(this.age);
    }
}


class Student extends Person{
	//Student继承Person后,就相当于再 Student 中定义了 age 属性,和 show 方法

    /*int age;

    void show(){
        System.out.println(this.age);
    }*/
}

张小飞:“原来是这样,一个类中不允许同时存在多个相同的方法

诸小亮:“但是,允许多层次继承

张小飞:“这又是什么意思?”

“来,看下面代码:”

class Person{
    int age;
    
    void show(){
        System.out.println(this.age);
    }
}

class Student extends Person{
}

//Pupil 继承Student,而Studen又继承Person,所以Pupil拥有Person的属性和方法
class Pupil extends Student{
    
}
public class Demo2{
    public static void main(String[] args){
        //这时候,pp 即是 Pupil 类型,也是 Student 类型,还是 Person 类型
        Pupil pp = new Pupil();
        pp.age = 18;
        pp.show();
    }
}

张小飞:“原来还可以这样,那,这时候 Person 是爷爷类?”

诸小亮:“额…,是这样没错,不过我们一般统称 Student 和 Person 都是父类”

张小飞:“好吧”

诸小亮:“最后一点:当事物之间存在所属关系时,才可以使用继承

张小飞:“额…,完全不懂”

诸小亮:“具体来说:如果一个类需要具备另一个类的 所有成员 时,才可以使用extends,比如:

class Dog {
    //奔跑
    void run(){}
    //看家
    void kanjia(){}
}
class Cat extends Dog{
    //这种继承是不允许的,因为猫不能看家
}

张小飞:“但是,如果 Cat 类想继承 run 方法,怎么办?”

诸小亮:“那就继续向上抽取,比如:”

class Animal {
    //奔跑
    void run(){}
}
class Dog extends Animal{
    //看家
    void kanjia(){}
}
class Cat extends Animal{
}

张小飞:“明白了”

3. 继承时,成员的特点

诸小亮:“Java中使用继承时,继承的成员也有一些特点”

张小飞:“哦?什么特点?”

1. 成员变量

诸小亮:“普通成员变量都可以被继承,但是私有属性。。。。。”

张小飞:“怎么,私有属性不能被继承?”

1. 私有属性

诸小亮:“可以被继承,不过先看代码吧”

class Person{
    private String name;//私有属性
    int age;
}
class Student extends Person{
}

private只是访问权限修饰符,不影响继承关系,所以 Student 仍然可以继承name属性”

张小飞:“那,问题是什么呢?”

诸小亮:“你忘了?name 是私有的,所以不能在外界访问(只能在 Person 中访问)”

张小飞:“你说的是这个,这简单,在 Person 中提供 get、set方法,不就行了”

诸小亮:“嗯,你说的不错,所以这也不是很大的问题,你明白就行”

2. super 关键字

诸小亮:“接下来,我们看 super 关键字”

张小飞:“这是做什么用的?”

诸小亮:“还是先看代码,你觉得下面的代码输出结果是什么?”

class Person{
    //1. Person中定义一个 name 属性
    String name = "人类";
    int age;
}
class Student extends Person{
    //2. Student 继承 Person,但是也定义一个 name 属性
    String name = "学生";
    void show(){
        //3. 方法中定义一个 name 局部变量
        String name="亚瑟";
        //4. 输出 name 的值
        System.out.println(name);
    }
}
public class Demo2{
    public static void main(String[] args){
        Student s = new Student();
        //5. 调用show方法
        s.show();
    }
}

张小飞:“这很简单啊,输出的肯定是 ‘亚瑟’”

“为什么输出的不是 ‘学生’呢?”

“想要输出‘学生’得用 this 关键字吧,比如:”
image.png
“问题就在这里,使用 this.name 输出的到底是 ‘学生’还是‘人类’呢?”

“这个…,我来试试”

张小飞:“师兄,输出的是‘学生’,这表示没有继承 Person 的 name 属性吗?”

诸小亮:“当然不是了,这种情况下想使用父类中定义的属性,得用 super,比如:”
image.png

张小飞:“果然,不过这是为什么呢?”

诸小亮:“Student 继承了 Person 中的 name ,但是 Student 中已经有了 name”

  • 为了区分 子父类 中的同名属性,需要使用this、super
  • 注意:真实开发中不应该出现子父类属性名一样的情况

张小飞:“明白了”

2. 成员方法

1. 方法覆盖

诸小亮:“刚才我们说的是属性,其实,方法也可能重复,比如:”

class Person{
    void show(){
        System.out.println("Person 中的方法。。。。。。");
    }

}
class Student extends Person{
    //子类中出现了跟父类中一样的方法
    void show(){
        System.out.println("Student 中的方法。。。。。。");
    }
}
public class Demo2{
    public static void main(String[] args){
        Student s = new Student();
        s.show();
    }
}

张小飞:“还可以这样,我试试,看结果是什么”
image.png

“师兄,如何解释上面的输出?”

当 子父类 中出现一样的方法时,称为:覆盖(override),也叫:重写 或 复写

  • 这时,子类对象调用方法,最终运行的是自己的方法

“这是一种正常现象,开发中经常使用,父类中的功能不是自己想要的,可以重写此功能,比如:”

//定义 Phone 类,有两个功能:call、show(来电显示手机号)
class Phone{
    void call(){}
    void show(){
        System.out.println("来电显示手机号。。。。。。");
    }

}

张小飞:“这,啥都没看出来”

诸小亮:“不要急嘛,如果需求改变:不仅显示手机号,还要显示姓名,这时不建议修改原来的功能,而是新建一个子类重写该功能,比如:

class newPhone extends Phone{
    void show(){
        //如果还需要父类的功能,通过super调用,这样通过复写,就扩展了功能
        super.show();
        System.out.println("显示姓名。。。。。。");
    }
}

张小飞:“为什么不直接在 Phone 中修改代码呢?”

诸小亮:“因为:Phone中的 show 方法可能已经被很多地方使用,修改后会可能造成很大的影响

“原来是这样,我记下了”

“这个——不用记”

“嗯?”

“理论上是这样,但是工作中都是直接修改 Phone 中的 show 方法”

“…,那你给我讲这个干嘛?”

“这不是为了给你解释‘方法覆盖’嘛”

“好吧,我明白了”

2. 注意点

诸小亮:“使用 方法覆盖 时,还需要注意一些地方”

张小飞:“都需要注意什么?”

“第一:子类方法访问权限需大于等于父类方法,否则编译报错,比如:”
image.png
“师兄,public 这个我知道,但是 newPhone 中的 show 方法,没有修饰符啊”

“这个确实还没说过,如果没有修饰符,那么就是默认的default,比 private 高,比 public 低,之后会专门介绍访问权限的”

“好的,好的”

“第二点:静态只能覆盖静态,否则编译报错,比如:”

image.png
“上图提示:newPhone 中的静态方法 show,不饿能覆盖 Phone 中的实例方法 show”

3. 构造函数

诸小亮:“继承时候,构造函数也有一些地方需要注意”

张小飞:“都有哪些地方呢”

“第一点:创建子类对象的时候,父类的构造函数也会运行,比如:”

class Person{
    Person(){
        System.out.println("父类构造函数。。。。。");
    }
}
class Student extends Person{
    Student(){
        System.out.println("子类构造函数。。。。。");
    }
}
public class Demo2{
    public static void main(String[] args){
        //创建 Student 对象时,Person 的构造函数也会执行
        new Student();
    }
}

结果:
image.png
张小飞:“为啥会运行?”

诸小亮解释道:“因为在子类构造函数中,默认的第一行是** super(); **调用了父类的构造函数”

“没有在代码中看到 super(); 啊”张小飞疑惑道

“有的,只是没有显示出来而已”

“那,这样是不是也创建出了一个 Person 对象?”

“不会的,调用父类构造函数的目的:初始化子类继承的属性,并不会创建一个父类的对象

“哦,原来是这样”

“师兄,还有一个问题,如果父类中没有空参数的构造函数呢?”

“这就是我要说的第二点:这时代码会编译报错”
image.png
“那该如何解决呢?”

在子类中需要显式的调用就行了,比如:”
image.png
注意:super 必须放在第一行,因为父类构造函数需要先运行

4. final

诸小亮:“接着我们学些——final 关键字”

张小飞:“这个我知道,final 是最终的、不可更改的意思,对吧”

“嗯…,中文翻译是没错,不过我要说的是,它用来可以修饰:类、方法、变量

“修饰就修饰呗,难道还有什么影响?”

“聪明,用 final 时候确实需要注意一下”

“第一:final 修饰的类不可被继承,比如:”
image.png
“为啥不让继承啊”张小飞问道

“官方的说法:为了保证某些类的稳定性和安全性,防止出现一些意外的情况和错误

“…,能不能说的简单些?”

“有些人就是不想有儿子,所以把自己结扎了,这样明白不?”

“哈哈,明白,明白”

“第二:final 修饰的方法不可被覆盖,比如:”
image.png
“这个又是为什么?”

“防止子类在不经意间修改父类的方法,从而破坏程序的正确性”

“好吧”

“第三:final 修饰的变量不可更改,比如:”
image.png
“上图提示,不能对final修饰的变量进行修改,另外:一般使用final定义的变量都应该是大写字母

“这是为什么?”

“因为——这样显的更加专业” 诸小亮回答道

“…”

“师兄,为什么我这里可以改?”
image.png
“这就是我要说的最后一点:”

  • 如果修饰的是一个对象,可以修改对象的属性,但不能让它指向一个新的对象

比如:
image.png
“能否具体解释一下?”

“好吧,首先你得明白,final 修饰的是 p 而不是 name 属性”

“嗯嗯,这个我知道”

p 的值是一个内存地址,而 p = new Person(); 表示让 p 指向新的内存地址,所以报错
而 p.name=“冯先生”,只是修改对象里面的属性,并不会更改 p 的值,所以可以修改成功”

“原来如此”

5. 抽象类

诸小亮:“接下来就介绍一些——抽象类”

张小飞:“什么叫——抽象类”

1. 介绍

诸小亮:“抽象:就是笼统,笼统就是信息不完善,无法具体描述”

张小飞:“Java不是可以描述世间万物吗,有什么是不能描述的?”

“你说的不错,不过总有例外,比如:现实中,动物都能吼叫,这你知道吧”

“废话,不会叫的是王八”

“哟,看你厉害的,那你给我说说,动物是怎么叫的”

“额…,你这个问题就不对,哪有这样问的”

“不行了吧,看我的,如果用常规的方法写一个类描述动物”

//定义一个 Animal 类,有一个 sound 的功能
class Animal{
    void sound(){
        //无法描述动物是怎么叫的
    }
}

张小飞白了一眼,说道:“你还不是一样,写不出来嘛”

诸小亮:“但是可以用子类复写啊,比如:”

class Animal{
    //定义一个 Animal 类,有一个 sound 的功能
    void sound(){
        //在方法中,我们无法描述动物是如何吼叫的,因为每种动物都不同,只有在子类中复写
    }
}
class Dog extends Animal{
    void sound(){
        System.out.println("汪汪汪。。。。。");
    }
}
class Cat extends Animal{
    void sound(){
        System.out.println("喵喵喵。。。。。");
    }
}

张小飞:“但是,Animal 中有个 sound 方法中没有内容,不是没有意义吗?”

诸小亮:“所以,当一个功能无法具体描述,但是又不可缺少的时候,就要把它写成抽象方法
“上面代码, sound 方法无法具体描述,但是又不可缺少,这时就要用抽象

//抽象方法:使用 abstract 修饰,没有方法体,直接分号结束
abstract void sound();

“另外,如果一个类中有抽象方法,那么这个类肯定是:抽象类”,比如:

//抽象类:也是使用 abstract 修饰
abstract class Animal{
    abstract void sound();
}

张小飞:“原来是这样”

诸小亮:“注意:抽象类无法创建对象

张小飞:“嗯?那有什么用”

“目的:声明抽象方法,让子类复写”

“好吧,不过子类必须的实现父类的抽象方法吗?”

“不是必须的,如果子类不想父类的所有抽象方法,那么子类也应该是一个抽象类
image.png
上图提示:Dog 中没有复写 Animal 的 sound 方法,也应该是一个抽象类

张小飞“那,抽象类中只能有抽象方法吗?”

诸小亮:“不一定,也可以又具体的方法,比如:”

abstract class Animal{
	void eat(){
        System.out.println("动物都是用嘴吃饭。。。。。");
    }
}

张小飞:“这又是为什么,难道是为了让子类继承?”

诸小亮:“又让你猜对了”

张小飞:“…”

诸小亮:“我们总结一下:”

  • 抽象类和抽象方法都需要使用 abstract 修饰
  • 抽象类中可以写具体的方法
  • 抽象类不能使用new创建对象
  • 如果子类没有复写父类的所有抽象方法,那么子类也应该是一个抽象类

2. 一些细节(了解)

诸小亮:“再给你说一些细节的东西吧,了解一下就行”

  • 抽象类中有构造函数,目的:给子类对象进行初始化
  • 抽象类一般都应该有子类,没有子类的抽象类没有意义
  • 抽象类中可以没有抽象方法,都是具体的方法,目的:不让它创建对象

诸小亮:“另外,关于 abstract 关键字也有一些注意点”

第一:不能和 final 同时修饰一个类,否则编译报错,比如:
image.png

第二:不能和 private 或 static 修饰同一个方法,否则编译报错,比如:
image.png

第三:不能和 default 修饰同一个方法(JDK 8的新特性,讲接口时再说)

3. 接口

张小飞:“这个接口是???”

诸小亮:“来,先给你介绍一下”

1. 介绍

诸小亮:“当一个抽象类中的所有方法都是抽象的,那么它就是个接口,比如:”

//接口:使用 interface 修饰
interface Animal{
    abstract void sound();
    abstract void eat();
}

“你可以这样认为:接口——就是一种特殊的抽象类

“具体是怎么特殊呢?”

接口一般中的成员有固定修饰符

“固定修饰符?”

“没错,接口中的成员默认就是 public,而且必须是 public,所以可以省略,比如:”
image.png

“而且,如果用其他修饰符,会报错,比如:”

image.png
张小飞“原来是这样”

“还有,接口中的方法默认就是抽象方法,所以 abstract 关键字也可以省略
image.png
张小飞:“所以说,public 和 abstract 就是接口中方法的固定修饰符?”

诸小亮“是的,不仅是方法,接口中的变量也都是 final 且 static 的,比如:”

image.png
上图提示,Animal 中的 num 是 final 的,不能修改

张小飞:“明白了,这样的话,接口的定义完全可以简化,比如:”

interface Animal{
    int NUM = 100;
    void sound();
    void eat();
}

“不过,接口具体有什么用呢?”

“工作中,一般用接口来定义全局常量,毕竟它默认是 public 且 final 且 static 的嘛”

“嗯,这个我明白,那它的抽象方法呢?”

“这就跟抽象类差不多了,都是给子类用的”

2. 接口的子类

诸小亮:“接口不能创建对象,定义它目的:让子类去实现

张小飞:“实现?不是继承吗?”

“接口这里不叫继承,而是通过 **implements **关键字去实现接口,比如:”
image.png

“另外,子类需要实现接口中的所有抽象方法,否则它也应该是个抽象类,比如:”

image.png
上图提示:必须实现每一个抽象方法,或者声明Dog是抽象类

张小飞:“这样看来,接口还不如抽象类呢,用抽象类还能继承一些东西”

诸小亮:“这你就错了,接口最大的好处就是:一个类可以同时实现多个接口

“还可以这么玩儿?”

“当然,其目的:弥补不能多继承的缺陷,比如:”
image.png
“另外,在实现接口的同时,也可以继承其他类”
image.png
张小飞:“原来还可以这样”

诸小亮:“最后一点:接口可以有子接口,它们之间是继承关系,比如:”

interface Animal{
    void sound();
}
//BigDog继承了Animal,那么它就继承了Animal的sound方法
interface BigDog extends Animal{
    void eat();
}
//Dog实现BigDog,所以要实现两个方法
class Dog implements BigDog {
    @Override
    public void sound() {}
    @Override
    public void eat() {}
}

张小飞:“好的,我记下了”

3. default

诸小亮:“其实,接口中也可以定义具体的方法”

张小飞:“不是说只能定义抽象方法吗?”

“那是JDK 8之前的事了,从 JDK 8 开始,接口通过 default 关键字可以定义具体的方法,比如:”

interface Animal{
    //使用 default 可以定义具体的方法
    default void run(){
        System.out.println("动物在奔跑。。。。。");
    }
}

“同时,接口的子类可以继承 defualt 方法,比如:”

interface Animal{
    //1. 使用 default 可以定义具体的方法
    default void run(){
        System.out.println("动物在奔跑。。。。。");
    }
}
class Dog implements Animal{
}
public class Demo{
    public static void main(String[] args){
        //2. Dog直接继承run方法,所以它的对象可以直接调用
        new Dog().run();
    }
}

结果:
image.png
张小飞:“这样说的话,抽象类就没啥用了”

诸小亮:“你说的不错,所以现在工作中大多数都用接口,我这么多年也没用过抽象类”

如果一个类实现了俩接口,它们又有相同的default方法,这样是继承哪个?”

“这种情况下,子类么必须进行复写,比如:”
image.png
上图提示:Dog不知道从哪个接口中继承run方法,所以必须复写

张小飞:“原来如此,那如果是这样呢?”

比如:Dog 继承了 BigDog,并实现 Animal 接口,而 BigDog 和 Animal 有相同的方法

interface Animal{

    default void run(){
        System.out.println("动物在奔跑。。。。。。");
    }
}

class BigDog{

    public void run(){
        System.out.println("大狗在奔跑。。。。。。");
    }
}

class Dog extends BigDog implements Animal{
}

诸小亮:“这个,你运行一下,就可以得到答案了”

30秒后。

张小飞:“原来是继承优先”

public class Demo {
    public static void main(String[] args) {
        //Dog 最终继承的是 BigDog 中的方法
        new Dog().run();
    }
}

结果:image.png

4. 静态方法

诸小亮:“最后一个知识点了——静态方法”

张小飞:“接口中也可以定义静态方法?”

“是的,可以定义静态方法,使用方式:接口名.方法名,比如:”

interface Animal{

    default void run(){
        System.out.println("动物在奔跑。。。。。。");
    }
    static void eat(){
        System.out.println("动物都会吃东西。。。。。。");
    }
}

public class Demo {
    public static void main(String[] args) {
        Animal.eat();//调用接口中的静态方法
    }
}

“好了,这样我们就说完了,你先回顾一下,一会儿我们做个总结”

5. 总结

十分钟后。

诸小亮:“师弟,能简单的总结一下接口吗?”

张小飞:“我试试”

  • 接口是一种特殊的抽象类,使用 interface 修饰
  • 接口中的方法全是 public 且 abstract 的
  • 接口中的变量全是 public 且 final 且 static 的(其实就是常量)
  • JDK 8 开始可以通过 default 在接口中定义具体的方法
  • 另外,接口中也可以定义静态方法

诸小亮:“嗯,不错,记住这些就够了,刚好也中午了,咱就先休息一下”

张小飞:“早就等着你这句话呢”

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值