Course II notes
两个重要的组成部分:
instant variable 的声明位置应该在紧接 class header
简单的例子:
Visibility Modifiers 指在类/instance variable 声明的最前面的关键字。该关键字用于控制类对 instance variable 的管理权限。一些概念:
private
: 只有类中的 method 能够修改本类的 instance variablespublic
:允许类外的其他类访问本类的 instance varibales.
通常对类进行测试的手段一般是在类中直接新建一个 main
来测试:
null
public
,因为需要允许其他部分调用构造函数生成对象。构造函数的调用(Object 的实例化)需要配合自定义构造函数的参数列表:
与其他 method 一样,constructor 也可以通过重载适应不同的参数列表:
this
作为创建方式
Instances methods 概念上类似 C++ 的成员函数。分为两类:
public
的,为其他外部调用者提供接口,因此没有 static
关键字。private
的,只用于实现 interace 中的 Method。
class 的定义中通常会出现一些各个 Instance 都会共享的常量:
static
将该常量从 instance 限定 转换为多个 instances 共享。public
并不会影响封装。
如果不是常量的话,需要设置为 private
,避免除了 instances 以外的数据对其进行修改。比如我们在构造函数内部添加一个计数属性用于统计总共创建的对象数:
只有在外部访问类成员的时候,才需要 className.classMember
这样的方式。
Math.random()
返回 之间的数。可以使用下面的表达式来返回 (min, max)
之间的 int
:
this
关键字表示的是当前 object 的引用。Java 中可以在类的内部使用 this.attribute
/ this.method()
的方式来访问(调用)类成员。比如之前的构造函数可以改写成:
weight
, x
, y
是构造函数的局部变量;真正的 instance variable weight
, x
, y
是通过 this
来取得的。
某些情况下,我们可能需要在类外访问或者修改类中的私有变量。这种情况下我们是无法通过 className.classMember
的方式进行访问的。
访问解决方法是,在类中创建 public
的 method,通过该 method 去访问对应的私有变量。我们将这样的 method 称为 Accessor 或 Getter,比如:
Mutator(Setter),用于在类外修改类中的私有变量。其实现方式与 Accessor 一致。几个 converntion:
void
,因为只需要做修改set
为前缀,比如 setName(para)
toString()
是 Java 自带的,所有 Object 都会继承的,一个将当前对象信息转化为 String 的 method。默认情况下,toString()
的输出结果为:
Insect
类,如果需要查看类中 3 个 Instance variable 的内容:
由于 println
method 默认情况下打印的就是 toString()
的返回结果,因此上面的内容打印可以直接简化为:
几个重点:
Die
,以及骰子的行为 roll
的实现方法Craps
CrapLauncher
Math.Random()
,Java 还提供了 util.Random
供使用。本例使用了 Random.nexInt(bound)
实现了骰子的随机结果。Random.nexInt(bound)
返回的区域是 Source code with comments: craps.zip
Class
的类;该类多用于继承时使用。在 Java 的继承机制中:
public
的方式继承会破坏封装
因此,如果一个 subclass,希望继承来自 Parent class 的私有成员,但又不希望这些成员被该 subclass 以外的类使用的话,使用 protected
关键字定义 parent class 中的成员:
注:Java 中的 protected 成员,允许被同 package 下的其他 class 使用。
访问权限表格:
Modifier | Class | Package | Subclass | World |
---|---|---|---|---|
public | Y | Y | Y | Y |
protected | Y | Y | Y | N |
private | Y | N | N | N |
extends
super()
methodsuper()
代表了对父类构造函数的调用
super
的调用在子类里是强制的。如果我们没有显式的为子类调用 super
以及对应的私有变量,Java 会自动调用一个无参数 的 super
。这种调用不会考虑到已继承的变量,因此通常会出错:
Java 中的重写只需要在子类中重新定义需要重写的函数即可:
注意上面的重写部分,如果希望以父类函数为基础进行重写,只需要使用super
调用父类函数即可,比如这里的 super.bark()
。
调用重写函数的过程的概念就是 C++ 里的动态绑定;也就是根据 instance 指向的 class 类型来决定(动态类型,运行期决定)。
可以在函数前加上 @Override
来确保重写的函数名是否与父类中的函数名相同。
final
关键字应用到 method 上,会阻止其被重写final
关键字应用到 class 上,会阻止其被继承
什么是 abstract method? 在模拟现实生活的过程中,有一些行为的一致性是很低的。比如犬类给自己顺毛的行为,狼,狗,家狗,野狗,都不一样;这种情况下,在父类 Canine
中定义一个标准的 groom()
method 是很难的。
我们处理的方式是,声明这个方法,但不实例化,将实现交给子类自行解决。这个要求是强制的;也就是说,在继承了 abstract method (class) 后,子类必须对对应的 abstract method 进行具体的实现。
定义抽象类 / 抽象方法只需要添加 abstract
关键字即可:
Object
类型的对象,去访问任意其他 Object
对象中没有声明的方法,都是错误的。
Object
中有一个 method 叫 equals
(之前用过,String
类中对齐进行了重载)。默认情况下,Object.equals()
的作用是判断两个实例引用是否指向同一个实例。根据之前讨论的内容,设想一种情况:在不知道 Object
的实例类型具体是哪种子类的情况下,我们要如何使用该对象与其某种特定类型的子类进行比较呢?
一个可行的判断依据是,只有两个子类是同一类型的对象时,才能进行比较。以 Dog
为例,只有两边都是狗的时候,我们才能依据一些条件来进行比较。因此,首先需要做两种判断:
null
的对象)Dog
的不予考虑
Java 提供了 instanceof
运算符将上述的两个操作合二为一了。因此,要检测是不是同一类型的对象,有:
Dog
类中将 equals
按照我们的规则进行重写了,比如:
需要注意的是,即便证明了 o
是 Dog
类型的对象,但使用 o
依然无法访问 Dog
重写的 equals
。因此,我们需要使用另外一个 Dog
类型的引用来指向 o
,从而间接达到用 o
访问 Dog.equal()
的效果。我们将该引用命名为 doggy
,其值为将 o
强制转换为 Dog
类型后的值:
这样,即便不清楚 o
的具体类型的情况下,我们也能安全的将其与 Dog
类型的实例进行比较了。
在实际的应用中会出现这么一种关系,某一类的对象具有某个行为上的共同点,但对象本身并不构成继承的关系。比如狗与车,都有 “洗” 的行为;但狗和车显然不构成 IS 的关系。
这种情况是无法使用子类重写的方式解决的。因为子类重写的方式要求两点:
比如如果都是犬类所属,那对象的 group 定义为犬类即可:
但显然车对象是无法放进grommer
的,因为车的父类并不是 Canine
。因此,我们需要一种新的方式来解决这个问题:如果将没有继承关系,但又具有某些相同行为的不同对象放入到一个 group 中进行遍历调用?
与 class 的定义类似,interface 也需要单开一个与 Interface 名称相同的文件。在 interface 中声明 method 的方式与 class 一致。在 interface 中,moidifer 的默认有两点:
编译的结果依然是
.class
文件。
implements
关键字:
Canine[]
替换为了 Groomable[]
:默认情况下,所有希望一起使用的 class 都需要添加
implements
关键字来绑定 interface。但如果父类是抽象类,我们只需要绑定父类即可;父类下的所有子类都会继承这一绑定。规则与之前一样,抽象类要有方法声明,子类要有方法实现:
Sorting 在 OOP 中有着重要的作用;但 Sorting 是一件具体的事情。以 Wolf
对象为例,我们可以根据其 rank
的值设计一个排序方法:
Wolf
对象,那么我们需要重写该方法的两部分:header(包括接收的对象)和 condition。这样做非常繁琐,因为每一个新类型的对象都需要写一个新的排序方法。
我们将这个方法命名为 CompereTo()
。由于所有的类都是 Object
的子类,因此通用排序方法可以改写为:
compareTo()
就可以完成比较,而不是针对每一个类都去设计一个 sorting 的 method。
int
,分为三种情况:arr[index]
的方式。与 interface 绑定会确认这些元素内有可以执行的方法。java.lang.Comparable
类型作为 compareTo
的实现条件。util.Array.sort()
作为对象排序的实现(使用 Timsort)compareTo
的对象转化为 Compareable
类型
Comparable
与 之前自定义 Interface 的使用方法一致,将其至于 implements
关键字后面。如果有多个 Interface,以逗号隔开:
Wolf
对象使用的 compareTo()
实现:
int
Object
类型Object
类型转化为 Wolf
类型,这样才能访问 rank
变量。.
的优先级非常高。
上述的解决方案中,由于 casting 是在 runtime 时进行的,因此 anotherWolf
的类型直到被转换的那一刻才会被检验。这个过程使得不正确的输入类型会导致 runtime error。
解决这个问题的方式是将条件判断转移到编译期。Java 提供了 Interface 的 Type Parameter 来解决这个问题。Type Parameter 可以让 compareTo()
在编译期就可以将真正需要的对象类型确定下来。具体写法:
compareTo
中的实现也需要将 Object
类型改为 Wolf
类型;但因为类型已经确定,casting 的过程也可以省略了:
类型检测尽量放到编译期去验证。
实现了 compareTo()
之后,我们就可以利用 Arrays.sort()
对 Wolf
对象进行排序了。使用前需要导入 Array
package:
10
的会排在最前面:
compareTo()
loop 中的交换:
只需要在最小值不处于比较区域的最开头时才进行。return;
)如果需要修改为支持
Comparable
interface,只需要修改:
int
改为 Comparable
类型CompareTo
Source Code: mergecomparable.java
Java 中的实现关键点:
使用 System.nanoTime()
来记录算法运行的时间:
这是一种允许在 interface 中直接定义 Method 的方式。主要适应的场景是快速为 interface 下的各种类型添加新的共同 Method。比如之前例子中,动物与车都有“洗”的行为;如果希望给这个“洗”加一个付款 pay()
,如果不使用 default method
,那么只能在 interface 中声明虚函数,再去每个类里单独实现了。
有了 default method 以后,则可以直接在 interface 里写;所有 interface 下的实现类都会自动得到该 Method:
重写与一般重写一致:
如果不要求重写,那么没有必要给每一个 interface 的参与类都添加一个 Method。为此,Interface 中也提供了用于定义 static method 的方法:
调用的时候按正常的className.methodName
写法调用即可:
public static final
类型,无论是否带有 Modifier。为什么 interface 里的变量必须是常量?
相比起在 interface 中存放常量,选择在类中存放是更好的选择。因为 class 中同时允许 instance variable 的存在。
Constant Interfaces 指 Interface 中包含的成员均为常量。其应用场景为:程序需要使用很多常量。将这些常量全放在一个地方,可以方便读取。
这样使用固然很方便,但我们还是应该记得:interface 是用于声明行为(concept)的。constant interface 并不属于这一概念,因此尽量不要使用。
«interface»
表示 interfaceextends
)implements
)多态(Polymorphism)允许我们使用一段代码灵活吃处理不同类型的对象。
new
后面那个类型,instantiate type)如果上述条件都不满足,Java 编译器会认为该 assignment 是不合法的。
Java 会对针对第一个问题进行所谓的 Relationshio test:
比如:
这种 Is a 的规则同样应用于将 argument 传递给 parameter 的过程中。这种情况下:比如:
声明为相同 interface 类型的两个对象很可能不是互通的;比如车不能实例化为狗,即便车和狗都有“洗”的行为。
第二个需要检查的是 method 的调用:
method call 只考虑声明类型中是否存在该函数的定义,不考虑对象类型;除非对象类型中存在着该 method 的 overriding(详情见之后的动态绑定)
上述例子中,如果我们希望使用 pixy
直接访问 enterDogShow()
,我们需要将其转换为 Poodle
类型:
Casting 只是生成了一个临时的,对应的 Object 类型的引用。pixy
永远都是指向 Canine
类型的引用。
Casting 带来了一个问题。来看以下例子:
这种情况下,dg
的 object type 是 Dog
,而不是 Poodle
。Casting 是可以进行的;但是在调用 method 的时候,JVM 会做 Is a 的检测。上面的例子中,我们调用的是 Poodle
类型实现的 method,在调用之前,编译器会询问:调用者的 object type 是不是 method 实现所在的 object type? (此处就是在问:Dog
是 Poodle
吗?)如果不是,那该 method call 就无法通过编译。
在检测 method call 是否合法的同时,JVM 还会同时在运行期检测哪一个版本最适合当前的 call。处理该调用匹配的过程被称为动态绑定(Dynamic Binding)。总的来说: