泛型程序设计(generi c programming)意味着编写的代码可以对多种不同类型的对象重
用。
它们会让你的程序更易读,也更安全。
集合中没有使用泛型时:
集合中使用泛型时:
Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastException异常。即,把不安全的因素在编译期间就排除了,而不是运行期;既然通过了编译,那么类型一定是符合要求的,就避免了类型转换。
实现泛型类典型问题:
一个程序员可能想要将一个ArrayList
Java设计者为此发明了通配符类型。
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 = ArrayAIg.getMiddle(3.14, 1729, 0);
编译器将把参数自动装箱为1个Double和2个Integer对象,然后寻找这些类的共同超类型。事实上,它找到了 2个超类型:Number和Comparable接口,Comparable接口本身也是一个泛型类型。在这种情况下,可以采取的补救措施是将所有的参数都写为double值。
public static <T extends Comparable> T min(T[] a)
为什么使用关键字extends而不是implements ?毕竟,Comparable是一个接口。下面的记法
以是接口。选择关键字extends的原因是它更接近子类型的概念,并且Java的设计者也不打
算在语言中再添加一个新的关键字(如sub)。
一个类型变量或通配符可以有多个限定,例如:T extends Comparable & Serializable。限定类型用“&”分隔,而逗号用来分隔类型变量。
可以根据需要拥有多个接口超类型,但最多有一个限定可以是类。如果有一个类作为限定,它必须是限定列表中的第一个限定。
对于Java泛型的转换,需要记住以下几个事实:
Java泛型与C++模板有很大的区别。C++为每个模板的实例化产生不同的类型,这一现象称为“模板代码膨胀”。Java不存在这个问题的困扰。
Pair<Employee> buddies =...;
Employee buddy = buddies.getFirst();
getFirst擦除类型后的返回类型是0bject。编译器自动插人转换到Employee的强制类型转换。也就是说,编译器把这个方法调用转换为两条虚拟机指令:
• 对原始方法Pair.getFirst的调用。
• 将返回的Object类型强制转换为Employee类型。
private T first; public void setFirst(T newValue) { first = newValue; }
擦除后private Object first; public void setFirst(Object newValue) { first = newValue; }
。但Pair调用setFirst(T newValue)方法时,编译器要求只能传入Employee及其子类型(Manager)的对象。不可能传入一个Object对象,保证了泛型的安全性。
public static
是整个一组方法,而擦除类型之后,只剩下一个方法:T min(T[] a) public static Comparable min(Comparable[] a)
。其中,类型参数T已经被擦除了,只留下了限定类型Comparable。
public class Pair<T>
{
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 T getSecond() { return second; }
public void setFirst(T newValue) { first = newValue; }
public void setSecond(T newValue) { second = newValue; }
}
(1)DateInterval类是Pair的子类,Pair是个泛型类。
public class DateInterval extends Pair<LocalDate> {
//重写的方法
public void setSecond(LocalDate second) {
System.out.println("DateInterval.setScond() is called");
if(second.compareTo(getFirst())>=0)
super.setSecond(second);
}
}
(2)DateInterval类被擦除后变成:
public class DateInterval extends Pair { //Pair被擦除
//重写的方法
public void setSecond(LocalDate second) {
System.out.println("DateInterval.setScond() is called");
if(second.compareTo(getFirst())>=0)
super.setSecond(second);
}
}
(3)此时,DateInterval还有一个从Pair继承的setSecond方法:
public void setSecond(Object second) // 泛型类Pair的泛型方法在JVM中被擦除成object
(4)显然,DateInterval的public void setSecond(LocalDate second)
方法,不是对Pair的public void setSecond(Object second)
方法的重写。因为参数类型不同。所以无法自动实现多态。理论上,DateInterval有两个不相关的方法。即类型擦除与多态发生了冲突。
(1) 下面的例子可以看出,泛型实现了多态。
此处实现了多态,调用的是DateInterval.setScond()方法。只有second的日期在first之后才会给second赋值。年份换成1999年,输出的就是null。
public static void main(String[] args) {
DateInterval dateInterval = new DateInterval();
Pair<LocalDate> superDateInterval=dateInterval; // 子类对象赋值给父类变量
superDateInterval.setFirst(LocalDate.of(2000, 12, 22));
superDateInterval.setSecond(LocalDate.of(1999, 12, 22)); //not work,second为null
superDateInterval.setSecond(LocalDate.of(2008, 12, 22)); // work,second为2008-12-22
System.out.println(superDateInterval.getSecond());
}
(2) 为了解决类型擦除与多态的冲突,编译器在DateInterval类中生成了一个桥方法。
public void setSecond(Object second) { setSecond((LocalDate) second); }
DateInterval的桥方法
public void setSecond(Object second) { setSecond((LocalDate) second); }
是Pair的public void setSecond(Object second)
方法的重写。所以,多态性让superDateInterval调用的是superDateInterval的桥方法,而桥方法会调用DateInterval的public void setSecond(LocalDate second)
方法。
public static void main(String[] args) {
DateInterval dateInterval = new DateInterval();
Pair<LocalDate> superDateInterval=dateInterval; // 子类对象赋值给父类对象
superDateInterval.setFirst(LocalDate.of(2000, 12, 22));
//此处实现了多态,调用的是DateInterval.setScond()方法。只有second的日期在first之后才会给second赋值。年份换成1999年,输出的就是null。
superDateInterval.setSecond(LocalDate.of(2008, 12, 22));
System.out.println(superDateInterval.getSecond());
Method[] declaredMethods = dateInterval.getClass().getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.println(Modifier.toString(declaredMethod.getModifiers())+" "+declaredMethod.getReturnType()
+" "+declaredMethod.getName()
+"("+declaredMethod.getParameterTypes()[0]+")");
}
}
假设Datelnterval类也覆盖了 getSecond方法:
class Datelnterval extends Pair<LocalDate>
{
public LocalDate getSecond() { return (LocalDate) super.getSecond(); }
}
在Datelnterval类中,有两个getSecond方法:
LocalDate getSecond() // defined in Datelnterval
Object getSecond() // overrides the method defined in Pair to call the first method
LocalDate getSecond()
。设计Java泛型时,主要目标是允许泛型代码和遗留代码之间能够互操作。
Dictionary
labelTable = slider.getLabelTablel); // warning
警告: 确保标签表确实包含Integer和Component对象。
基本类型不是对象,无法擦除为object。
虚拟机中的对象总有一个特定的非泛型类型。因此,所有的类型查询只产生原始类型。
var table = new Pair[10]; //error
4. Varargs 警告
public class PairTest2
{
public static void main(String[] args)
{
Pair p1=Pair.makePair(new Supplier<String>() {
public String get()
{
return "hhh";
}
});
Pair p2=Pair.makePair(String.class);
}
}
public class Pair<T>
{
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 T getSecond() { return second; }
public void setFirst(T newValue) { first = newValue; }
public void setSecond(T newValue) { second = newValue; }
public static <T> Pair<T> makePair(Supplier<T> constr) { //用Supplier创建
return new Pair<>(constr.get(),constr.get());
}
public static <T> Pair<T> makePair(Class<T> cl) { //用反射创建
try {
return new Pair<>(cl.getConstructor(String.class).newInstance("123"),cl.getConstructor(String.class).newInstance("456"));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
理解问题:为什么
return (E) elements[n];
可以用强制类型转换。而不能:T a = new T(); return (T) a;
。
- 实际上new T()会报错,因为不能实例化类型变量,同时,(T) a这个强制类型转换也不满足强转的条件: a instanceof T为假。经过类型擦除后,new T() 变成new object() , 所以a实际是一个object对象,不能把父类对象强转成子类对象。进一步揭示了为什么不能实例化类型变量。
- 观察上面的代码,发现elements是对象变量而不是new出来的对象。elements[n]擦除为object,但elements[n]指向了某个T类型,所以可以进行强制转换。
数组父类为object,但B继承了A,那么A[]类型的引用就可以指向B[]类型的对象
静态字段属于类,两个不同对象只有一个属于类的singleInstance字段,不可能既是A类型又是B类型。
当Pair
重写equals方法时,即使加override注解也会报错。原因:泛型类重写equals方法时,为了实现多态,JVM会自动生成桥方法:boolean equals(Object),桥方法会调用重写的方法boolean equals(T),但由于擦除后T就是Object,所以重写的方法与巧方法冲突。、
没有继承关系,不可能多态或者强制转型。
会会产生类型错误:
Pair pair=new Pair
// only a compile-time warning(1,2); pair.setFirst("牛");
运行时才会报错ClassCastException。
但失去的只是泛型程序设计提供的附加安全性,更重要的是与遗留代码交互。
ArrayList
类实现了List 接口。这意味着,一个ArrayList 可以转换为一个List 。但是,如前面所见,ArrayList<Manager>不是一个ArrayList<Employee>或List<Employee>。
? t = p.getFirst(); // ERROR
p.setFirst(p.getSecond());
p.setSecond(t);
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);
}
有待考证
) var ceo = new Manager("Gus Greedy", 800000, 2003, 12, 15);
var cfo = new Manager("Sid Sneaky", 600000, 2003, 12, 15);
Employee employee1=new Employee("employee1",80000,1999,11,22);
Employee employee2=new Employee("employee2",80000,1999,11,22);
var buddies = new Pair<Manager>(ceo, cfo);
ceo.setBonus(1000000);
cfo.setBonus(500000);
Manager[] managers = { ceo, cfo };
ArrayList<Pair<?>> arrayList=new ArrayList<>(); // ArrayList存储了不同类型的两个pair
arrayList.add(new Pair<Manager>(ceo,cfo));
arrayList.add(new Pair<Employee>(employee1,employee2));
PairAlg.swap(arrayList.get(1)); //可行
PairAlg.swap(arrayList.get(0)); //可行
System.out.println();
Class
...
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("[]");
}
}
...