{
"type": "note",
"title": "Java泛型",
"tags": [
"post",
"Java"
],
"sources": [
"xlog"
],
"external_urls": [
"https://0xe3aad-3767.xlog.app/Java-fan-xing-md"
],
"date_published": "2022-08-04T14:33:37.000Z",
"content": "---\ntitle: \"Java泛型\"\ndate: 2022-08-04T22:33:37+08:00\ntags:\n - Java\n---\n\n## 擦拭法\n\n### 编译器把类型`<T>`视为Object\n\n```java\npublic class Pair<T> {\n private final T first;\n private final T last;\n\n public Pair(T first, T last) {\n this.first = first;\n this.last = last;\n }\n\n public T getFirst() {\n return first;\n }\n\n public T getLast() {\n return last;\n }\n}\n```\n\nJava中泛型是在编译阶段的,编译器将上述代码经过擦除法,JVM实际看到的代码如下所示:\n\n```java\npublic class Pair {\n private final Object first;\n private final Object last;\n\n public Pair(Object first, Object last) {\n this.first = first;\n this.last = last;\n }\n\n public Object getFirst() {\n return first;\n }\n\n public Object getLast() {\n return last;\n }\n}\n```\n\n\n### 编译器根据`<T>`实现安全的强制转型\n\n```java\nPair<String> p = new Pair<>(\"Hello\", \"world\");\nString first = p.getFirst();\nString last = p.getLast();\n```\n\n编译器根据T的类型安全强转:\n\n```java\nPair p = new Pair(\"Hello\", \"world\");\nString first = (String) p.getFirst();\nString last = (String) p.getLast();\n```\n\n### 擦拭法的局限性\n\n- `<T>`不能是基本类型,因为擦除法要将其替换为`Object`\n- 无法获取携带泛型的`Class`,因为泛型是在编译期,所以获取到的都为`Pair<Object>`\n例如:\n```\n@Test\nvoid testGetClass() {\n Pair<String> p1 = new Pair<>(\"Hello\", \"World\");\n Pair<Integer> p2 = new Pair<>(114, 514);\n Class c1 = p1.getClass();\n Class c2 = p2.getClass();\n\n assertEquals(c1, c2);\n assertEquals(c1, Pair.class);\n}\n```\n- 不恰当的覆写方法\n\n```java\nclass Pair<T> {\n public boolean equals(T t) {}\n}\n```\n\n这是因为,定义的`equals(T t)`方法实际上会被擦拭成`equals(Object t)`,而这个方法是继承自`Object`的,编译器会阻止一个实际上会变成覆写的泛型方法定义。\n\n### 泛型继承\n\n类可以继承一个泛型类。在继承了泛型类型的情况下,子类可以获取父类的泛型类型。例如:`IntPair`可以获取到父类的泛型类型`Integer`。\n\n```java\npublic class IntPair extends Pair<Integer> {\n\n public IntPair(Integer first, Integer last) {\n super(first, last);\n }\n}\n\nclass IntPairTest {\n @Test\n void getSuperParameterizedType() {\n Class<IntPair> clazz = IntPair.class;\n Type t = clazz.getGenericSuperclass();\n if (t instanceof ParameterizedType) {\n ParameterizedType pt = (ParameterizedType) t;\n Type[] types = pt.getActualTypeArguments(); // may have many types\n Type firstType = types[0];\n Class<?> typeClass = (Class<?>) firstType;\n assertEquals(typeClass, Integer.class);\n }\n }\n}\n```\n\n## `extends`通配符\n\n```java\npublic class PairHelper {\n static int add(Pair<Number> p) {\n Number first = p.getFirst();\n Number last = p.getLast();\n return first.intValue() + last.intValue();\n }\n}\n```\n\n![](https://i.imgur.com/rERvLBT.png)\n\n如上,`add`函数接收的参数类型为`Pair<Number>`,所以我们传入`Pair<Number>`是没有问题的;但传入`Pair<Integer>`时,会得到一个编译错误,这是因为虽然`Integer`是`Number`的子类,但是`Pair<Integer>`却不是`Pair<Number>`的子类。\n\n为了解决这个问题,可以使用`Pair<? extends Number>`,这样就可以接收泛型类型为`Number`子类的`Pair`类型了:\n\n```java\nstatic int add(Pair<? extends Number> p) {\n Number first = p.getFirst();\n Number last = p.getLast();\n return first.intValue() + last.intValue();\n}\n\n@Test\nvoid addNumberBased() {\n assertEquals(579, add(new Pair<Integer>(123, 456)));\n}\n```\n\n### 读取\n\n- 能够从`List<? extends Number>`中获取到`Number`类型,因为其包含的元素是`Number`类型或者`Number`的自类型。\n- 不能从`List<? extends Number>`中获取到`Integer`类型,因为其保存的元素可能是`Double`。\n- 不能从`List<? extends Number>`中获取到`Double`类型,因为其保存的元素可能是`Integer`。\n\n### 写入\n\n- 不能添加`Number`到`List<? extends Number>`中,因为其保存的元素可能是`Integer`或`Double`。\n- 不能添加`Integer`到`List<? extends Number>`中,因为其保存的元素可能是`Double`。\n- 不能添加`Number`到`List<? extends Number>`中,因为其保存的元素可能是`Integer`。\n\n```java\npublic class ExtendsTest {\n List<? extends Number> nums1 = new ArrayList<Number>();\n List<? extends Number> nums2 = new ArrayList<Integer>();\n List<? extends Number> nums3 = new ArrayList<Double>();\n\n void get() {\n Number x = nums1.get(0);\n // Integer y = nums2.get(0); // compile error\n // Double z = nums3.get(0); // compile error\n }\n\n void set() {\n /*\n nums1.add(new Number() {\n @Override\n public int intValue() {\n return 0;\n }\n\n @Override\n public long longValue() {\n return 0;\n }\n\n @Override\n public float floatValue() {\n return 0;\n }\n\n @Override\n public double doubleValue() {\n return 0;\n }\n });\n */ // compile error\n // nums2.add(new Integer(1)); // compile error\n // nums3.add(new Double(1.2)); // compile error\n }\n}\n```\n\n## `super`通配符\n\n`<? super T>` 描述了通配符下界, 即具体的泛型参数需要满足条件: 泛型参数必须是 `T` 类型或它的父类。\n\n### 读取\n\n- 我们不能保证可以从`List<? super Integer>`类型对象中读取到 `Integer` 类型的数据, 因为其可能是 `List<Number>` 类型的。\n- 我们不能保证可以从`List<? super Integer>`类型对象中读取到 `Number` 类型的数据, 因为其可能是 `List<Object>` 类型的。\n- 唯一能够保证的是, 我们可以从`List<? super Integer>`类型对象中获取到一个 `Object` 对象的实例。\n\n### 写入\n\n对于上面的例子中的 `List<? super Integer>` array 对象:\n- 我们可以添加 `Integer` 对象到 `array` 中, 也可以添加 `Integer` 的子类对象到 `array` 中,因为`Integer`及其父类都可以接受`Integer`的子类对象。\n- 我们不能添加 `Double/Number/Object` 等不是 `Integer` 的子类的对象到 `array` 中。\n\n![](https://i.imgur.com/ToEcttT.png)\n\n## `extends` vs `super`\n\n### 对于`List<? super Integer> l1`:\n\n- 正确的理解:`? super Integer` 限定的是泛型参数。令 `l1` 的泛型参数是 `T`, 则 `T` 是 `Integer` 或 `Integer` 的父类, 因此 `Integer` 或 `Integer` 的子类的对象就可以添加到 `l1` 中.\n- 错误的理解:~~`? super Integer`限定的是插入的元素的类型, 因此只要是 `Integer` 或 `Integer` 的父类的对象都可以插入 `l1` 中。~~\n\n### 对于`List<? extends Integer> l2:`\n\n- 正确的理解:`? extends Integer` 限定的是泛型参数. 令 `l2` 的泛型参数是 `T`, 则 `T` 是 `Integer` 或 `Integer` 的子类, 进而我们就不能找到一个类 `X`, 使得 `X` 是泛型参数 `T` 的子类, 因此我们就不可以向 `l2` 中添加元素. 不过由于我们知道了泛型参数 `T` 是 `Integer` 或 `Integer` 的子类这一点, 因此我们就可以从 `l2` 中读取到元素(取到的元素类型是 `Integer` 或 `Integer` 的子类), 并可以存放到 `Integer` 中。\n- 错误的理解:~~`? extends Integer` 限定的是插入元素的类型, 因此只要是 `Integer` 或 `Integer` 的子类的对象都可以插入 `l2` 中~~\n\n## 应用:PECS\n\n**PECS 原则: Producer Extends, Consumer Super**\n\n- Producer extends: 如果我们需要一个 `List` 提供类型为 `T` 的数据(即希望从 `List` 中读取 `T` 类型的数据), 那么我们需要使用 `? extends T`, 例如 `List<? extends Integer>`。 但是我们不能向这个 `List` 添加数据。\n- Consumer Super: 如果我们需要一个 `List` 来消费 `T` 类型的数据(即希望将 `T` 类型的数据写入 `List` 中), 那么我们需要使用 `? super T`, 例如 `List<? super Integer>`。但是这个 `List` 不能保证从它读取的数据的类型。\n- 如果我们既希望读取, 也希望写入, 那么我们就必须明确地声明泛型参数的类型, 例如 List<Integer>。\n\n```java\npublic class Collections {\n public static <T> void copy(List<? super T> dest, List<? extends T> src) {\n for (int i = 0; i < src.size(); i++) {\n dest.set(i, src.get(i));\n }\n }\n}\n```\n\n## Source code\n\n- <https://github.com/fffzlfk/generics_tour>\n\n## References\n\n- <https://www.liaoxuefeng.com/wiki/1252599548343744/1255945193293888>\n- <https://segmentfault.com/a/1190000008423240>",
"attributes": [
{
"value": "Java-fan-xing-md",
"trait_type": "xlog_slug"
}
]
}