1. Automatic Conversions
1.1 Autoboxing and Unboxing
Java中的泛型用到了
<>
,當我們執行個體化一個泛型的時候,必須要指明一個确定的類型。
回憶一下,Java有8中基本類型,其他所有的類型都是引用類型。Java的一個特點就是不能把基礎類型當作參數傳遞給泛型,比如說,
ArrayDeque<int>
是一個文法錯誤,正确的寫法是
ArrayDeque<Integer>
。
對于每一種基本類型,都有一種引用類型與之對應,這些引用類型稱為
包裝類(wrapper classes)
。
我們假定基本類型和包裝類之間沒有類型轉換,這時如果我們要使用一個泛型的資料結構,就不得不這樣做:
public class BasicArrayList {
public static void main(String[] args) {
ArrayList<Integer> L = new ArrayList<Integer>();
L.add(new Integer());
L.add(new Integer());
/* Use the Integer.valueOf method to convert to int */
int first = L.get().valueOf();
}
}
幸運的是,Java為基本類型和包裝類之間提供了隐式的轉換,是以我們可以寫出這樣的代碼:
public class BasicArrayList {
public static void main(String[] args) {
ArrayList<Integer> L = new ArrayList<Integer>();
L.add();
L.add();
int first = L.get();
}
}
這種轉換稱為
box
和
unbox
,也就是
Auto-box(自動裝箱功能)
。
對自動裝箱功能有幾個需要注意的地方:
- 數組沒有自動裝箱功能,比如,你有一個整數類型的數組
,它将不會自動轉換成int[] x
類型。Integer[]
- 自動裝箱功能會減慢程式運作的速度。
- 包裝類會占據更大的記憶體空間。
1.2 Widening
還有一種自動轉換是Java基本類型之間的轉換,記憶體小的類型會自動轉換成記憶體大的類型,比如說我們有這樣一個函數:
public static void blahDouble(double x) {
System.out.println(“double: “ + x);
}
我們可以這樣調用它:
int x = 20;
blahDouble(x);
不過要把記憶體大的類型轉換成記憶體小的類型,就必須進行強制類型轉換:
public static void blahInt(int x) {
System.out.println(“int: “ + x);
}
我們需要這樣調用它:
double x = ;
blahInt((int) x);
2. Immutability
資料類型分為
可變型(mutable)
和
不變型(immutable)
。
一個不變的資料類型它的
執行個體
任何情況下都不會發生改變。比如說,Java中的
String
類型就是不變的,一旦字元串被确定,它就再也不會改變,即使想對它進行一些改變,也要傳回一個新的字元串。
資料類型的不變性使用
final
關鍵字定義,比如說下面這個Date類:
public class Date {
public final int month;
public final int day;
public final int year;
private boolean contrived = true;
public Date(int m, int d, int y) {
month = m; day = d; year = y;
}
}
當執行個體化一個
Date
類後,就無法再改變它的屬性了。
我們再來看一個特殊的例子:
public final ArrayDeque<String>() deque = new ArrayDeque<String>();
變量deque被定義為final代表它不會進行再次配置設定,但是我們知道deque裡面存儲的是引用,引用不能改變,但是引用所指向記憶體中的内容是可以改變的,是以說
ArrayDeque永遠都是可以改變的
。
3. Generics
3.1 Creating Another Generic Class
在此之前,我們已經建立了一些泛型連結清單,下面讓我們建立一種新的資料類型——
maps
。
我們将建立
ArrayMap
類,它實作
Map61B
接口,這個接口目前有以下幾個基本方法:
put(key, value): Associate key with value.
containsKey(key): Checks if map contains the key.
get(key): Returns value, assuming key exists.
keys(): Returns a list of all keys.
size(): Returns number of keys.
為了快速建立模型,我們忽略記憶體的重新配置設定問題,需要說明一下,我們的類中
一個key隻能對應一個value
,下面是它的實作:
package Map61B;
import java.util.List;
import java.util.ArrayList;
/***
* An array-based implementation of Map61B.
***/
public class ArrayMap<K, V> implements Map61B<K, V> {
private K[] keys;
private V[] values;
int size;
public ArrayMap() {
keys = (K[]) new Object[];
values = (V[]) new Object[];
size = ;
}
/**
* Returns the index of the key, if it exists. Otherwise returns -1.
**/
private int keyIndex(K key) {
for (int i = ; i < size; i++) {
if (keys[i].equals(key)) {
return i;
}
return -;
}
public boolean containsKey(K key) {
int index = keyIndex(key);
return index > -;
}
public void put(K key, V value) {
int index = keyIndex(key);
if (index == -) {
keys[size] = key;
values[size] = value;
size += ;
} else {
values[index] = value;
}
}
public V get(K key) {
int index = keyIndex(key);
return values[index];
}
public int size() {
return size;
}
public List<K> keys() {
List<K> keyList = new ArrayList<>();
for (int i = ; i != size; i++) {
keyList.add(keys[i]);
}
return keyList;
}
}
這個map基于array數組,它不是最好的,但是可以用來說明一些問題。
我們寫一個測試類來測試一下:
@Test
public void test() {
ArrayMap<Integer, Integer> am = new ArrayMap<Integer, Integer>();
am.put(, );
int expected = ;
assertEquals(expected, am.get());
}
你将會發現報了一個運作時錯誤:
$ javac ArrayMapTest.java
ArrayMapTest.java:11: error: reference to assertEquals is ambiguous
assertEquals(expected, am.get(2));
^
both method assertEquals(long, long) in Assert and method assertEquals(Object, Object) in Assert match
錯誤的原因是JUnit的
assertEquals
方法重載了,我們隻需要把最後一行代碼改為
assertEquals((Integer)expected, am.get(2));
,明确調用的是
assertEquals(Object, Object)
方法即可。
3.2 Generic Methods
接下來我們将建立一個新的類
MapHelper
,它有以下兩個方法:
- get(Map61B, key):根據給出的key值傳回Map61B中對應的value,如果key不存在的話,傳回null
- maxKey(Map61B):傳回Map61B中key的最大值。
3.2.1 Implementing get
在這個類中,我們沒有在類名的後邊聲明這個類是一個泛型,而是在動議每個方法時聲明,是以get方法就會是這樣:
public static <K,V> V get(Map61B<K,V> map, K key) {
if map.containsKey(key) {
return map.get(key);
}
return null;
}
我們可以這樣使用它:
ArrayMap<Integer, String> isMap = new ArrayMap<Integer, String>();
System.out.println(mapHelper.get(isMap, ));
3.2.1 Implementing maxKey
一開始實作這個方法的時候你可能會這樣寫:
public static <K, V> K maxKey(Map61B<K, V> map) {
List<K> keylist = map.keys();
K largest = map.get();
for (K k: keylist) {
if (k > largest) {
largest = k;
}
}
return largest;
}
不過很快你将發現一些問題,K類型之間的比較發生了錯誤,這就意味着我們要考慮對象的比較規則,是以要實作
Comparable
接口:
public static <K extends Comparable<K>, V> K maxKey(Map61B<K, V> map) {
List<K> keylist = map.keys();
K largest = map.get();
for (K k: keylist) {
if (k.compareTo(largest)) {
largest = k;
}
}
return largest;
}
可能你對這個函數的定義有些困惑,特别是傳回類型
<K extends Comparable<K>, V> K
這一部分。
首先
Comparable
接口本身就是一個範式的接口,是以我們必須指明我們要比較的類型,也就是在Comparable後面加
<K>
。
那麼為什麼不用
implements
反而使用
extends
呢?其實,這裡的extends有了不一樣的含義。當我們說
K extends Comparable
時,僅僅意味着K必須可以比較,extends不同種的使用方法稱為
type upper bounding
。