8.1 为什么要使用泛型程序设计

  1. 为了使程序的可读性更强
  2. 可以对多种不同类型的对象重用
  3. 编译器可以利用泛型的类型信息,进行检查

    8.2 定义简单泛型类

    泛型类就是有一个或多个类型变量的类:
    例如: ```java package generic;

/**

  • Created by ql on 2022/5/30 */ public class Pair { private T first; private T second;

    public Pair() {

    1. first = null;
    2. second = null;

    }

    public Pair(T first, T second) {

    1. this.first = first;
    2. this.second = second;

    }

    public T getFirst() {

    1. return first;

    }

    public void setFirst(T first) {

    1. this.first = first;

    }

    public T getSecond() {

    1. return second;

    }

    public void setSecond(T second) {

    1. this.second = second;

    } }

  1. 泛型类可以有多个类型变量<br />Java库使用E表示集合元素,KV分别表示表的键和值,T(或者US)表示“任意类型”
  2. ```java
  3. public class Pair<T, U> {...}
  1. package generic;
  2. /**
  3. * Created by ql on 2022/5/30
  4. */
  5. public class PairTest1 {
  6. public static void main(String[] args) {
  7. String[] words = {"Mary", "had", "a", "little", "lamb"};
  8. Pair<String> mm = ArrayAlg.minmax(words);
  9. System.out.println("min=" + mm.getFirst());
  10. System.out.println("max=" + mm.getSecond());
  11. }
  12. }
  13. class ArrayAlg {
  14. /**
  15. * 得到String数组中的最小值和最大值
  16. * @param a
  17. * @return
  18. */
  19. public static Pair<String> minmax(String[] a) {
  20. if (a == null || a.length == 0) return null;
  21. String min = a[0];
  22. String max = a[0];
  23. for (int i = 0; i < a.length; i++) {
  24. if (min.compareTo(a[i]) > 0) min = a[i];
  25. if (max.compareTo(a[i]) < 0) max = a[i];
  26. }
  27. return new Pair<>(min, max);
  28. }
  29. }

image.png

8.3 泛型方法

示例:

  1. class ArrayAlg {
  2. public static <T> T getMiddle(T... a) {
  3. return a[a.length / 2];
  4. }
  5. }

调用泛型方法:

  1. String middle = ArrayAlg.<String>getMiddle("John", "Q.", "Public");

也可以省略,因为编译器会推断你想要的方法

  1. String middle = ArrayAlg.getMiddle("John", "Q.", "Public");

偶尔,编译器也会报错:

  1. double middle = ArrayAlg.getMiddle(3.14, 1729, 0);

8.4 类型变量的限定

  1. class ArrayAlg {
  2. public static <T extends Comparable> T min(T[] a) {
  3. if (a == null || a.length == 0) return null;
  4. T smallest = a[0];
  5. for (int i = 0; i < a.length; i++) {
  6. if (smallest.compareTo(a[i]) > 0) smallest = a[i];
  7. }
  8. return smallest;
  9. }
  10. }

只有T 继承了 Comparable接口,T类型才有compareTo方法
如果一个类型变量有多个限定,可以用“&”分隔:
T extends Comparable & Serializable

  1. public static <T extends Comparable> Pair<T> minmax(T[] a) {
  2. if (a == null || a.length == 0) return null;
  3. T min = a[0];
  4. T max = a[0];
  5. for (int i = 0; i < a.length; i++) {
  6. if (min.compareTo(a[i]) > 0) min = a[i];
  7. if (max.compareTo(a[i]) < 0) max = a[i];
  8. }
  9. return new Pair<>(min, max);
  10. }
  1. public class PairTest2 {
  2. public static void main(String[] args) {
  3. LocalDate[] birthdays = {
  4. LocalDate.of(1960, 12, 9),
  5. LocalDate.of(1815, 12, 10),
  6. LocalDate.of(1903, 12, 3),
  7. LocalDate.of(1910, 6, 22)
  8. };
  9. Pair<LocalDate> mm = ArrayAlg.minmax(birthdays);
  10. System.out.println("min=" + mm.getFirst());
  11. System.out.println("max=" + mm.getSecond());
  12. }
  13. }

8.5 泛型代码和虚拟机(了解)

定义一个泛型类型的时候会自动提供一个相应的原始类型(raw type),也就是去掉泛型类型名的类型
对于无限定类型的变量替换为Object
对于限定类型的变量用第一个限定来替换类型变量

