天天看點

CS61B sp2018筆記 | Generics and Autoboxing

1. Automatic Conversions

1.1 Autoboxing and Unboxing

Java中的泛型用到了

<>

,當我們執行個體化一個泛型的時候,必須要指明一個确定的類型。

回憶一下,Java有8中基本類型,其他所有的類型都是引用類型。Java的一個特點就是不能把基礎類型當作參數傳遞給泛型,比如說,

ArrayDeque<int>

是一個文法錯誤,正确的寫法是

ArrayDeque<Integer>

對于每一種基本類型,都有一種引用類型與之對應,這些引用類型稱為

包裝類(wrapper classes)

CS61B sp2018筆記 | Generics and Autoboxing

我們假定基本類型和包裝類之間沒有類型轉換,這時如果我們要使用一個泛型的資料結構,就不得不這樣做:

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

繼續閱讀