Chapter 13 翻譯 (Abstact Classes and Interfaces)

抽象類同接口

詞彙

  • geometric 幾何的
  • subject 話題

概念

  • 一個 superclass(超類) 定義相關 subclasses(子類) 的共同行爲
  • 一個 interface(接口) 定義多個類的共同行爲(包括不相關的類)

13.2 Abstract Classes 1493

  • abstract class (抽象類) 不能用去創建對象
  • abstract class 可以包括 abstract method (抽象方法)
  • abstract method (抽象方法) 會被 concrte(真實的) 子類所實現
  • perimeter 週長
  • are referred to as
  • denote
  • modifier
  • appropriate

    13.2.2

  • is worth noting 是值得注意的

13.51

13.1 Introduction p1492

父類定義相關子類的共同行爲。接口可以用來多個類的共同行爲(包括不相關類)。

你可以使用 java.util.Arrays.sort 方法去排序一個數字數組或者字符數組。你可以依然用 sort 方法去排序一個幾何數組?爲了寫出這樣的代碼,你必須知道接口。一個接口定義類的行共同爲。在討論接口前,我們引入一個相關主題:抽象類。

13.2 Abstract classes

一個抽象類不能去創建對象。一個抽象類可以包含抽象方法,抽象方法會實現於實子類 concrete subclasses

inheritance hierarchy 繼承體系 中,隨着新類的出現,類變得越來越具體,並且變得越來越實。如果你從子類返到父類,類會越來越通用越來越不具體。類的設計應該確保父類包含子類的共同功能。有時候,一個父類係好抽象,抽象到不可以建立具體實例。噉嘅類 被人叫作 is referred to as 抽象類 abstract class

在 charpter 11 里,GeometricObject 定義成一個父類,CircleRectangle 的父類。GeometricObject 方法將幾何對象的共同特徵造成模型(model 將...做成模型)。兩者 CircleRecangle 包含咗 getArea()getPerimeter() 方法來計算圓同方型嘅面積和 週長 perimeter

因爲你可以計算所有幾何圖形的面積同週長,最好定義 getArea()getPerimeter() 方法在幾何對象類之中。但是這些方法不能被實現在幾何對象類中,因爲這些實現要基於特定的幾何對象類型。這樣的方法被稱作抽象類,並且用 abstract 修改器表示。當你定義了多個抽象方法於 GeometricObject 後,個類變成抽象類。

抽象類用 abstract 修改器標識。在 UML 圖上,抽象類同抽象方法的名字都會是斜體。

GeometricObject
|__ getArea(): double

GeometricObject.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public abstract class GeometricObject {
private String color = "white";
private boolean filled;
private java.util.Date dateCreated;

/** Construct a default geometric object */
protected GeometricObject() {
dateCreated = new java.util.Date();
}

/** Construct a geometric object with color and filled value */
protected GeometricObject(String color, boolean filled) {
dateCreated = new java.util.Date();
this.color = color;
this.filled = filled;
}

/** Return color */
public String getColor() {
return color;
}

/** Set a new color */
public void setColor(String color) {
this.color = color;
}

/** Return filled. Since filled is boolean,
* the get method is named isFilled */
public boolean isFilled() {
return filled;
}

/** Set a new filled */
public void setFilled(boolean filled) {
this.filled = filled;
}

/** Get dateCreated */
public java.util.Date getDateCreated() {
return dateCreated;
}

@Override
public String toString() {
return "created on " + dateCreated +
"\ncolor: " + color +
" and filled: " + filled;
}

/** Abstract method getArea */
public abstract double getArea();

/** Abstract method getPerimeter */
public abstract double getPerimeter();
}

抽象類就好似普通的類,但係不可以用 new 來創建抽象類的實例。一個抽象方法只有定義,沒有實現。實現是它的子類的事情。一個類包含抽象方法,就必須定義成抽象類啦!

abstract class 的構建器被定義成 protected ,因爲只會它的子類會使用它。當你創建一個實子類的實例,是包含了它的父類構造器。

GeometricObject 抽象類定義了幾何對象的共同功能,並且提供了適當的構造器。因爲你不知道如何去計算幾何對象的面積和週長,所以 getArea()getPerimeter() 定義成抽象。這些方法會在它的子類實現。 CircleRectangle 的實現都一樣,除了他們延伸 GeometricObject 類不一樣。

Circle.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class Circle extends GeometricObject {
private double radius;

public Circle() {
}

public Circle(double radius) {
this.radius = radius;
}

/** Return radius */
public double getRadius() {
return radius;
}

/** Set a new radius */
public void setRadius(double radius) {
this.radius = radius;
}

@Override /** Return area */
public double getArea() {
return radius * radius * Math.PI;
}

/** Return diameter */
public double getDiameter() {
return 2 * radius;
}

@Override /** Return perimeter */
public double getPerimeter() {
return 2 * radius * Math.PI;
}

/* Print the circle info */
public void printCircle() {
System.out.println("The circle is created " + getDateCreated() +
" and the radius is " + radius);
}
}

Rectangle.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class Rectangle extends GeometricObject {
private double width;
private double height;

public Rectangle() {
}

public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}

/** Return width */
public double getWidth() {
return width;
}

/** Set a new width */
public void setWidth(double width) {
this.width = width;
}

/** Return height */
public double getHeight() {
return height;
}

/** Set a new height */
public void setHeight(double height) {
this.height = height;
}