8.6 限制与局限性

8.6.1 不能用基本类型实例化类型参数

没有Pair,只有Pair
原因:类型擦除
类型擦除后,Pair类含有Object类型的字段,而Object不能存储double

8.6.2 运行时类型查询只适用于原始类型

所有类型查询只产生原始类型:

  1. if (a instanceof Pair<String>) // Error
  2. if (a instanceof Pair<T>) //Error
  3. Pair<String> p = (Pair<String>) a; //warning--can only test that a is a Pair
  1. Pair<String> stringPair = ...;
  2. Pair<Employee> employeePair = ...;
  3. if(stringPair.getClass() == employeePair.getClass()) // equal

8.6.3 不能创建参数化类型的数组

  1. var table = new Pair<String>[10]; //error

类型擦除之后,table类型为Pair[]

8.6.4 Varargs警告

考虑以下方法:

  1. public static <T> void addAll(Collection<T> coll, T... ts) {
  2. for(T t : ts) coll.add(t);
  3. }

实际上ts是一个数组,包含提供的所有实参
考虑以下调用:

  1. Collection<Pair<String>> table = ...;
  2. Pair<String> pair1 = ...;
  3. Pair<String> pair2 = ...;
  4. addAll(table, pair1, pair2);

为了调用addAll,虚拟机必须建立一个Pair 数组,这就违反了前面的规则。但是对于这种情况,只会得到一个警告,而不是错误
可以采用@SuppressWarnings(“unchecked”)来抑制警告
或者@SafeVarargs

8.6.5 不能实例化类型变量

  1. // error
  2. public Pair() {
  3. first = new T();
  4. second = new T();
  5. }

最好的解决:

  1. Pair<String> p = Pair.makePair(String::new);
  2. // Supplier<T>是一个函数式接口,表示一个无参数而且返回类型为T的函数
  3. public static <T> Pair<T> makePair(Supplier<T> constr) {
  4. return new Pair<>(constr.get(), constr.get());
  5. }

传统的解决:
原理是通过反射调用Constructor.newInstance方法,但细节很复杂

  1. first = T.class.getConstructor().newInstance(); //error

必须适当的设计API:

  1. public static <T> Pair<T> makePair(Class<T> cl) {
  2. try {
  3. return new Pair<>(cl.getConstructor().newInstance(), cl.getConstructor().newInstance());
  4. }catch(Exception e) {
  5. return null;
  6. }
  7. }

该方法的调用:

  1. Pair<Stirng> p = Pair.makePair(String.class);

8.6.6 不能构造泛型数组

8.7 泛型类型的继承规则

无论S与T有什么关系,Pair与Pair都没有任何关系
image.png
image.png

8.8 通配符类型

8.8.1 通配符概念

在通配符类型中,允许类型参数发生变化:

  1. Pair<? extends Employee>

假设:

  1. public static void printBuddies(Pair<Employee> p) {
  2. Employee first = p.getFirst();
  3. Employee second = p.getSecond();
  4. System.out.println(first.getName() + "and" + second.getName() + " are buddies.");
  5. }

由8.7可知,不能将Pair传递给这个方法
解决方法:使用一个通配符类型:

  1. public static void printBuddies(Pair<? extends Employee> p)

image.png

  1. ? extends Employee getFirst();
  2. void setFirst(? extends Employee);

编译器只知道需要Employee的某个子类型,但不知道具体什么类型,所以setFirst拒绝传递任何特定的类型
getFirst不存在该问题,因为把返回值赋值给一个Employee是完全合法的

8.8.2 通配符的超类型限定

  1. ? super Manager

这个通配符限制为Manager的所有超类型

  1. void setFirst(? super Manager)
  2. ? super Manager getFirst()

编译器无法知道setFirst方法的具体类型,所以不能接受Employee或Object,只能传递Manager或它的子类对象
调用getFirst,不能保证返回对象的类型,所以只能赋值给Object

  1. public static void minmaxBouns(Manager[] a, Pair<? super Manager> result) {
  2. if(a.length == 0) return;
  3. Manager min = a[0];
  4. Manager max = a[0];
  5. for(int i = 1; i < a.length; i++) {
  6. if(min.getBonus() > a[i].getBonus) min = a[i];
  7. if(max.getBonus() < a[i].getBonus) max = a[i];
  8. }
  9. result.setFirst(min);
  10. result.setSecond(max);
  11. }

