什么是泛型
泛型就是类型参数化,用尖括号 <T> 给类或方法”留个坑”,用的时候再填上具体类型
打个比方:泛型就像快递柜,柜子本身不在乎你放什么东西,但你可以贴个标签说明里面放的是什么(书、水果、衣服),这样取的时候不会拿错
没有泛型之前(Java5以前),集合里啥都能放,取出来要强制转型,一不小心就 ClassCastException
为什么需要泛型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Test public void testWithoutGenerics() {
List list = new ArrayList(); list.add("hello"); list.add(123);
String s = (String) list.get(0);
List<String> safeList = new ArrayList<>(); safeList.add("hello");
String str = safeList.get(0); }
|
泛型的好处:
编译期类型检查:类型不对编译就报错,不用等到运行时才炸
不用强制转型:编译器自动帮你转
代码复用:一套逻辑适用于多种类型
泛型类
在类名后面加 <T>,这个T就是类型参数,用的时候指定具体类型
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
| class Box<T> { private T data;
public void set(T data) { this.data = data; }
public T get() { return data; } }
@Test public void testGenericClass() {
Box<String> stringBox = new Box<>(); stringBox.set("Hello"); String s = stringBox.get();
Box<Integer> intBox = new Box<>(); intBox.set(42); int n = intBox.get();
}
|
常见的类型参数命名惯例:
| 字母 |
含义 |
典型使用 |
T |
Type |
通用类型 |
E |
Element |
集合元素 |
K |
Key |
键 |
V |
Value |
值 |
N |
Number |
数字 |
R |
Result |
返回值 |
可以有多个类型参数:class Pair<K, V> { K key; V value; }
泛型方法
在方法返回值前面加 <T>,这个方法就有了自己的类型参数,不依赖类的泛型
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
| class GenericMethodDemo {
public <T> void printArray(T[] array) { for (T item : array) { System.out.print(item + " "); } System.out.println(); }
public <T> T getFirst(List<T> list) { return list.isEmpty() ? null : list.get(0); } }
@Test public void testGenericMethod() { GenericMethodDemo demo = new GenericMethodDemo();
Integer[] intArr = {1, 2, 3}; String[] strArr = {"A", "B", "C"};
demo.printArray(intArr); demo.printArray(strArr);
List<String> names = Arrays.asList("张三", "李四"); String first = demo.getFirst(names); System.out.println(first); }
|
泛型接口
接口也可以定义泛型,实现类指定具体类型
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 44
| interface DataStore<T> { void save(T data); T load(int id); }
class StringStore implements DataStore<String> { @Override public void save(String data) { System.out.println("保存字符串:" + data); }
@Override public String load(int id) { return "数据" + id; } }
class CacheStore<T> implements DataStore<T> { private Map<Integer, T> cache = new HashMap<>();
@Override public void save(T data) { cache.put(cache.size(), data); }
@Override public T load(int id) { return cache.get(id); } }
@Test public void testGenericInterface() { DataStore<String> store = new StringStore(); store.save("Hello"); System.out.println(store.load(1));
CacheStore<Integer> intCache = new CacheStore<>(); intCache.save(100); System.out.println(intCache.load(0)); }
|
通配符
通配符 ? 用在使用泛型的地方(不是定义泛型的地方),表示”我不确定具体类型”
无界通配符 <?>
表示”任何类型都行”,一般只读不写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @Test public void testUnbounded() {
List<String> strings = Arrays.asList("A", "B"); List<Integer> integers = Arrays.asList(1, 2);
printList(strings); printList(integers); }
public void printList(List<?> list) { for (Object item : list) { System.out.println(item); }
}
|
上界通配符 <? extends Number> —— 只读
表示”Number或Number的子类”,可以安全读取(当Number用),但不能写入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Test public void testUpperBound() { List<Integer> intList = Arrays.asList(1, 2, 3); List<Double> doubleList = Arrays.asList(1.1, 2.2, 3.3);
System.out.println("整数之和:" + sumOfList(intList)); System.out.println("小数之和:" + sumOfList(doubleList)); }
public double sumOfList(List<? extends Number> list) { double sum = 0; for (Number n : list) { sum += n.doubleValue(); }
return sum; }
|
下界通配符 <? super Integer> —— 只写
表示”Integer或Integer的父类”,可以安全写入Integer,但读出来只能当Object
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Test public void testLowerBound() { List<Number> numberList = new ArrayList<>(); addNumbers(numberList); System.out.println(numberList); }
public void addNumbers(List<? super Integer> list) { list.add(1); list.add(2); list.add(3);
Object obj = list.get(0); }
|
PECS原则:Producer Extends, Consumer Super
如果集合是生产者(你从里面读数据)→ 用 extends
如果集合是消费者(你往里面写数据)→ 用 super
记忆口诀:读extends,写super
1 2 3 4 5 6 7 8
|
public static <T> void copy(List<? super T> dest, List<? extends T> src) { for (int i = 0; i < src.size(); i++) { dest.set(i, src.get(i)); } }
|
通配符对比表
| 通配符 |
含义 |
能读? |
能写? |
使用场景 |
<?> |
任意类型 |
读为Object |
不能写 |
只需遍历不需修改 |
<? extends T> |
T或T的子类 |
读为T |
不能写 |
从集合读数据 |
<? super T> |
T或T的父类 |
读为Object |
能写T |
往集合写数据 |
类型擦除
泛型是编译期的语法糖,编译后泛型信息会被擦除,运行时不存在
也就是说 List<String> 和 List<Integer> 在运行时其实是同一个类 List
1 2 3 4 5 6 7 8 9 10 11 12
| @Test public void testTypeErasure() { List<String> stringList = new ArrayList<>(); List<Integer> intList = new ArrayList<>();
System.out.println(stringList.getClass() == intList.getClass()); System.out.println(stringList.getClass().getName());
}
|
类型擦除带来的限制
不能 new T():编译后T变成Object,JVM不知道你要创建什么类型的对象
不能 new T[]:同理
不能 instanceof List<String>:运行时没有泛型信息
不能用基本类型:List<int> 不行,要用 List<Integer>
泛型的实际应用
看看 Collection体系 和 Map体系 的源码声明就知道了:
public interface List<E> extends Collection<E>
public class ArrayList<E> implements List<E>
public interface Map<K, V>
public class HashMap<K, V> implements Map<K, V>
自己写一个泛型工具方法的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| @Test public void testPracticalGeneric() {
List<String> names = Arrays.asList("张三", "李四", "王五");
List<String> result = mapList(names, name -> name + "同学"); System.out.println(result);
List<Integer> lengths = mapList(names, String::length); System.out.println(lengths); }
public <T, R> List<R> mapList(List<T> list, Function<T, R> mapper) { List<R> result = new ArrayList<>(); for (T item : list) { result.add(mapper.apply(item)); } return result; }
|
常见坑
坑1:泛型不能用基本类型
List<int> ❌,要写 List<Integer>,自动装箱拆箱会帮你搞定
坑2:泛型类的静态方法不能用类的类型参数
1 2 3 4 5 6
| class MyClass<T> {
static <E> void bar(E e) {} }
|
坑3:泛型数组不能直接创建
1 2 3
| List<String>[] arr = new List[10];
|
坑4:List<Object> 不是 List<String> 的父类
练习题
题1:实现一个泛型Pair类 —— 存储两个可能不同类型的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class Pair<A, B> { private A first; private B second;
public Pair(A first, B second) { this.first = first; this.second = second; }
public A getFirst() { return first; } public B getSecond() { return second; } }
@Test public void exercise1() { Pair<String, Integer> pair = new Pair<>("张三", 90); System.out.println(pair.getFirst() + " : " + pair.getSecond());
}
|
题2:写一个泛型方法 —— 找出List中的最大值(要求元素实现Comparable)
提示:方法签名 <T extends Comparable<T>> T findMax(List<T> list)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public <T extends Comparable<T>> T findMax(List<T> list) { if (list.isEmpty()) throw new IllegalArgumentException("空列表"); T max = list.get(0); for (T item : list) { if (item.compareTo(max) > 0) { max = item; } } return max; }
@Test public void exercise2() { List<Integer> nums = Arrays.asList(3, 1, 4, 1, 5, 9); System.out.println(findMax(nums));
List<String> words = Arrays.asList("banana", "apple", "cherry"); System.out.println(findMax(words)); }
|
题3:理解PECS —— 下面哪些代码能编译通过?为什么?
1 2 3 4 5 6 7 8
| List<? extends Number> list1 = new ArrayList<Integer>();
List<? super Integer> list2 = new ArrayList<Number>();
|
答案:extends的不能add,能读为Number;super的能add Integer,读只能当Object