@Override /** Return area */
public double getArea() {
return width * height;
}

@Override /** Return perimeter */
public double getPerimeter() {
return 2 * (width + height);
}
}

13.2.1 Why Abstract Methods?

你可以奇怪有什麼好處,在 GeometricObject 類裡去定義 getArea()getPerimeter() 抽象方法。下面的例子顯示了定義 GeometricObject 類的好處,包括 equalArea方法檢查是否兩者面積相等,包括 displayGeometricObject 方法去顯示它們。

TestGeometricObject.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

public class TestGeometricObject {
/** Main method */
public static void main(String[] args) {
// Declare and initialize two geometric objects
GeometricObject geoObject1 = new Circle(5);
GeometricObject geoObject2 = new Rectangle(5, 3);

System.out.println("The two objects have the same area? " +
equalArea(geoObject1, geoObject2));

// Display circle
displayGeometricObject(geoObject1);

// Display rectangle
displayGeometricObject(geoObject2);
}

/** A method for comparing the areas of two geometric objects */
public static boolean equalArea(GeometricObject object1,
GeometricObject object2) {
return object1.getArea() == object2.getArea();
}

/** A method for displaying a geometric object */
public static void displayGeometricObject(GeometricObject object) {
System.out.println();
System.out.println("The area is " + object.getArea());
System.out.println("The perimeter is " + object.getPerimeter());
}
}

方法 getArea()getPerimeter() 定義在 GeometricOjbect 類度,這些方法會被 CircleRectangle 類所覆蓋。下面 statement表達式:

1
2
GeometricObject geoObject1 = new Circle(5);
GeometricObject geoObject2 = new Rectangle(5, 3);

創建一個新的圓形同新的長方形,之後 指向到 assign to 變量 geoObject1geoObject2。這兩個變量都是 GeometricObject 類。

