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
| class GenericsInterface implements interfaceName<Interger>
|
- 实现接口的子类不明确声明泛型类型
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
|
关于 Integer
和 String
对于 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 类型擦除
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; public Number getValue() { return value; } public void setValue(Number t) { this.value = t; } }
|
原始类型相等且都为 Object
。
其中 stringArrayList
和 integerArrayList
都是 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 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); printArray(doubleArray); printArray(strArray); }
1 2 3 1.1 2.2 3.3 Hello World
|
- 指定泛型类型的情况:
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"}; System.out.println(sum(intArray)); System.out.println(sum(doubleArray)); }
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 4: return
public T getData(); Code: 0: aload_0 1: getfield 4: areturn
public void setData(T); Code: 0: aload_0 1: aload_1 2: putfield 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 4: return
public java.lang.String getData(); Code: 0: aload_0 1: getfield 4: checkcast 7: areturn
public void setData(java.lang.String); Code: 0: aload_0 1: aload_1 2: putfield 5: return
public void setData(java.lang.Object); Code: 0: aload_0 1: aload_1 2: checkcast 5: invokevirtual 8: return
public java.lang.Object getData(); Code: 0: aload_0 5: invokevirtual 8: return
public java.lang.Object getData(); Code: 0: aload_0 1: invokevirtual 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
| 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));
oa[1] = li;
String s = lsa[1].get(0);
|
1 2 3 4 5 6 7 8 9 10
| List<?>[] lsa = new List<?>[10]; Object o = lsa; Object[] oa = (Object[]) o; List<Integer> li = new ArrayList<Integer>(); li.add(new Integer(3));
oa[1] = li;
String s = (String) lsa[1].get(0);
|
正常运行:
1 2 3 4 5 6 7 8 9 10
| List<?>[] lsa = new List<?>[10]; Object o = lsa; Object[] oa = (Object[]) o; List<Integer> li = new ArrayList<Integer>(); li.add(new Integer(3));
oa[1] = li;
Integer i = (Integer) lsa[1].get(0);
|
泛型数组初始化时数组类型不能是具体的泛型类型,只能是通配符的形式。
因为具体类型会导致存入任意类型对象,在取出时发生类型转换异常 ClassCastException
。
通配符形式需要强转,只会出现运行时报错。