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
。