調用invoke equalArea(geoObject1, geoObject2)getArea() 方法定義在 Recrangle 類中,個類係用於 object2.Area() 裡,因爲 goeObject2 係一個長方形。`

同樣,當提及 displayGeometricObject(geoObject1)CirclegetArea()getPerimeter 就會被使用。 displayGeometricObject(geoObject2) 就係 Rectangle 相應的類。JVM 動態地決定哪一個方法被調用,具體係基於真實的對象調用了變一個方法。

Note 注意,如果 getArea 方法沒有被定義在 GeometricObject 類,你就不能定義比較兩個幾何對象是否有相同的面積的 equalArea 方法。你已經見到定義個抽象方法係“共同”類的好處。

13.2.2 Interesting Points about Abstract classes

如下幾點關於抽象類 is worth noting 是值得注意的

  • 抽象方法不能包含在非抽象類度。如果一個子類屬於抽象父類,未能實現所有的抽象方法,那麼子類必須成抽象。換句話說,在一個非抽象子類,個類延伸於抽象父類,所有的抽象方法必須被實現。也要注意抽象方法不能係靜態
  • 使用 new 操作符不能初始化一個抽象類,但係你仍然可以定義佢嘅構造器,某某被調用在子類的構造器上。例如,GeometricObject 的構造器會被調用在 CircleRectangle 類裡面。
  • 一個類包含抽象方法,一定要係抽象。但係可以定義一個抽象類不包含任何抽象方法。 這個抽象類可以作爲定義後面子類的基礎類。
  • 一個子類可以覆蓋來自父類類的方法,子類定義個方法成抽象。這樣是非常不正常,但是當實現父類的方法成爲無效,就幾有用。這種情況,子類必須定義成抽象。—— 實體方法被覆蓋成抽象
  • 一個子類可以是抽象,even if 即使 父類是真實方法。例如,object 類是實體方法,但是它的子類 GeometricObject 是抽象。
  • 你不能使用 new 操作符從抽象類創建一個實例,但是一個抽象類可以作爲一個抽象類。因此,如下的語句,創建一個數組,數組的元素是 GeometricObject 類。
    1
    GeometricObject[] objects = new GeometricObject[10];
  • 你可以創建一個實例, GeometricObject 的實例 (Circle),並且 assign a to b 將a配給b 將 Circle 的引配到數組上。
    1
    object[0] = new Circle();

13.2 Check Point p1506

1.

13.3 Case Study: the Abstract Number Class

Number 是一个抽象的父类,是数组容器类 BigIntegerBigDecimal 的抽象父类。

Section 10.7 引入了數字 wrapper 封裝器/包裝紙 類。 Section 10.9 引入了 BigIntegerBigDecimal 類。這些類有共同的方法 byteValue() , shortValue(), intValue(), longValue(), floatValue()doubleValue() 用於返回 byte, short, int, long, float, 和 double 值,返回的這些值都是來自這些類。這些共同方法是定義入 Number 類,這個類是數字封裝類 BigIntegerBigDecimal 的父類。

1
doubleValue() <-- Double

Number 類是一個抽象父類, Double , Float , Long , Integer , Short , Byte , BigIntegerBigDecimal 的父類。

因为 intValue() , longValue() , floatValuedoubleValue() 方法不能被实现在 Number 类里,所以這些方法在 Number 類裡面定義成抽象方法。 Number 類因此是抽象方法。byteValue()shortValue() 方法由 intValue() 方法實現:

1
2
3
4
5
6
7
public byte byteValue() {
return (byte)intValue();
}

public short shortValue() {
return (short)intValue();
}

Number 定義成數字類的父類,可以定義多個方法去 perform執行 數字的多個共有操作。下面畀出一個程式去找一列 Number 對象中最大的數字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import java.util.ArrayList;
import java.math.*;

// The largest number is 312312321

public class LargestNumber {
public static void main(String[] args) {
// create an array list
ArrayList<Number> list = new ArrayList<>();
// add number to list
// 个45会自动转换成Integer对象
list.add(45);
// 会自动转换成Double
list.add(3445.53);
// add a BigInteger
list.add(new BigInteger("312312321"));
// add a BigDecimal
list.add(new BigDecimal("2.7321973921"));
System.out.println("The largest number is " + getLargestNumber(list));
}

public static Number getLargestNumber(ArrayList<Number> list) {
if (list == null || list.size() == 0)
return null;
Number number = list.get(0);
for (int i = 0; i < list.size(); i++) {
if (number.doubleValue() < list.get(i).doubleValue())
number = list.get(i);
}
return number;
}
}

// The largest number is 312312321

个程序创建了一个数组,一个数组有多个 Number 对象。个数组加上一个 Integer 对象, Double 对象,一个 BigInteger 对象,和一个 BigDecimal 对象到个数组。

调用了 getLargestNumber 方法,个方法返回了最大的数子。个 getLargestNumber 方法返回了 null,如果个列表是 null 或者个列表的大细是0。去找出列表最大的数,个数字通过调用 doubleValue() 方法去比较。个 doubleValue 方法是定义在 Number 类里面,并且在 Number 的实现子类里实现。如果一个数是一个 Integer 对象,那么 IntegerdoubleValue 会被调用。如果数是 BigDecimal 对象,就调用 BigDecimaldoubleValue()

如果 doubleValue() 方法没有被定义在 Number 类里,你就不可以在多个不同类型的数字中找到最大的数。

13.4 Case Study: Calendar and GregorianCalendar

GregorianCalendar 是一个抽象类 Calendar 的 实体子类。

java.util.Date 的实例代表一个特定的实例,实例是以时间的形式,带有 millisecond 毫秒 的精度。 java.util.Calendar 是一个抽象的基础类,提取日历信息的基础类,例如:月,日,小时,分,同秒。 Calendar 的子类能够实现特定的日历系统,例如 Gregorian calendar,阴历,犹太历。目前,Gregorian 日历的 java.util.GregorianCalendar 是在java被支持的。 add 方法 在 Calendar 类中是抽象的,因为它的实现是基于特定实体日历系统。

13.5 Interface 接口

一个接口像一个类构造器,用于定义多个对象的共同操作。

『在多个方面 in many ways』,一个接口类似抽象类,但是 意图intent 是去规定多个相关或不相关类的共同行为。例如,使用多个接口,去规定对象是否可以comparable 比较可食用的 edible [ˈedəbl]cloneable 复制

distinguish sth from sth 比较一个接口和类,Java 使用这样的语法去定义接口:

1
2
3
4

modifier interface InterfaceName {
...
}

Ex.

1
2
3
4
public interface Edible {
/** describe how to eat */
public abstract String howToEat();
}

一个接口在 Java 中就当作「特殊的类」。每个接口编译成独立的二进制文件,和一般类一样。用接口 『more or less 几乎』 同用抽象类差唔多。例如你用接口当引用變量的数据类。『如同一樣 as with…』抽象類,你不能利用 new 從一個接口創建一個實例。

可以使用 Edible 接口去規定一個對象是否可以食。對象『執行Implement』接口類使用 implements 關鍵字。例如,「雞」類同「水果」類都會執行「可食用」接口。類與接口的關係『be know as 畀認爲係』『接口繼成Interface Inheritance』。因爲接口繼成同類繼成基本都一樣,都統一『refer to指的是』繼成。

接口定義了多個對象的共同行爲。

注意:在接口裡,數據場的『首飾器Modifier』public static void 和方法的首飾器 public abstract 都可以忽略。
下面係相等:

1
2
3
4
public interface T {
public static final int k = 1;
public abstract void p();
}

等於

1
2
3
4
public interface T {
int K = 1;
void p();
}

雖然 public 首飾器可以在接口裡忽略,但是當那個方法在子類實現,那個方法依然要是 public

注意:默認方法 Default Method

Java 8 引入默認接口方法,通過使用 default 關鍵字。一個默認方法提供一個默認實現。一個類執行一個接口,可以使用方法的默認實現,或者通過新執行去覆蓋默認。這個功能使你可以添加新方法到已經存在的接口,利用默認實現,不用重寫存在的多個類執行接口的代碼。

Java 8 又允許『公開靜態方法Public Static Method』存在在接口。公開靜態方法在接口中使用和普通方法一樣目的。

1
2
3
4
5
6
7
8
9
public interface A {
public default void doSomething() {
System.out.println("Do sth");
}

public static int getAValue() {
return 0;
}
}

Check Point

  1. 不能使用 new A() 来创建接口。
  2. 可以用接口来创建参变量 A x;
  3. 接口正確?不正確?
    正確
    1
    2
    3
    interface A {
    void print();
    }
    不正確
    1
    2
    3
    4
    // interface 裡面的方法是抽象,不帶具體實現{};
    interface A {
    void print() {};
    }
    1
    2
    3
    abstract interface A {
    abstract void print() {};
    }
    1
    2
    3
    4
    // 不能直接 print() 而不表明 void
    abstract interface A {
    print();
    }
    1
    2
    3
    interface A {
    default void print() { }
    }
    1
    2
    3
    4
    5
    interface A {
    static int get() {
    return 0;
    }
    }
  4. 解釋:
    • All methods defined in an interface are public. 「所有」接口方法是公共
    • When a class implements the interface, the method must be declared public. 實現方法必須是公共
    • public 可見性不能忽略

13.6 The Comparable Interface

这个 Comparable 接口定义了用来比较多个对象的方法 compareTo

假设你希望设计一个通用的方法去找相同类的两个对象中更大的一个。例如两个学生、两个日期、两个圆、两个长方形。所以,两个对象必须是可比较,两个对象的共同行为必须是可比较的。 Java 提供了 Comparable 接口去做呢件事。

1
java.lang.Comparable
1
2
3
4
5
6
// 比较多个对象的接口,这个定义在 java.lang 里面
package java.lang;

public interface Comparable<E> {
public int comparaTo(E o);
}

compareTo 方法决定了一个有对象 o 的对象的顺序,小于 o 会返回负,等于系0,大于系正数。

Comparable 接口是一个通用接口。當實現這個接口時候,通用类 E 係會被實體類去替代。好多 Java 庫的類都實現了 Comparable 去定義對象中的順序。類如 Byte , Short , Integer , Long , Float , Double , Character , BigInteger , BigDecimal , Calendar , StringDate 都實現了 Comparable 接口。

例如 Integer , BigInteger , String , Date 類在 API 中定義了如下:

1
2
3
4
5
6
7
8
9
10
// Integer
// 名 延伸 抽象類 實現 接口<Integer>
public final class Integer extends Number implements Comparable<Integer> {
// 具體忽略

@Override
public int compareTo(Integer o) {
// 具體忽略
}
}
1
2
3
4
5
6
7
8
9
10
// BigInteger
// !!! !!!
public class BigInteger extends Number implements Comparable<BigInteger> {
// 具體忽略

@Override
public int compareTo(BigInteger o) {
// 具體忽略
}
}
1
2
3
4
5
6
7
8
9
10
// String 
// !!! !!!
public final class String extends Object implements Comparable<String> {
// 具體忽略

@Override
public int compareTo(String o) {
// 具體忽略
}
}
1
2
3
4
5
6
7
8
9
10
// String 
// !!! !!!
public class Date extends Object implements Comparable<Date> {
// 具體忽略

@Override // !!!
public int compareTo(Date o) {
// 具體忽略
}
}

你可以使用 compareTo 方法去比較兩個數字,兩個字符等等。
Ex:

1
2
3
4
System.out.println(new BigInteger("2312313").compareTo(new BigInteger("5")));
// 1 意思就是 大於
System.out.println("ABC".compareTo("ABC"));
// 0 等於

s 是一個 String 對象,下面都是事實:

1
2
3
s instanceof String
s instanceof Object
s instanceof Comparable

因爲所有的 Comparable 對象都有 compareTo 方法,所以 java.util.Arrays.sort(Object []) 方法使用 compareTo 方法去比較同排序數組的對象。放落去的 object 對象都是 Comparable 接口的實例。

下面是排序字符串數組的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// SortComparableObjects.java
import java.math.*;

public class SortComparableObjects {
public static void main(String[] args) {
String[] cities = {"GZ", "SZ", "ZH"};
java.util.Arrays.sort(cities);
for (String city: cities) {
System.out.print(city + " ");
}
System.out.println();
BigInteger[] hugeNumbers = {new BigInteger("23231"),
new BigInteger("232"),
new BigInteger("3211")};
java.util.Arrays.sort(hugeNumbers);
for (BigInteger number: hugeNumbers)
System.out.print(number + " ");
}
}
1
2
GZ SZ ZH
33 232 3211 23231

不能用 sort 方法去排序多個長方形的數組。因爲 Rectange 沒有實現 Comparable 。但是,你可以定義一個新的長方形類,個長方形類係實現了 Comparable 。這個新類的實例是可以比較。

這裏建一個新類 ComparableRectangle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// ComparableRectangle.java

public class ComparableRectangle extends Rectangle
implements Comparable<ComparableRectangle> {

public ComparableRectangle(double width, double height) {
super(width, height);
}

@Override // 實施 Comparable 的 comparaTo 方法
public int compareTo(ComparableRectangle o) {
if (getArea() > o.getArea())
return 1;
else if (getArea() < o.getArea())
return -1;
else
return 0;
}

@Override // 實施 GeometricObject 的 toString 方法
public String toString() {
return super.toString() +
" Area: " + getArea();
}

}

ComparableRectangle 延伸 Rectangle 並且實施咗 Comparable 。關鍵字 implements 說明了 ComparableRectangle 繼承 Comparable 接口的所以常量和方法。 compareTo 方法比較了兩個長方形的面積。 ComparableRectangle 的實例也是 RectangleGeometricObjectObjectComparable 的實例。

注意
接口和它的方法的名字都是 斜體
java.lang.Comparable
compareTo(o: ComparableRectangle): int
用虛線空心箭頭去指向接口

ComparableRectangle 延伸 Rectangle 和 實施了 Comparable

你可以使用 sort 方法去排序多個 ComparableRectangle 對象的數組:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

// SortRectangles.java
public class SortRectangles {
public static void main(String[] args) {
ComparableRectangle[] rectangles = {
new ComparableRectangle(3.4, 5.4),
new ComparableRectangle(13.24, 55.4),
new ComparableRectangle(7.4, 35.4)};
java.util.Arrays.sort(rectangles);
for (Rectangle rectangle: rectangles) {
System.out.print(rectangle + " ");
System.out.println();
}
}
}

/*

⇒ java SortRectangles
created on Fri Jan 31 21:22:41 CST 2020
color: white and filled: false Area: 18.36
created on Fri Jan 31 21:22:41 CST 2020
color: white and filled: false Area: 261.96
created on Fri Jan 31 21:22:41 CST 2020
color: white and filled: false Area: 733.496

*/
  • 一个接口提供通用编程的另外一种形式。
  • 如果不使用接口,可能会有些困难去使用通用排序 sort 方法去排序那些对象。
  • 因为多「重继承係」係好必要去繼承 Comparable 同另外一些類,例如 Rectangle
  • object 類包含了 equal 方法,這個方法是被子類所繼承,子類能夠覆蓋。
  • 這樣就能夠比較是否多個對象是相等的。
  • 假設 Object 類包含了 compareTo 方法,『as由於』compareTo 定義在 Comparable 接口裡,所以 sort 方法能夠被使用,來比較一組對象。
  • 是否一個 compareTo 方法應該被包含在 Object 類裡,仍然在爭論中。
  • 因爲 compareTo 方法在 Object 類中沒有定義,如果多個對象都繼承 Comparable 接口,那麼這個 Comparable 接口使多個對象能夠相互比較。
  • compareTo 應該同 equals 『一樣 be consistent with』。
  • 如果 o1.equals(o2) 係真,那麼 o1.compareTo(o2) == 0
  • 如果兩個有相同面積,你應該覆蓋 ComparableRectangleequals 方法返回 true

Check Point

  1. 如果一個類 實施implement 咗 Comparable , 那麼這個類的實例可以有 compareTo 方法。True!
  2. public int compareTo(String o) is for compareTo mehtod in the String cl
  3. 如果他們的共同類,或者共同父類,都有 compareTo ,先可以比較,否則 error。
  4. 要 implements 咗 Comparable 接口先可以定義 compareTo 方法。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 實施 Comparable<類名>
    public class ComparableRectangle extends Rectangle
    implements Comparable<ComparableRectangle> {

    @Override
    public int compareTo(ComparableRectangle o) {
    // ...
    }
    }
  5. A: Person 類沒有 implement Comparable 接口,不可以 sort 。

13.7 The Cloneable Interface

  • Cloneable 接口 規定了 那個 對象 是 可以 複製的。
  • 創建 一個 對象 的 副本 是 『desirable想有的』。
  • 你需要使用 clone 方法 並且 明白 Cloneable 接口。
  • 雖然 一個 接口 包含 常量 同 抽象方法,但是 Cloneable 接口 是 一個特殊例子。
  • Cloneable 接口定義在 java.lang.Cloneable
1
2
3
4
5
package java.lang;

public interface Cloneable {

}
  • 個接口是空的。
  • 接口無內容,『被叫做是個 is referred to as a』marker interface 標記接口。
  • 一個 標記接口 用來『表示denoet』類『擁有 possess [pəˈzes]』特定的並且想有的屬性。
  • 一個 類 實施咗 Cloneable 接口 就標記成 可複製。
  • Clone() 方法已經定義在 Object 類中,這個類的對象可以被複製,通過 Clone() 方法做到。
  • Java 許多類,例如 Date ,就實施了 Cloneable
  • 因此,這些類都是可 複製的。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    import java.util.*;

    public class DateClone {

    public static void main(String[] args) {
    Calendar calendar = new GregorianCalendar(2013, 2, 1);
    Calendar calendar1 = calendar;
    Calendar calendar2 = (Calendar)calendar.clone();
    System.out.println("calendar = calendar1 is " +
    (calendar == calendar1));
    System.out.println("calendar = calendar2 is " +
    (calendar == calendar2));
    System.out.println("calendar.equals(calendar2) is " +
    calendar.equals(calendar2));

    }
    }

    /*

    ⇒ java DateClone
    calendar = calendar1 is true
    calendar = calendar2 is false
    calendar.equals(calendar2) is true

    */
  • 『前面的preceding』代碼, Calendar calendar1 = calendar; 複製了 calendar 的地址到 calendar1
  • 所以 calendarcalendar1 都指向相同的 Calendar 對象。
  • Calendar calendar2 = (Calendar)calendar.clone(); 創建新的對象,個對象是原來 calendar 的副本,並且將新的副本的地址分配給 calendar2
  • 所以 calendar2calendar 是不同的對象,雖然有着相同的內容。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// ArrayTest.java
import java.util.*;

public class ArrayTest {
public static void main(String[] args) {
ArrayList<Double> list1 = new ArrayList<>();
list1.add(1.5);
list1.add(2.5);
list1.add(3.5);
ArrayList<Double> list2 = (ArrayList<Double>)list1.clone();
ArrayList<Double> list3 = list1;
list2.add(4.5);
list3.remove(1.5);
System.out.println("list1 is " + list1);
System.out.println("list2 is " + list2);
System.out.println("list3 is " + list3);
}
}

/*

⇒ java ArrayTest
list1 is [2.5, 3.5]
list2 is [1.5, 2.5, 3.5, 4.5]
list3 is [2.5, 3.5]

*/
  • 在上面的代码
  • ArrayList<Double> list2 = (ArrayList<Double>)list1.clone();list1 的副本地址去到 list2
  • list1list2 是不同的对象,虽然内容相同。
  • ArrayList<Double> list3 = list1 表明 3 同 1 都是指向相同的对象。
  • 所以 1 同 3 都会同时小咗 1.5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// List2.java

public class List2 {
public static void main(String[] args) {
int[] list1 = {1, 2};
int[] list2 = list1.clone();
list1[0] = 7;
list2[1] = 8;
System.out.println("list1 is " + list1[0] + ", " + list1[1]);
System.out.println("list2 is " + list2[0] + ", " + list2[1]);
}
}

/*

⇒ java List2
list1 is 7, 2
list2 is 1, 8

*/
  • 数组的 clone() 的返回類是同複製前一樣。
  • list1.clone() 的返回類是 int[] ,因爲 list1int[]
  • 去定義一個自定義的類,這個類實施了 Cloneable 接口,這個類必須要『覆蓋override』 Object 類的 clone() 方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// House.java

public class House implements Cloneable, Comparable<House> {
private int id;
private double area;
private java.util.Date whenBuilt;

public House(int id, double area) {
this.id = id;
this.area = area;
whenBuilt = new java.util.Date();
}

public int getId() {
return id;
}

public double getArea() {
return area;
}

public java.util.Date getWhenBuilt() {
return whenBuilt;
}

@Override
public Object clone() {
try {
return super.clone();
}
catch
return null;
}

}

@Override
public int compareTo(House o) {
if (area > o.area)
return 1;
else if (area < o.area)
return -1;
else
return 0;
}
}
  • House 類實施了 clone 方法,個 clone 方法原來定義在 Object 類中。
  • clone 方法的 頭部 定義在 Object 類中是這樣
    1
    protected native Object clone() throws CloneNotSupportedException;
  • 個關鍵字 native 說明了這個方法不是用 Java 寫的,而是用 JVM 去實施的。
  • 個關鍵詞 protected 限制了這個方法,只可以被相同 Package 包 或者 子類 去訪問。
  • House 類必須覆蓋某些方法,並且改變「修改器modifier」的可見度,這樣那個方法先可以用在任何包中。
  • 因爲 clone 方法實現在 Object 類裡,去做對象複製,所以 Houseclone 方法簡單就用 super.clone() 就可以喇。
  • clone 方法定義在 object 類裡,如果個對象不是「可複製的cloneable」,就會跳出 CloneNotSupportedException
  • 因爲我們已經做了「錯誤」捕捉在原生,所以無必要在這裡做。
  • House 類實現了 Comparable 接口的 compareTo 方法。個方法可以比較兩個對象。
  • 你現在可以創建一個對象 House ,並且創建副本:
    1
    2
    House house1 = new House(1, 1750.50);
    House house2 = (House)house1.clone();
  • house1house2 是不同兩個對象,是有着一樣的內容。
  • Object 類的 Clonel 方法复制每个域到目标对象。
  • 如果個域係「原始的primitive」類,那麼值就會被複製。
  • area(double) 的值會從 house1 複製到 house2 。
  • 如果個域是一個對象,那麼域的地址會被複製。
  • 例如 whenBuilt 是屬於 Date ,它的地址會複製到 house2
  • 儘管 house1==house2 是錯的,但是 house1.whenBuilt == house2.whenBuilt 是真的!
  • 這就叫『淺Shallow』複製。不是深複製。如果個域是對象,只會複製地址,不是常量。
  • 去執行「深度複製」
  • 要改下 clone 方法。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // House.java

    public class House implements Cloneable, Comparable<House> {
    ....

    @Override
    public Object clone() {
    try {
    House houseClone = (House)super.clone();
    // clone 目標對象是深度複製,對象裡的對象是淺複製
    houseClone.whenBuilt =
    (java.util.Date)(WhenBuilt.clone());
    return houseClone;
    }
    catch
    return null;
    }

    }
    }

用下面的代碼複製 House 對象:

1
2
House house1 = new House(1, 750.50);
House house2 = (House)house1.clone();

house1.whenBuilt == house2.whenBuilt 就是 false 喇。

  • 關於 clone 方法 和 Cloneable 接口有多個問題
  • 1st, 爲什麼 Objectclone 方法是被「保護的protected」,而不是共用「public」。
    • 不是每個對象都可以被複製。
    • 如果子類生成的對象可以複製,Java 設計師專登強制子類要去「覆蓋Override」。
  • 2nd, 爲什麼 clone 方法不定義在 Cloneable 接口中?
    • Java 提供一個原生的方法去執行淺複製。
    • 因爲接口的方法是「抽象 abstract」,所以原生方法不能在接口中實現。
    • 因此,設計師決定定義並且實現個 clone 方法在 Object 中。
  • 3rd, 爲什麼 Object 類不實現 Cloneable 接口?
    • 同1st。
  • 4th, 如果 House 類不實現 Cloneable ,會發生什麼?
    • house1.clone() 會返回 null
    • 因爲 super.clone() 會掟出 CloneNotSupportedException
  • 5th, 你可以在 House 類 實現 clone 方法,但是又唔用 clone 方法。
    1
    2
    3
    4
    5
    6
    public Object clone() {
    House houseClone = new House(id, area);
    houseClone.whenBuilt = new Date();
    houseClone.getWhenBuilt().setTime(whenBuilt.getTime());
    return houseClone;
    }
  • 在這個例子中, house 類不需要實現 Cloneable 接口。
  • 你必須確保所有數據都要正確複製。
  • 使用 Object 類中 clone() 方法『減輕你從relieve you from』手動複製收據的工作。
  • Objectclone 方法會自動地執行淺複製所以收據域。

Check Point

  1. 如果一个类不实施 java.lang.Cloneable ,類能不能用 super.clone()
    • 不能用 super.clone()
    • Date 類實施了 Cloneable
    • 如果不在類裡面覆蓋 clone() ,因爲java.lang.Object 是受保護的,直接使用係個語法錯誤。
    • 如果不實施 java.lang.Cloneable 係一個run time error,意思就係用時候會返回 null 。
    • == 號驗證兩個是否指向一個對象
    • xx.equals.aa 驗證內容是否相同
    • = 指向同一個對象
    • list.clone() 意味着全新的對象
  2. 要用 xxx.clone() 1)要實施 java.lang.Cloneable 2)寫override clone()

13.8 Interface vs. Abstract Classes

  • 一個類,可以實施多個接口,只可以延伸一個父類。

  • 一個接口同抽象類「幾乎more or less」相同地使用。

  • 但是接口同抽象類的定義不一樣

    X 變量 構建器Constructor 方法
    抽象類 冇限制 Constructor 純粹加入給子類,不能初始化 冇限制
    接口 所以變量必須public static final 沒有constructor,不能初始化 public abstract instance, public default, and public static
  • Java 只允許類延伸的單獨繼承
  • 但是允許多重接口多重延伸
1
2
3
4
public class NewClass extends BaseClass
implements Interface1, Interface2,.., InterfaceN {

}
  • 一個接口可以通過 extends 繼承另一個接口。
  • 這個接口叫做 子接口subinterface
1
2
3
public interface NewInterface extends Interface1, Interface2, Interface3 {
// constants and abstract methods
}
  • 一個類實施 NewInterface 必須實施來自 NewInterface, Interface1 , Interface2 , Interface3 的抽象方法。
  • 一個接口可以延伸多個接口,但係不能延伸多個類
  • 一個類係可以延伸多個類(父類)和實施多個接口。
  • 所以的類共享同一個根,叫 Object
  • 但是,沒有多個接口的唯一根
  • 一個接口類的變量可以「參考 Reference」任何實施了這個接口的類所生成的實例。
  • 如果一個類實施了一個接口,個接口就如同這個類的父類
  • 你能把接口當成數據類,接口可以「投射 cast」接口的變量到它的子類。
  • 例如 c 是最上層 class2 的實例,那麼也同時是 Object , class1 , Interface1_2 interface1_1 , interface1 , …. 的實例。

命名規則
類的名字是名詞。接口的名字一般是形容詞或者名詞。

設計說明

  • 抽象類 和 接口 都可以用去規定多個對象的共同行爲。
  • 如何決定使用 接口 和 類。
  • 通常嚟講,「是一個关系 is-a relationship」清楚地描述父子关系
  • 父子关系用多个类来展示
  • Gregorian calendar 是一个 calendar
  • java.util.GregorianCalendarjava.util.Calendar 的关系通过类继承来「展示 model」。
  • 一个弱「是一关系」,说明一个对象「possess拥有」一个特定的属性
  • 一个弱「是一关系」,可以通过使用接口来表示。
  • String 类实施了 Comparable 接口,所以所有的字符串系可比较的。

接口更推荐使用,而不是抽象类
因为接口可以定义不相关类的共同父类
接口比一般类更灵活
想想 Animal 類, howToEat 方法定義在 Animal 類中如下。

1
2
3
abstract class Animal {
public abstract String howToEat();
}

動物的子類如下:

1
2
3
4
5
6
class Chicken extends Animal {
@Override
public String howToEat() {
return "Fry it"; // 炸它
}
}
1
2
3
4
5
6
class Duck extends Animal {
@Override
public String howToEat() {
return "Roast it"; // 烤
}
}

「inheritance hierarchy 繼承 層次體系」、「polymorphism多形態性」使你可以有個 Animal 變量指向 Chicken 對象和 Duck 對象的地址。

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) {
Animal animal = new Chicken();
eat(animal);

animal = new Duck();
eat(animal);

public static void eat(Animal animal) {
System.out.println(animal.howToEat());
}
}

基於具體的對象,個對象提及的方法,JVM判斷使用來自哪個對象的 howToEat

你可以定義 Animal 的子類。
但是,有個限制。子類必須其它動物。
另外一個問題出現,如果一個動物是不能食,是不能延伸 Animal 類。

接口不會這些問題。
接口有更多靈活性,因爲你不需要成個接口內容連結到特定類。
你可以定義接口 howToEat() 方法,讓它服務多類。

例如:「Edible interface 可食接口」與 「Chicken Class 雞類」

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// DesignDemo

public class DesignDemo {
public static void main(String[] args) {
Edible stuff = new Chicken();
eat(stuff);

stuff = new Duck();
eat(stuff);

stuff = new Broccoli();
eat(stuff);
}

public static void eat(Edible stuff) {
System.out.println(stuff.howToEat());
}
}

interface Edible {
public String howToEat();
}

class Chicken implements Edible {
@Override
public String howToEat() {
return "Fry it";
}
}

class Duck implements Edible {
@Override
public String howToEat() {
return "Roast it";
}
}

class Broccoli implements Edible {
@Override
public String howToEat() {
return "Stir-fry it"; // 翻炒
}
}

定義一個類,類「represent 代表」可食對象
簡單將類實施 Edible 接口
類現在是 Edible 類的子類, Edible 對象可以用 howToEat 方法。

Check Point

    • 接口編譯成另外獨立的二進制代碼
    • 接口不能有靜態方法
    • 接口可以延伸多個接口
    • 接口不能延伸抽象類
    • 接口可以有默認方法

13.9 Case Study: The Rational有理由的 Class

這個部分顯示了如何設計一個有理類,個類能表達同處理有理數。

一個有理數有「numerator分子」和「denominator分母」,寫成 a/b

有理數分母不能爲0,但係0作爲分子就可以。
整數 1 等於 i/1。
有理數用在準確的計算當中。
意味着, 1/3 = 0.3333333 就不能准确地表达,以 double 或者 float 浮点格式都不能。
去获得准确的结果,我哋必须使用有理数。

Java 提供浮點数据类,但是沒有有理數。
這個部分顯示了如何設計一個類,去表達有理數。

因爲有理數同 Integerfloat-point 有許多共同特點,
並且 Number 是數字包裹器的根類,比較合適就是定義 Rational 作爲 Number 的子類。
因爲有理數是可以比較的,所以 Rational 類應該實施 Comparable 接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
java.lang.Number <-----------------| Rational |
java.lang.Comparable<Rational> <---|----------|

一個有理數有一個分子和分母。
有很多等值的有理數,`1/3 = 2/6 = 3/9 = 4/12`
1/3 的分子分母沒有除了 1 和自身的除數,所以 1/3 叫「lowest term最小項」。
爲得 lowest terms,你需要找到最大公約數(GCD),分子分母絕對值的公約數。

寫一個測試程序去創建兩個有理數和測試它的方法

```java
// TestRationalClass.java

