1 泛型类

1
class className<T1, T2, ..., Tn> { /* ... */ }
  • className:原型
  • <T1, T2, ..., Tn>:类型参数

单类型参数的泛型类

1
2
3
4
5
6
7
8
9
10
11
public class Box<T> {
private T value;

public void setValue(T value) {
this.value = value;
}

public T getValue() {
return value;
}
}
1
2
3
4
5
6
7
8
9
Box<Integer> intBox = new Box<>();
intBox.setValue(10);
System.out.printf("intBox: %d\n", intBox.getValue());
Box<String> strBox = new Box<>();
strBox.setValue("Hello");
System.out.printf("strBox: %s\n", strBox.getValue());

intBox: 10
strBox: Hello

多类型参数的泛型类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Box<K, V> {
private K key;
private V value;

public void setKey(K key) {
this.key = key;
}

public K getKey() {
return key;
}

public void setValue(V value) {
this.value = value;
}

public V getValue() {
return value;
}
}
1
2
3
4
5
6
7
8
Box<Integer, String> box = new Box<>();
box.setKey(10);
box.setValue("Hello");
System.out.printf("box.key: %d\n", box.getKey());
System.out.printf("box.value: %s\n", box.getValue());

box.key: 10
box.value: Hello

2 泛型接口

1
public interface interfaceName<T> { /* ... */ }

两种实现方式。

  1. 实现接口的子类明确声明泛型类型
1
class GenericsInterface implements interfaceName<Interger>
  1. 实现接口的子类不明确声明泛型类型
1
class GenericsInterface<T> implements interfaceName<T>

3 泛型方法

1
<T1, T2, ..., Tn> returnType methodName(T1 t1, ..., Tn tn){ /* ... */ }

例:定义泛型方法,用于比较实现了Comparable接口的对象。

1
2
3
public interface Comparable<T> {
public int compareTo(T o);
}
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
public static void main(String[] args) {
List<Integer> integers = new ArrayList<>();
integers.add(1);
integers.add(2);
integers.add(3);
System.out.println(maxInList(integers));
List<String> strings = new ArrayList<>();
strings.add("a");
strings.add("b");
strings.add("c");
System.out.println(maxInList(strings));
}
private static <T extends Comparable<T>> T maxInList(List<T> list) {
if (list.isEmpty()) {
throw new IllegalArgumentException("List is empty");
}
T max = list.get(0);
for (T item : list) {
if (item.compareTo(max) > 0) {
max = item;
}
}
return max;
}

3
c

关于 IntegerString 对于 CompareTo<T> 接口的实现:

1
2
3
4
5
6
7
public int compareTo(Integer anotherInteger) {
return compare(this.value, anotherInteger.value);
}

public static int compare(int x, int y) {
return (x < y) ? -1 : ((x == y) ? 0 : 1);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public int compareTo(String anotherString) {
int len1 = value.length;
int len2 = anotherString.value.length;
int lim = Math.min(len1, len2);
char v1[] = value;
char v2[] = anotherString.value;
int k = 0;
while (k < lim) {
char c1 = v1[k];
char c2 = v2[k];
if (c1 != c2) {
return c1 - c2;
}
k++;
}
return len1 - len2;
}

4 上下界 & 通配符

当定义泛型类型时,默认是没有任何限制的,意味可以是任何类型。

但是有时候希望限制该类型,确保要么是某个类型,要么是某个类型的父类或子类。

  • 无界通配符

<?>,表示任意类型。

  • 上界通配符

<? extends E>:表示该类型可以是 E 类本身,也可以是 E 的任何子类。

  • 上界通配符:泛型参数是某个类或它的父类

<? super E>:表示该类型可以是 E 类本身,也可以是 E 的任何父类。

5 类型擦除

  • 无限制类型擦除:擦除为 Object
1
2
3
4
5
6
7
8
9
10
11
12
13
// 无限定的泛型类
public class Container<T> {
private T value;
public T getValue() { return value; }
public void setValue(T t) { this.value = t; }
}

// 编译后变为:
public class Container {
private Object value;
public Object getValue() { return value; }
public void setValue(Object t) { this.value = t; }
}
  • 有限制类型擦除:擦除为边界类型(最近)。
1
2
3
4
5
6
7
8
9
10
11
12
13
// 有限定的泛型类
public class NumberContainer<T extends Number> {
private T value;
public T getValue() { return value; }
public void setValue(T t) { this.value = t; }
}

// 编译后变为:
public class NumberContainer {
private Number value; // T 被擦除为 Number
public Number getValue() { return value; }
public void setValue(Number t) { this.value = t; }
}

原始类型相等且都为 Object

其中 stringArrayListintegerArrayList 都是 java.util.ArrayList

1
2
3
4
5
6
7
8
9
public class Main {
public static void main(String[] args) {
ArrayList<String> stringArrayList = new ArrayList<String>();
ArrayList<Integer> integerArrayList = new ArrayList<Integer>();
System.out.println(stringArrayList.getClass() == integerArrayList.getClass());
}
}

true

说明 Java 会在编译之前检查数据类型。如果是在编译后检查数据类型,类型擦除之后,原始类型为 Object 类型,是应该允许任意引用类型添加,但是事实上不是的,也恰恰说明是会在编译之前检查数据类型的。因此可以考虑使用:

:one: 通过反射来添加其他类型元素:

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>();
list.add("Hello");
try {
Method m = list.getClass().getMethod("add", Object.class);
m.invoke(list, 2025);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(list);
}

[Hello, 2025]

:two: 通过不指定泛型来添加其他类型元素:

1
2
3
4
5
6
7
8
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add("Hello");
list.add(2025);
System.out.println(list);
}