image.png
直观的讲,带有超类型限定的通配符允许你写入一个泛型对象,而带有子类型限定的通配符允许你读取一个泛型对象

8.8.3 无限定通配符

例如:Pair<?>, 假设有以下方法

  1. ? getFirst();
  2. void setFirst(?);

getFirst只能赋值给Object。setFirst不能被调用
这样的脆弱的类型,看起来毫无用处,其实也有自己的应用场景

8.8.4 通配符捕获

  1. public static void swap(Pair<?> p)

以下是非法的:

  1. ? t = p.getFirst();
  2. p.setFirst(p.getSecond());
  3. p.setSecond(t);

但是swap的时候必须临时保存第一个元素,解决方案:编写一个辅助方法:

  1. public static <T> void swapHelper(Pair<T> p) {
  2. T t = p.getFirst();
  3. p.setFirst(p.getSecond);
  4. p.setSecond(t);
  5. }

现在可以由swap调用swapHelper:

  1. public static swap(Pair<?> p) {
  2. swapHelper(p);
  3. }

swapHelper方法的参数T捕获通配符

综合

  1. package pair3;
  2. /**
  3. * @version 1.01 2012-01-26
  4. * @author Cay Horstmann
  5. */
  6. public class PairTest3
  7. {
  8. public static void main(String[] args)
  9. {
  10. var ceo = new Manager("Gus Greedy", 800000, 2003, 12, 15);
  11. var cfo = new Manager("Sid Sneaky", 600000, 2003, 12, 15);
  12. var buddies = new Pair<Manager>(ceo, cfo);
  13. printBuddies(buddies);
  14. ceo.setBonus(1000000);
  15. cfo.setBonus(500000);
  16. Manager[] managers = { ceo, cfo };
  17. var result = new Pair<Employee>();
  18. minmaxBonus(managers, result);
  19. System.out.println("first: " + result.getFirst().getName()
  20. + ", second: " + result.getSecond().getName());
  21. maxminBonus(managers, result);
  22. System.out.println("first: " + result.getFirst().getName()
  23. + ", second: " + result.getSecond().getName());
  24. }
  25. public static void printBuddies(Pair<? extends Employee> p)
  26. {
  27. Employee first = p.getFirst();
  28. Employee second = p.getSecond();
  29. System.out.println(first.getName() + " and " + second.getName() + " are buddies.");
  30. }
  31. public static void minmaxBonus(Manager[] a, Pair<? super Manager> result)
  32. {
  33. if (a.length == 0) return;
  34. Manager min = a[0];
  35. Manager max = a[0];
  36. for (int i = 1; i < a.length; i++)
  37. {
  38. if (min.getBonus() > a[i].getBonus()) min = a[i];
  39. if (max.getBonus() < a[i].getBonus()) max = a[i];
  40. }
  41. result.setFirst(min);
  42. result.setSecond(max);
  43. }
  44. public static void maxminBonus(Manager[] a, Pair<? super Manager> result)
  45. {
  46. minmaxBonus(a, result);
  47. PairAlg.swapHelper(result); // OK--swapHelper captures wildcard type
  48. }
  49. // can't write public static <T super manager> . . .
  50. }
  51. class PairAlg
  52. {
  53. public static boolean hasNulls(Pair<?> p)
  54. {
  55. return p.getFirst() == null || p.getSecond() == null;
  56. }
  57. public static void swap(Pair<?> p) { swapHelper(p); }
  58. public static <T> void swapHelper(Pair<T> p)
  59. {
  60. T t = p.getFirst();
  61. p.setFirst(p.getSecond());
  62. p.setSecond(t);
  63. }
  64. }

8.9 反射和泛型

反射允许你在运行时分析任意对象
利用反射可以获得泛型类的哪些信息?

8.9.1 泛型class类

例如:String.class实际上是Class类的对象,Class以下方法使用了类型参数:
image.png

8.9.2 使用Class参数进行类型匹配

8.9.3 虚拟机中的泛型类型信息