public class TestRationalClass {
public static void main(String[] args) {
Rational r1 = new Rational(4, 2);
Rational r2 = new Rational(2, 3);
System.out.println(r1 + " + " + r2 + " = " r1.add(r2));
System.out.println(r1 + " - " + r2 + " = " r1.subtract(r2));
System.out.println(r1 + " * " + r2 + " = " r1.multiply(r2));
System.out.println(r1 + " / " + r2 + " = " r1.divide(r2));
System.out.println(" r2 is " + r2.doubleValue());
}
}

doubleValue() 定義在 java.lang.Number 裡面,並且在 Rational 中重寫。
如果字符串「is concatenated with 接上」一個對象,而且通過 + 號,這個對象的字符串表達式 toString() 就會被用上。
所以 r1 + " + " + r2 + " = " r1.add(r2) 等同於 r1.toString() + " + " + r2.toString() + " = " r1.add(r2).toString()

p1580

  • is intended for 是专供
  • particular 专门的

p1581

  • immutable 不可改变的
  • limitation 局限 # 区别limit
  • overflow 溢jat出

check point

#2 #3

1
2
3
4
Rational r1 = new Rational(-2, 6);        // Rational 有實現 compareTo方法
Object r2 = new Rational(1, 45); // Object 類是 Rational 父類,但是沒有 compareTo 方法!!!
System.out.println(r2.compareTo(r1)); // 所以 Object 調用 compareTo 方法會爆炸!
System.out.println(r1.compareTo(r2)); // 因爲r1的compareTo方法入參是Rational, Object cannot be converted to Rational

