8.1 为什么要使用泛型程序设计
- 为了使程序的可读性更强
- 可以对多种不同类型的对象重用
- 编译器可以利用泛型的类型信息,进行检查
8.2 定义简单泛型类
泛型类就是有一个或多个类型变量的类:
例如: ```java package generic;
/**
Created by ql on 2022/5/30 */ public class Pair
{ private T first; private T second; public Pair() {
first = null;
second = null;
}
public Pair(T first, T second) {
this.first = first;
this.second = second;
}
public T getFirst() {
return first;
}
public void setFirst(T first) {
this.first = first;
}
public T getSecond() {
return second;
}
public void setSecond(T second) {
this.second = second;
} }
泛型类可以有多个类型变量<br />Java库使用E表示集合元素,K和V分别表示表的键和值,T(或者U和S)表示“任意类型”
```java
public class Pair<T, U> {...}
package generic;
/**
* Created by ql on 2022/5/30
*/
public class PairTest1 {
public static void main(String[] args) {
String[] words = {"Mary", "had", "a", "little", "lamb"};
Pair<String> mm = ArrayAlg.minmax(words);
System.out.println("min=" + mm.getFirst());
System.out.println("max=" + mm.getSecond());
}
}
class ArrayAlg {
/**
* 得到String数组中的最小值和最大值
* @param a
* @return
*/
public static Pair<String> minmax(String[] a) {
if (a == null || a.length == 0) return null;
String min = a[0];
String max = a[0];
for (int i = 0; i < a.length; i++) {
if (min.compareTo(a[i]) > 0) min = a[i];
if (max.compareTo(a[i]) < 0) max = a[i];
}
return new Pair<>(min, max);
}
}
8.3 泛型方法
示例:
class ArrayAlg {
public static <T> T getMiddle(T... a) {
return a[a.length / 2];
}
}
调用泛型方法:
String middle = ArrayAlg.<String>getMiddle("John", "Q.", "Public");
也可以省略
String middle = ArrayAlg.getMiddle("John", "Q.", "Public");
偶尔,编译器也会报错:
double middle = ArrayAlg.getMiddle(3.14, 1729, 0);
8.4 类型变量的限定
class ArrayAlg {
public static <T extends Comparable> T min(T[] a) {
if (a == null || a.length == 0) return null;
T smallest = a[0];
for (int i = 0; i < a.length; i++) {
if (smallest.compareTo(a[i]) > 0) smallest = a[i];
}
return smallest;
}
}
只有T 继承了 Comparable接口,T类型才有compareTo方法
如果一个类型变量有多个限定,可以用“&”分隔:
T extends Comparable & Serializable
public static <T extends Comparable> Pair<T> minmax(T[] a) {
if (a == null || a.length == 0) return null;
T min = a[0];
T max = a[0];
for (int i = 0; i < a.length; i++) {
if (min.compareTo(a[i]) > 0) min = a[i];
if (max.compareTo(a[i]) < 0) max = a[i];
}
return new Pair<>(min, max);
}
public class PairTest2 {
public static void main(String[] args) {
LocalDate[] birthdays = {
LocalDate.of(1960, 12, 9),
LocalDate.of(1815, 12, 10),
LocalDate.of(1903, 12, 3),
LocalDate.of(1910, 6, 22)
};
Pair<LocalDate> mm = ArrayAlg.minmax(birthdays);
System.out.println("min=" + mm.getFirst());
System.out.println("max=" + mm.getSecond());
}
}
8.5 泛型代码和虚拟机(了解)
定义一个泛型类型的时候会自动提供一个相应的原始类型(raw type),也就是去掉泛型类型名的类型
对于无限定类型的变量替换为Object
对于限定类型的变量用第一个限定来替换类型变量
…
8.6 限制与局限性
8.6.1 不能用基本类型实例化类型参数
没有Pair
原因:类型擦除
类型擦除后,Pair类含有Object类型的字段,而Object不能存储double
8.6.2 运行时类型查询只适用于原始类型
所有类型查询只产生原始类型:
if (a instanceof Pair<String>) // Error
if (a instanceof Pair<T>) //Error
Pair<String> p = (Pair<String>) a; //warning--can only test that a is a Pair
Pair<String> stringPair = ...;
Pair<Employee> employeePair = ...;
if(stringPair.getClass() == employeePair.getClass()) // equal
8.6.3 不能创建参数化类型的数组
var table = new Pair<String>[10]; //error
8.6.4 Varargs警告
考虑以下方法:
public static <T> void addAll(Collection<T> coll, T... ts) {
for(T t : ts) coll.add(t);
}
实际上ts是一个数组,包含提供的所有实参
考虑以下调用:
Collection<Pair<String>> table = ...;
Pair<String> pair1 = ...;
Pair<String> pair2 = ...;
addAll(table, pair1, pair2);
为了调用addAll,虚拟机必须建立一个Pair
可以采用@SuppressWarnings(“unchecked”)来抑制警告
或者@SafeVarargs
8.6.5 不能实例化类型变量
// error
public Pair() {
first = new T();
second = new T();
}
最好的解决:
Pair<String> p = Pair.makePair(String::new);
// Supplier<T>是一个函数式接口,表示一个无参数而且返回类型为T的函数
public static <T> Pair<T> makePair(Supplier<T> constr) {
return new Pair<>(constr.get(), constr.get());
}
传统的解决:
原理是通过反射调用Constructor.newInstance方法,但细节很复杂
first = T.class.getConstructor().newInstance(); //error
必须适当的设计API:
public static <T> Pair<T> makePair(Class<T> cl) {
try {
return new Pair<>(cl.getConstructor().newInstance(), cl.getConstructor().newInstance());
}catch(Exception e) {
return null;
}
}
该方法的调用:
Pair<Stirng> p = Pair.makePair(String.class);
8.6.6 不能构造泛型数组
…
8.7 泛型类型的继承规则
8.8 通配符类型
8.8.1 通配符概念
在通配符类型中,允许类型参数发生变化:
Pair<? extends Employee>
假设:
public static void printBuddies(Pair<Employee> p) {
Employee first = p.getFirst();
Employee second = p.getSecond();
System.out.println(first.getName() + "and" + second.getName() + " are buddies.");
}
由8.7可知,不能将Pair
解决方法:使用一个通配符类型:
public static void printBuddies(Pair<? extends Employee> p)
? extends Employee getFirst();
void setFirst(? extends Employee);
编译器只知道需要Employee的某个子类型,但不知道具体什么类型,所以setFirst拒绝传递任何特定的类型
getFirst不存在该问题,因为把返回值赋值给一个Employee是完全合法的
8.8.2 通配符的超类型限定
? super Manager
这个通配符限制为Manager的所有超类型
void setFirst(? super Manager)
? super Manager getFirst()
编译器无法知道setFirst方法的具体类型,所以不能接受Employee或Object,只能传递Manager或它的子类对象
调用getFirst,不能保证返回对象的类型,所以只能赋值给Object
public static void minmaxBouns(Manager[] a, Pair<? super Manager> result) {
if(a.length == 0) return;
Manager min = a[0];
Manager max = a[0];
for(int i = 1; i < a.length; i++) {
if(min.getBonus() > a[i].getBonus) min = a[i];
if(max.getBonus() < a[i].getBonus) max = a[i];
}
result.setFirst(min);
result.setSecond(max);
}
直观的讲,带有超类型限定的通配符允许你写入一个泛型对象,而带有子类型限定的通配符允许你读取一个泛型对象
8.8.3 无限定通配符
例如:Pair<?>, 假设有以下方法
? getFirst();
void setFirst(?);
getFirst只能赋值给Object。setFirst不能被调用
这样的脆弱的类型,看起来毫无用处,其实也有自己的应用场景
8.8.4 通配符捕获
public static void swap(Pair<?> p)
以下是非法的:
? t = p.getFirst();
p.setFirst(p.getSecond());
p.setSecond(t);
但是swap的时候必须临时保存第一个元素,解决方案:编写一个辅助方法:
public static <T> void swapHelper(Pair<T> p) {
T t = p.getFirst();
p.setFirst(p.getSecond);
p.setSecond(t);
}
现在可以由swap调用swapHelper:
public static swap(Pair<?> p) {
swapHelper(p);
}
swapHelper方法的参数T捕获通配符
综合
package pair3;
/**
* @version 1.01 2012-01-26
* @author Cay Horstmann
*/
public class PairTest3
{
public static void main(String[] args)
{
var ceo = new Manager("Gus Greedy", 800000, 2003, 12, 15);
var cfo = new Manager("Sid Sneaky", 600000, 2003, 12, 15);
var buddies = new Pair<Manager>(ceo, cfo);
printBuddies(buddies);
ceo.setBonus(1000000);
cfo.setBonus(500000);
Manager[] managers = { ceo, cfo };
var result = new Pair<Employee>();
minmaxBonus(managers, result);
System.out.println("first: " + result.getFirst().getName()
+ ", second: " + result.getSecond().getName());
maxminBonus(managers, result);
System.out.println("first: " + result.getFirst().getName()
+ ", second: " + result.getSecond().getName());
}
public static void printBuddies(Pair<? extends Employee> p)
{
Employee first = p.getFirst();
Employee second = p.getSecond();
System.out.println(first.getName() + " and " + second.getName() + " are buddies.");
}
public static void minmaxBonus(Manager[] a, Pair<? super Manager> result)
{
if (a.length == 0) return;
Manager min = a[0];
Manager max = a[0];
for (int i = 1; i < a.length; i++)
{
if (min.getBonus() > a[i].getBonus()) min = a[i];
if (max.getBonus() < a[i].getBonus()) max = a[i];
}
result.setFirst(min);
result.setSecond(max);
}
public static void maxminBonus(Manager[] a, Pair<? super Manager> result)
{
minmaxBonus(a, result);
PairAlg.swapHelper(result); // OK--swapHelper captures wildcard type
}
// can't write public static <T super manager> . . .
}
class PairAlg
{
public static boolean hasNulls(Pair<?> p)
{
return p.getFirst() == null || p.getSecond() == null;
}
public static void swap(Pair<?> p) { swapHelper(p); }
public static <T> void swapHelper(Pair<T> p)
{
T t = p.getFirst();
p.setFirst(p.getSecond());
p.setSecond(t);
}
}
8.9 反射和泛型
反射允许你在运行时分析任意对象
利用反射可以获得泛型类的哪些信息?
8.9.1 泛型class类
例如:String.class实际上是Class
8.9.2 使用Class参数进行类型匹配
8.9.3 虚拟机中的泛型类型信息
- 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) {
String name;
// 从命令行参数或用户输入中读取类名
if (args.length > 0) name = args[0];
else {
try(var in = new Scanner(System.in)) {
System.out.println("Enter class name: ");
name = in.next();
}
}
try {
//打印class和public methods的泛型信息
Class<?> cl = Class.forName(name);
printClass(cl);
for (Method m : cl.getDeclaredMethods()) {
printMethod(m);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
private static void printMethod(Method m) {
String name = m.getName();
System.out.print(Modifier.toString(m.getModifiers()));
System.out.print(" ");
printTypes(m.getTypeParameters(), "<", ", ", "> ", true);
printType(m.getGenericReturnType(), false);
System.out.print(" ");
System.out.print(name);
System.out.print("(");
printTypes(m.getGenericParameterTypes(), "", ", ", "", false);
System.out.println(")");
}
private static void printClass(Class<?> cl) {
System.out.print(cl);
printTypes(cl.getTypeParameters(), "<", ",", ">", true);
Type sc = cl.getGenericSuperclass();
if (sc != null) {
System.out.print(" extends ");
printType(sc, false);
}
printTypes(cl.getGenericInterfaces(), " implements ", ", ", "", false);
System.out.println();
}
public static void printType(Type type, boolean isDefinition) {
if (type instanceof Class) {
var t = (Class<?>) type;
System.out.print(t.getName());
}
else if (type instanceof TypeVariable) {
var t = (TypeVariable<?>) type;
System.out.print(t.getName());
if (isDefinition)
printTypes(t.getBounds(), " extends ", " & ", "", false);
}
else if (type instanceof WildcardType) {
var t = (WildcardType) type;
System.out.print("?");
printTypes(t.getUpperBounds(), " extends ", " & ", "", false);
printTypes(t.getLowerBounds(), " super ", " & ", "", false);
}
else if (type instanceof ParameterizedType) {
var t = (ParameterizedType) type;
Type owner = t.getOwnerType();
if (owner != null) {
printType(owner, false);
System.out.print(".");
}
printType(t.getRawType(), false);
printTypes(t.getActualTypeArguments(), "<", ", ", ">", false);
}
else if (type instanceof GenericArrayType) {
var t = (GenericArrayType) type;
System.out.print("");
printType(t.getGenericComponentType(), isDefinition);
System.out.print("[]");
}
}
public static void printTypes(Type[] types, String pre, String sep, String suf, boolean isDefinition) {
if (pre.equals(" extends ") && Arrays.equals(types, new Type[] { Object.class }))
return;
if (types.length > 0) System.out.print(pre);
for (int i = 0; i < types.length; i++)
{
if (i > 0) System.out.print(sep);
printType(types[i], isDefinition);
}
if (types.length > 0) System.out.print(suf);
} }
![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)
<a name="bcscE"></a>
## 8.9.4 类型字面量
```java
package generic;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
/**
* Created by ql on 2022/5/31
*/
class TypeLiteral<T>
{
private Type type;
/**
* This constructor must be invoked from an anonymous subclass
* as new TypeLiteral<. . .>(){}.
*/
public TypeLiteral()
{
Type parentType = getClass().getGenericSuperclass();
if (parentType instanceof ParameterizedType)
{
type = ((ParameterizedType) parentType).getActualTypeArguments()[0];
}
else
throw new UnsupportedOperationException(
"Construct as new TypeLiteral<. . .>(){}");
}
private TypeLiteral(Type type)
{
this.type = type;
}
/**
* Yields a type literal that describes the given type.
*/
public static TypeLiteral<?> of(Type type)
{
return new TypeLiteral<Object>(type);
}
public String toString()
{
if (type instanceof Class) return ((Class<?>) type).getName();
else return type.toString();
}
public boolean equals(Object otherObject)
{
return otherObject instanceof TypeLiteral
&& type.equals(((TypeLiteral<?>) otherObject).type);
}
public int hashCode()
{
return type.hashCode();
}
}
/**
* Formats objects, using rules that associate types with formatting functions.
*/
class Formatter
{
private Map<TypeLiteral<?>, Function<?, String>> rules = new HashMap<>();
/**
* Add a formatting rule to this formatter.
* @param type the type to which this rule applies
* @param formatterForType the function that formats objects of this type
*/
public <T> void forType(TypeLiteral<T> type, Function<T, String> formatterForType)
{
rules.put(type, formatterForType);
}
/**
* Formats all fields of an object using the rules of this formatter.
* @param obj an object
* @return a string with all field names and formatted values
*/
public String formatFields(Object obj)
throws IllegalArgumentException, IllegalAccessException
{
var result = new StringBuilder();
for (Field f : obj.getClass().getDeclaredFields())
{
result.append(f.getName());
result.append("=");
f.setAccessible(true);
Function<?, String> formatterForType = rules.get(TypeLiteral.of(f.getGenericType()));
if (formatterForType != null)
{
// formatterForType has parameter type ?. Nothing can be passed to its apply
// method. Cast makes the parameter type to Object so we can invoke it.
@SuppressWarnings("unchecked")
Function<Object, String> objectFormatter
= (Function<Object, String>) formatterForType;
result.append(objectFormatter.apply(f.get(obj)));
}
else
result.append(f.get(obj).toString());
result.append("\n");
}
return result.toString();
}
}
public class TypeLiterals
{
public static class Sample
{
ArrayList<Integer> nums;
ArrayList<Character> chars;
ArrayList<String> strings;
public Sample()
{
nums = new ArrayList<>();
nums.add(42); nums.add(1729);
chars = new ArrayList<>();
chars.add('H'); chars.add('i');
strings = new ArrayList<>();
strings.add("Hello"); strings.add("World");
}
}
private static <T> String join(String separator, ArrayList<T> elements)
{
var result = new StringBuilder();
for (T e : elements)
{
if (result.length() > 0) result.append(separator);
result.append(e.toString());
}
return result.toString();
}
public static void main(String[] args) throws Exception
{
var formatter = new Formatter();
formatter.forType(new TypeLiteral<ArrayList<Integer>>(){},
lst -> join(" ", lst));
formatter.forType(new TypeLiteral<ArrayList<Character>>(){},
lst -> "\"" + join("", lst) + "\"");
System.out.println(formatter.formatFields(new Sample()));
}
}