image.png

  • Class类,描述具体类型
  • TypeVariable接口,描述类型变量(如T extends … Comparable<? super T>)
  • WildcardType接口,描述通配符(?super T)
  • ParameterizedType接口,描述泛型类或接口类型(Comparable<? super T>)
  • GenericArrayType接口,描述泛型数组 ```java package generic;

import java.lang.reflect.*; import java.util.Arrays; import java.util.Scanner;

/**

  • Created by ql on 2022/5/31 */ public class GenericRelfectionTest { public static void main(String[] args) {

    1. String name;
    2. // 从命令行参数或用户输入中读取类名
    3. if (args.length > 0) name = args[0];
    4. else {
    5. try(var in = new Scanner(System.in)) {
    6. System.out.println("Enter class name: ");
    7. name = in.next();
    8. }
    9. }
    10. try {
    11. //打印class和public methods的泛型信息
    12. Class<?> cl = Class.forName(name);
    13. printClass(cl);
    14. for (Method m : cl.getDeclaredMethods()) {
    15. printMethod(m);
    16. }
    17. } catch (ClassNotFoundException e) {
    18. e.printStackTrace();
    19. }

    }

    private static void printMethod(Method m) {

    1. String name = m.getName();
    2. System.out.print(Modifier.toString(m.getModifiers()));
    3. System.out.print(" ");
    4. printTypes(m.getTypeParameters(), "<", ", ", "> ", true);
    5. printType(m.getGenericReturnType(), false);
    6. System.out.print(" ");
    7. System.out.print(name);
    8. System.out.print("(");
    9. printTypes(m.getGenericParameterTypes(), "", ", ", "", false);
    10. System.out.println(")");

    }

    private static void printClass(Class<?> cl) {

    1. System.out.print(cl);
    2. printTypes(cl.getTypeParameters(), "<", ",", ">", true);
    3. Type sc = cl.getGenericSuperclass();
    4. if (sc != null) {
    5. System.out.print(" extends ");
    6. printType(sc, false);
    7. }
    8. printTypes(cl.getGenericInterfaces(), " implements ", ", ", "", false);
    9. System.out.println();

    }

    public static void printType(Type type, boolean isDefinition) {

    1. if (type instanceof Class) {
    2. var t = (Class<?>) type;
    3. System.out.print(t.getName());
    4. }
    5. else if (type instanceof TypeVariable) {
    6. var t = (TypeVariable<?>) type;
    7. System.out.print(t.getName());
    8. if (isDefinition)
    9. printTypes(t.getBounds(), " extends ", " & ", "", false);
    10. }
    11. else if (type instanceof WildcardType) {
    12. var t = (WildcardType) type;
    13. System.out.print("?");
    14. printTypes(t.getUpperBounds(), " extends ", " & ", "", false);
    15. printTypes(t.getLowerBounds(), " super ", " & ", "", false);
    16. }
    17. else if (type instanceof ParameterizedType) {
    18. var t = (ParameterizedType) type;
    19. Type owner = t.getOwnerType();
    20. if (owner != null) {
    21. printType(owner, false);
    22. System.out.print(".");
    23. }
    24. printType(t.getRawType(), false);
    25. printTypes(t.getActualTypeArguments(), "<", ", ", ">", false);
    26. }
    27. else if (type instanceof GenericArrayType) {
    28. var t = (GenericArrayType) type;
    29. System.out.print("");
    30. printType(t.getGenericComponentType(), isDefinition);
    31. System.out.print("[]");
    32. }

    }

    public static void printTypes(Type[] types, String pre, String sep, String suf, boolean isDefinition) {

    1. if (pre.equals(" extends ") && Arrays.equals(types, new Type[] { Object.class }))
    2. return;
    3. if (types.length > 0) System.out.print(pre);
    4. for (int i = 0; i < types.length; i++)
    5. {
    6. if (i > 0) System.out.print(sep);
    7. printType(types[i], isDefinition);
    8. }
    9. if (types.length > 0) System.out.print(suf);

    } }

  1. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/27178439/1653965825001-129d0771-41f4-4033-a868-7eec46088124.png#clientId=u91c6cf5e-3979-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=183&id=u25012841&margin=%5Bobject%20Object%5D&name=image.png&originHeight=183&originWidth=738&originalType=binary&ratio=1&rotation=0&showTitle=false&size=23220&status=done&style=none&taskId=u468c5a09-21e2-4751-9fa0-3931b82b67e&title=&width=738)
  2. <a name="bcscE"></a>
  3. ## 8.9.4 类型字面量
  4. ```java
  5. package generic;
  6. import java.lang.reflect.Field;
  7. import java.lang.reflect.ParameterizedType;
  8. import java.lang.reflect.Type;
  9. import java.util.ArrayList;
  10. import java.util.HashMap;
  11. import java.util.Map;
  12. import java.util.function.Function;
  13. /**
  14. * Created by ql on 2022/5/31
  15. */
  16. class TypeLiteral<T>
  17. {
  18. private Type type;
  19. /**
  20. * This constructor must be invoked from an anonymous subclass
  21. * as new TypeLiteral<. . .>(){}.
  22. */
  23. public TypeLiteral()
  24. {
  25. Type parentType = getClass().getGenericSuperclass();
  26. if (parentType instanceof ParameterizedType)
  27. {
  28. type = ((ParameterizedType) parentType).getActualTypeArguments()[0];
  29. }
  30. else
  31. throw new UnsupportedOperationException(
  32. "Construct as new TypeLiteral&lt;. . .&gt;(){}");
  33. }
  34. private TypeLiteral(Type type)
  35. {
  36. this.type = type;
  37. }
  38. /**
  39. * Yields a type literal that describes the given type.
  40. */
  41. public static TypeLiteral<?> of(Type type)
  42. {
  43. return new TypeLiteral<Object>(type);
  44. }
  45. public String toString()
  46. {
  47. if (type instanceof Class) return ((Class<?>) type).getName();
  48. else return type.toString();
  49. }
  50. public boolean equals(Object otherObject)
  51. {
  52. return otherObject instanceof TypeLiteral
  53. && type.equals(((TypeLiteral<?>) otherObject).type);
  54. }
  55. public int hashCode()
  56. {
  57. return type.hashCode();
  58. }
  59. }
  60. /**
  61. * Formats objects, using rules that associate types with formatting functions.
  62. */
  63. class Formatter
  64. {
  65. private Map<TypeLiteral<?>, Function<?, String>> rules = new HashMap<>();
  66. /**
  67. * Add a formatting rule to this formatter.
  68. * @param type the type to which this rule applies
  69. * @param formatterForType the function that formats objects of this type
  70. */
  71. public <T> void forType(TypeLiteral<T> type, Function<T, String> formatterForType)
  72. {
  73. rules.put(type, formatterForType);
  74. }
  75. /**
  76. * Formats all fields of an object using the rules of this formatter.
  77. * @param obj an object
  78. * @return a string with all field names and formatted values
  79. */
  80. public String formatFields(Object obj)
  81. throws IllegalArgumentException, IllegalAccessException
  82. {
  83. var result = new StringBuilder();
  84. for (Field f : obj.getClass().getDeclaredFields())
  85. {
  86. result.append(f.getName());
  87. result.append("=");
  88. f.setAccessible(true);
  89. Function<?, String> formatterForType = rules.get(TypeLiteral.of(f.getGenericType()));
  90. if (formatterForType != null)
  91. {
  92. // formatterForType has parameter type ?. Nothing can be passed to its apply
  93. // method. Cast makes the parameter type to Object so we can invoke it.
  94. @SuppressWarnings("unchecked")
  95. Function<Object, String> objectFormatter
  96. = (Function<Object, String>) formatterForType;
  97. result.append(objectFormatter.apply(f.get(obj)));
  98. }
  99. else
  100. result.append(f.get(obj).toString());
  101. result.append("\n");
  102. }
  103. return result.toString();
  104. }
  105. }
  106. public class TypeLiterals
  107. {
  108. public static class Sample
  109. {
  110. ArrayList<Integer> nums;
  111. ArrayList<Character> chars;
  112. ArrayList<String> strings;
  113. public Sample()
  114. {
  115. nums = new ArrayList<>();
  116. nums.add(42); nums.add(1729);
  117. chars = new ArrayList<>();
  118. chars.add('H'); chars.add('i');
  119. strings = new ArrayList<>();
  120. strings.add("Hello"); strings.add("World");
  121. }
  122. }
  123. private static <T> String join(String separator, ArrayList<T> elements)
  124. {
  125. var result = new StringBuilder();
  126. for (T e : elements)
  127. {
  128. if (result.length() > 0) result.append(separator);
  129. result.append(e.toString());
  130. }
  131. return result.toString();
  132. }
  133. public static void main(String[] args) throws Exception
  134. {
  135. var formatter = new Formatter();
  136. formatter.forType(new TypeLiteral<ArrayList<Integer>>(){},
  137. lst -> join(" ", lst));
  138. formatter.forType(new TypeLiteral<ArrayList<Character>>(){},
  139. lst -> "\"" + join("", lst) + "\"");
  140. System.out.println(formatter.formatFields(new Sample()));
  141. }
  142. }

image.png