#4

1
2
3
4
5
if ((this.subtract((Rational)(other))).getNumerator() == 0)
return true;
else
return false;
}

其實就係

1
return ((this.subtract((Rational)(other))).getNumerator() == 0);

用 conditional operator 簡化:

1
2
3
4
5
6
7
if (this.subtract(o).getNumerator() > 0)
return 1;
else if (this.subtract(o).getNumerator() < 0)
return -1;
else
return 0;
}

5

  • revise 修正

13.10 Class-Design Guidelines 類設計說明

1585

  • sound 可靠的 adj
  • cohesion 結合 凝聚性
  • entity 實體
  • coherent 有調理的
  • separate 獨立的 adj
  • synchronize 同步v
    p1587
  • as is the case 一直都係
  • encapsulate date field 封裝 數據 域
    p1588
  • achieve 實現
  • clarity 明確性
  • contract 合約
  • incorporate 包含
  • impose 推行

p1589

  • intuitively 直观地 [ɪnˈtjuːɪtɪvli]
  • derive 取得
  • depend on 基於

1590

  • is dependent on 依賴於

錯誤

1
2
3
4
5
6
7
8
9

public class SomeThing {
private int t1;
private static int t2;
public SomeThing(int t1, int t2) {
// 靜態t2應該用靜態方法去更新
...
}
}

應該

1
2
3
4
5
6
7
8
9
10
11
12
private class SomeThing {
private int t1;
private static int t2;
public SomeThing(int t1) {
...
}

// 用靜態方法先更新,可以避免實例后再更新,這是不應該
public static void setT2(int t2) {
SomeThing.t2 = t2;
}
}

1591

  • integral 必要的
  • mistakenly 錯誤地
  • overlook 忽略v
  • is independent of 獨立於

1592

  • aggregation 集合、總量
  • possesse 有

1595

  • construct 概念、觀念
  • in many ways 在好多方面