写在前面:
视频是什么东西,有看文档精彩吗?
视频是什么东西,有看文档速度快吗?
视频是什么东西,有看文档效率高吗?
诸小亮:“封装说完后,我们接着看面向对象的第二大特征——继承”
张小飞:“跟我们生活中继承是一样的?”
诸小亮:“当然,计算机中的一切都是从生活中来的”
1. 介绍
1. 生活中的继承
诸小亮:“首先,我们看下生活中的继承”
张小飞:“这有啥了解的,不就是儿子继承老爹的家产么”
“…,我说的继承不是这个”
“嗯?那你说的是?”
“生活中有:小学生、大学生、小学老师、大学老师这些类别”
“何止啊,还有:男人、女人、好人、坏人这些呢”
“…,不要插嘴,听我说:小学生、大学生 拥有很多共同点,可以统称为:学生”
“嗯嗯,学生是一个更大的分类”
“同样的,小学老师、大学老师 也拥有很多共同点,可以统称为:老师”
“师兄,照你这么说,老师、学生 也有很多共同点,可以统称为:人”
“你说的很对,如果用一张图来表示它们之间的关系:”
所谓继承:不同分类可以向上抽取共同点,形成一个更大的分类(也叫:父类)
张小飞:“原来这就是继承”
2. Java中的继承
诸小亮:“Java 中的继承也一样,先看两个类:”
class Student{
int age;
}
class Teacher{
int age;
}
张小飞:“很明显:这两个类的共同点是:年龄”
诸小亮:“没错,所以可以向上抽取,创建一个新的类:”
class Person{
int age;
}
张小飞:“那,怎么表示继承关系呢?”
诸小亮:“在 Java 中用 extends 来表示继承关系,比如:”
“这时,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中,只有单继承,没有多继承”
张小飞:“这是什么意思?”
诸小亮:“也就是说:一个类只能有一个父类,比如:”
张小飞:“这是为啥?”
诸小亮:“这还用问?你有俩爹吗?”
“…,我是想问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 关键字吧,比如:”
“问题就在这里,使用 this.name 输出的到底是 ‘学生’还是‘人类’呢?”
“这个…,我来试试”
张小飞:“师兄,输出的是‘学生’,这表示没有继承 Person 的 name 属性吗?”
诸小亮:“当然不是了,这种情况下想使用父类中定义的属性,得用 super,比如:”
张小飞:“果然,不过这是为什么呢?”
诸小亮:“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();
}
}
张小飞:“还可以这样,我试试,看结果是什么”
“师兄,如何解释上面的输出?”
“当 子父类 中出现一样的方法时,称为:覆盖(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. 注意点
诸小亮:“使用 方法覆盖 时,还需要注意一些地方”
张小飞:“都需要注意什么?”
“第一:子类方法访问权限需大于等于父类方法,否则编译报错,比如:”
“师兄,public 这个我知道,但是 newPhone 中的 show 方法,没有修饰符啊”
“这个确实还没说过,如果没有修饰符,那么就是默认的default,比 private 高,比 public 低,之后会专门介绍访问权限的”
“好的,好的”
“第二点:静态只能覆盖静态,否则编译报错,比如:”
“上图提示: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();
}
}
结果:
张小飞:“为啥会运行?”
诸小亮解释道:“因为在子类构造函数中,默认的第一行是** super(); **调用了父类的构造函数”
“没有在代码中看到 super(); 啊”张小飞疑惑道
“有的,只是没有显示出来而已”
“那,这样是不是也创建出了一个 Person 对象?”
“不会的,调用父类构造函数的目的:初始化子类继承的属性,并不会创建一个父类的对象”
“哦,原来是这样”
“师兄,还有一个问题,如果父类中没有空参数的构造函数呢?”
“这就是我要说的第二点:这时代码会编译报错”
“那该如何解决呢?”
“在子类中需要显式的调用就行了,比如:”
注意:super 必须放在第一行,因为父类构造函数需要先运行
4. final
诸小亮:“接着我们学些——final 关键字”
张小飞:“这个我知道,final 是最终的、不可更改的意思,对吧”
“嗯…,中文翻译是没错,不过我要说的是,它用来可以修饰:类、方法、变量”
“修饰就修饰呗,难道还有什么影响?”
“聪明,用 final 时候确实需要注意一下”
“第一:final 修饰的类不可被继承,比如:”
“为啥不让继承啊”张小飞问道
“官方的说法:为了保证某些类的稳定性和安全性,防止出现一些意外的情况和错误”
“…,能不能说的简单些?”
“有些人就是不想有儿子,所以把自己结扎了,这样明白不?”
“哈哈,明白,明白”
“第二:final 修饰的方法不可被覆盖,比如:”
“这个又是为什么?”
“防止子类在不经意间修改父类的方法,从而破坏程序的正确性”
“好吧”
“第三:final 修饰的变量不可更改,比如:”
“上图提示,不能对final修饰的变量进行修改,另外:一般使用final定义的变量都应该是大写字母”
“这是为什么?”
“因为——这样显的更加专业” 诸小亮回答道
“…”
“师兄,为什么我这里可以改?”
“这就是我要说的最后一点:”
- 如果修饰的是一个对象,可以修改对象的属性,但不能让它指向一个新的对象
比如:
“能否具体解释一下?”
“好吧,首先你得明白,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();
}
张小飞:“原来是这样”
诸小亮:“注意:抽象类无法创建对象”
张小飞:“嗯?那有什么用”
“目的:声明抽象方法,让子类复写”
“好吧,不过子类必须的实现父类的抽象方法吗?”
“不是必须的,如果子类不想父类的所有抽象方法,那么子类也应该是一个抽象类”
上图提示:Dog 中没有复写 Animal 的 sound 方法,也应该是一个抽象类
张小飞“那,抽象类中只能有抽象方法吗?”
诸小亮:“不一定,也可以又具体的方法,比如:”
abstract class Animal{
void eat(){
System.out.println("动物都是用嘴吃饭。。。。。");
}
}
张小飞:“这又是为什么,难道是为了让子类继承?”
诸小亮:“又让你猜对了”
张小飞:“…”
诸小亮:“我们总结一下:”
- 抽象类和抽象方法都需要使用 abstract 修饰
- 抽象类中可以写具体的方法
- 抽象类不能使用new创建对象
- 如果子类没有复写父类的所有抽象方法,那么子类也应该是一个抽象类
2. 一些细节(了解)
诸小亮:“再给你说一些细节的东西吧,了解一下就行”
- 抽象类中有构造函数,目的:给子类对象进行初始化
- 抽象类一般都应该有子类,没有子类的抽象类没有意义
- 抽象类中可以没有抽象方法,都是具体的方法,目的:不让它创建对象
诸小亮:“另外,关于 abstract 关键字也有一些注意点”
第一:不能和 final 同时修饰一个类,否则编译报错,比如:
第二:不能和 private 或 static 修饰同一个方法,否则编译报错,比如:
第三:不能和 default 修饰同一个方法(JDK 8的新特性,讲接口时再说)
3. 接口
张小飞:“这个接口是???”
诸小亮:“来,先给你介绍一下”
1. 介绍
诸小亮:“当一个抽象类中的所有方法都是抽象的,那么它就是个接口,比如:”
//接口:使用 interface 修饰
interface Animal{
abstract void sound();
abstract void eat();
}
“你可以这样认为:接口——就是一种特殊的抽象类”
“具体是怎么特殊呢?”
“接口一般中的成员有固定修饰符”
“固定修饰符?”
“没错,接口中的成员默认就是 public,而且必须是 public,所以可以省略,比如:”
“而且,如果用其他修饰符,会报错,比如:”
张小飞“原来是这样”
“还有,接口中的方法默认就是抽象方法,所以 abstract 关键字也可以省略
张小飞:“所以说,public 和 abstract 就是接口中方法的固定修饰符?”
诸小亮“是的,不仅是方法,接口中的变量也都是 final 且 static 的,比如:”
上图提示,Animal 中的 num 是 final 的,不能修改
张小飞:“明白了,这样的话,接口的定义完全可以简化,比如:”
interface Animal{
int NUM = 100;
void sound();
void eat();
}
“不过,接口具体有什么用呢?”
“工作中,一般用接口来定义全局常量,毕竟它默认是 public 且 final 且 static 的嘛”
“嗯,这个我明白,那它的抽象方法呢?”
“这就跟抽象类差不多了,都是给子类用的”
2. 接口的子类
诸小亮:“接口不能创建对象,定义它目的:让子类去实现”
张小飞:“实现?不是继承吗?”
“接口这里不叫继承,而是通过 **implements **关键字去实现接口,比如:”
“另外,子类需要实现接口中的所有抽象方法,否则它也应该是个抽象类,比如:”
上图提示:必须实现每一个抽象方法,或者声明Dog是抽象类
张小飞:“这样看来,接口还不如抽象类呢,用抽象类还能继承一些东西”
诸小亮:“这你就错了,接口最大的好处就是:一个类可以同时实现多个接口”
“还可以这么玩儿?”
“当然,其目的:弥补不能多继承的缺陷,比如:”
“另外,在实现接口的同时,也可以继承其他类”
张小飞:“原来还可以这样”
诸小亮:“最后一点:接口可以有子接口,它们之间是继承关系,比如:”
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();
}
}
结果:
张小飞:“这样说的话,抽象类就没啥用了”
诸小亮:“你说的不错,所以现在工作中大多数都用接口,我这么多年也没用过抽象类”
“如果一个类实现了俩接口,它们又有相同的default方法,这样是继承哪个?”
“这种情况下,子类么必须进行复写,比如:”
上图提示: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();
}
}
结果:
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 在接口中定义具体的方法
- 另外,接口中也可以定义静态方法
诸小亮:“嗯,不错,记住这些就够了,刚好也中午了,咱就先休息一下”
张小飞:“早就等着你这句话呢”