CS 61B 继承
笔记的来源:CS 61B-2024 春季的课程 课程主要内容:数据结构与算法分析 课程运用语言:Java
这个课有6 个 Homework,10 个 Lab,9 个 Project。其中第一个 project 是一个完整的 2024 游戏的实现,很有意思。此文章对应的是课程 8-9 节的内容。 由于内容较多,还有 10-11 节的内容写在楼下一篇文章中。
此笔记对应资源:CS 61B 课本资源
上位词,下位词
在语言学中,上下位词的概念用于形容词的关系。比如红色的上位词可以是颜色。在 java 中,这种关系被用于形容类之间的继承关系。
比如说我定义了一个类Animal
,它有一些共同的属性和方法,比如说叫做“吃”,“睡觉”,“跑”。然后我定义了一个类Dog
,它继承了Animal
的属性和方法,并添加了一些狗独有的属性和方法,比如说“抱”,“拉”,“摇”。Dog
类可以认为是Animal
类的子类。
这里称Dog
类是Animal
类的子类 subclass,Animal
类是Dog
类的超类 superclass。
在这里我们先介绍接口继承的概念。
接口继承
在上面的例子当中,可以称Animal
类为接口,它本质上是一个契约,指定动物类能有什么行为。接下来我们用定义关系的关键词implements
来建立一个Dog
类,继承Animal
接口。
默认方法 Default Method
如果在接口中定义了一个方法,如:
那么系统会报错Interface methods cannot have body
,因为接口方法不能有方法体。
为了解决这个问题,Java 8 引入了默认方法的概念。默认方法可以有方法体,可以被子类继承,也可以被实现类实现。需要加上default
关键字。
覆盖
在子类中实现所需函数时,@Override
在方法签名的前面,用来表示覆盖父类中的默认方法。
其实不加这个标签,依然可以实现对父类方法的覆盖。这个标签的一大作用,便是对拼写错误的检查,如果你添加了@Override
,但是对应的方法名称在父类的接口中不存在,编译器会报错。
静态类型以及动态类型
在 Java 中,每一个变量都有一个静态类型和一个动态类型。静态类型是在编译时确定的,而动态类型是在运行时确定的。
比如说:
在上面的代码中,animal
的静态类型是Animal
,而它的动态类型是null
,因为还没有给它赋值。
在上面的代码中,animal
的静态类型是Animal
,而它的动态类型是Dog
。而且我们可以改变animal
的动态类型,但是不能改变它的静态类型。
Extends
关键词
Extends
关键词当我们继承一个接口的时候我们使用的关键词是implements
,但是当我们继承一个类而不是继承接口的时候我们使用的关键词是extends
--扩展。
扩展可以使的子类继承父类的所有成员,包括:
所有实例和静态变量
所有方法
所有嵌套类
构造函数不能被继承!
构造函数
构造函数不可继承。但是,Java 规则规定,所有构造函数都必须从调用超类的构造函数之一开始。可以使用关键字super
明确调用构造函数。如果您没有明确调用构造函数,Java 将自动为您执行该操作。
下面的代码等价:
但是,如果父类构造函数有参数,则子类构造函数必须调用父类构造函数,并传入相应的参数。
下面两段代码就不一样了:
Object
类
Object
类所有类的祖先都是Object
类,它是所有类的父类。Object
类中定义了一些方法,如:equals()
,hashCode()
,toString()
等。具体文档查看Object 类。Object
类声明了这些方法:
IS-A 关系和 HAS-A 关系
这两种关系用来描述的是类和对象之间彼此的两种基本关系。
IS-A 关系:
一个类是另一个类的子类,或者说,它是另一个类的一种。
例如,
Dog
类是Animal
类的子类。
HAS-A 关系:
一个类包含另一个类的实例变量,或者说,它是一个类的组成部分。
例如,
Dog
类包含一个name
变量,表示狗的名字。
在这里extends
方法只运用于 IS-A 关系。
类型检查和类型转换
在上面的代码中,animal
的静态类型是Animal
,而它的动态类型是Dog
。我们称含有 new 的类型声明为运行时类型(动态类型),而不含有 new 的类型声明为编译时类型(静态类型)。
一个重要的性质就是,animal
可以使用Dog
类的任何方法,因为Dog
类是Animal
类的子类。但是如果使用Dog
类中新添加而不是Animal
类中定义的方法,则会出现编译错误。
如果将上面等号两边反过来,则会发生类型检查错误:
在上面的代码中,dog
的静态类型是Dog
,而它的动态类型是Animal
。这时编译器会报错,因为Animal
不是Dog
的子类。
假如我们有一个方法 oldestAnimal()
,用来比较两个动物的年龄,他的类型是Animal
:
那么我们就不能这么写:
因为oldestAnimal()
方法传出的参数类型是Animal
,而 oldestDog 的静态类型是Dog
,所以编译器会报错。这个时候我们可以利用类型转换:
高阶函数
下面展示如何用复杂的 java 实现此简洁的 python 代码 : )
java 实现:
可以看出来,这里运用了一个apply
方法作为中间过渡,看起来是把函数当成变量,实则是改变了apply
方法的内容。
Last updated