[Hello, 2025]

5.1 调用泛型方法

  1. 不指定泛型类型的情况:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.print(element + " ");
}
System.out.println();
}
public static void main(String[] args) {
// 不指定泛型类型
Integer[] intArray = {1, 2, 3};
String[] strArray = {"Hello", "World"};
Double[] doubleArray = {1.1, 2.2, 3.3};
printArray(intArray); // T 被推断为 Integer
printArray(doubleArray); // T 被推断为 Double
printArray(strArray); // T 被推断为 String
}

1 2 3
1.1 2.2 3.3
Hello World
  1. 指定泛型类型的情况:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static <T extends Number> double sum(T[] array) {
double sum = 0.0;
for (T element : array) {
sum += element.doubleValue();
}
return sum;
}
public static void main(String[] args) {
// 指定泛型类型
Integer[] intArray = {1, 2, 3};
Double[] doubleArray = {1.1, 2.2, 3.3};
String[] strArray = {"Hello", "World"};
// 正确:Integer 是 Number 的子类
System.out.println(sum(intArray));
// 正确:Double 是 Number 的子类
System.out.println(sum(doubleArray));
// 编译错误:String 不是 Number 的子类
// System.out.println(sum(strArray));
}

6.0
6.6

5.2 类型检查

  • ArrayList() $\rightarrow$ ArrayList
1
2
3
ArrayList list1 = new ArrayList();
list1.add("1");
list1.add(2);
  • ArrayList<String>() $\rightarrow$ ArrayList<String>
1
2
3
ArrayList<String> list2 = new ArrayList<String>();
list2.add("1");
list2.add(2); // 编译错误
  • ArrayList() $\rightarrow$ ArrayList<String>
1
2
3
ArrayList<String> list3 = new ArrayList();
list2.add("1");
list2.add(2); // 编译错误
  • ArrayList<String>() $\rightarrow$ ArrayList
1
2
3
ArrayList list4 = new ArrayList<String>();
list1.add("1");
list1.add(2);

综合来看,new ArrayList()new ArrayList<String>() 都是开辟内存空间,使用引用 listx 来调用方法,真正设计类型检查的是引用。因此 ArrayList<String> 类型引用对于 add(2) 调用会产生编译错误提示。

考虑继承关系:

1
2
ArrayList<String> list1 = new ArrayList<Object>(); // 编译错误
ArrayList<Object> list2 = new ArrayList<String>(); // 编译错误

情况一:ArrayList<Object>() $\rightarrow$ ArrayList<String>

1
2
3
4
ArrayList<Object> list1 = new ArrayList<Object>();
list1.add(new Object());
list1.add(new Object());
ArrayList<String> list2 = list1; //编译错误

java.util.ArrayList<java.lang.Object>无法转换为java.util.ArrayList<java.lang.String>

情况二:ArrayList<String>() $\rightarrow$ ArrayList<Object>

1
2
3
4
ArrayList<String> list1 = new ArrayList<String>();
list1.add(new String());
list1.add(new String());
ArrayList<Object> list2 = list1; //编译错误

java.util.ArrayList<java.lang.String>无法转换为java.util.ArrayList<java.lang.Object>

5.3 桥接方法

父类:

1
2
3
4
5
6
7
8
9
10
11
public class Node<T> {
protected T data;

public T getData() {
return data;
}

public void setData(T data) {
this.data = data;
}
}

子类:

1
2
3
4
5
6
7
8
9
10
11
public class StringNode extends Node<String> {
@Override
public String getData() {
return data;
}

@Override
public void setData(String data) {
this.data = data;
}
}

本意通过子类 StringNode extends Node<String> 传入泛型类型 String,使得父类转换为:

1
2
3
4
5
6
7
8
9
10
11
public class Node {
protected String data;

public String getData() {
return data;
}

public void setData(String data) {
this.data = data;
}
}

事实上,在进行类型擦除之后,父类转换为:

1
2
3
4
5
6
7
8
9
10
11
public class Node {
protected Object data;

public Object getData() {
return data;
}

public void setData(Object data) {
this.data = data;
}
}

按照推断,子类应该具有四个方法:

  • public Object getData()
  • public void setData(Object data)
  • public String getData()
  • public void setData(String data)

不符合重写的定义,应该属于重载。

但是在执行以下代码,会出现编译错误:

1
2
3
StringNode stringNode = new StringNode();
stringNode.setData(new String());
stringNode.setData(new Object()); // 编译错误

也就是说明**确实是重写,而不是重载**。


JVM 采用了特殊的方法——桥接方法,来重写 String 类型参数的相关方法。

1
$ javac Node.java StringNode.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
$ javap -c Node
Compiled from "Node.java"
public class Node<T> {
protected T data;

public Node();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return

public T getData();
Code:
0: aload_0
1: getfield #2 // Field data:Ljava/lang/Object;
4: areturn

public void setData(T);
Code:
0: aload_0
1: aload_1
2: putfield #2 // Field data:Ljava/lang/Object;
5: return
}
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
$ javap -c StringNode            
Compiled from "StringNode.java"
public class StringNode extends Node<java.lang.String> {
public StringNode();
Code:
0: aload_0
1: invokespecial #1 // Method Node."<init>":()V
4: return

public java.lang.String getData();
Code:
0: aload_0
1: getfield #2 // Field data:Ljava/lang/Object;
4: checkcast #3 // class java/lang/String
7: areturn

public void setData(java.lang.String);
Code:
0: aload_0
1: aload_1
2: putfield #2 // Field data:Ljava/lang/Object;
5: return

public void setData(java.lang.Object);
Code:
0: aload_0
1: aload_1
2: checkcast #3 // class java/lang/String
5: invokevirtual #4 // Method setData:(Ljava/lang/String;)V
8: return

public java.lang.Object getData();
Code:
0: aload_0
5: invokevirtual #4 // Method setData:(Ljava/lang/String;)V
8: return

public java.lang.Object getData();
Code:
0: aload_0
1: invokevirtual #5 // Method getData:()Ljava/lang/String;
4: areturn
}

对应子类也就是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class StringNode extends Node<String> {
public String getData() {
return (String)super.getData();
}

public void setData(String data) {
super.setData(data);
}

public Object getData() {
return getData();
}

public void setData(Object data) {
setData((String)data);
}
}

桥接方法真正重写父类的方法,而子类的方法实现具体逻辑

6 泛型数组

  • 编译报错:
1
2
3
4
5
6
7
8
9
10
// Not really allowed.
List<String>[] lsa = new List<String>[10];
Object o = lsa;
Object[] oa = (Object[]) o;
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(3));
// Unsound, but passes run time store check
oa[1] = li;
// Run-time error: ClassCastException.
String s = lsa[1].get(0);
  • 编译不报错,运行时报错:
1
2
3
4
5
6
7
8
9
10
// OK, array of unbounded wildcard type.
List<?>[] lsa = new List<?>[10];
Object o = lsa;
Object[] oa = (Object[]) o;
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(3));
// Correct.
oa[1] = li;
// Run time error, but cast is explicit.: ClassCastException.
String s = (String) lsa[1].get(0);

正常运行:

1
2
3
4
5
6
7
8
9
10
// OK, array of unbounded wildcard type.
List<?>[] lsa = new List<?>[10];
Object o = lsa;
Object[] oa = (Object[]) o;
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(3));
// Correct.
oa[1] = li;
// OK
Integer i = (Integer) lsa[1].get(0);

泛型数组初始化时数组类型不能是具体的泛型类型,只能是通配符的形式。

因为具体类型会导致存入任意类型对象,在取出时发生类型转换异常 ClassCastException

通配符形式需要强转,只会出现运行时报错。