泛型程序设计(generic programming)意味者编写多种不同类型的对象重用。
举例:
收集String和File对象无需编写不同的类,只需要一个ArrayList就可以收集。
1.1类型参数的好处在Java中增加泛型类之前,泛型程序设计是用继承实现的。
ArrayList类只维护一个Object应用的数组:
public class ArrayList //before generic classes
{
private Object[] elementData;
...
public Object get(int i) {...}
public void add(Object o) {...}
}
缺点:
1.首先取值需要强制转换
ArrayList files = new Arraylist(); String filename = (String) files.get(0);
2.无错误检查,可以想数组列表中添加任何类的值
files.add(new File("..."));
编译和运行都不会出错,但若将get的即通过强制类型转换为String类型,会出错
泛型提供的解决方案 :类型参数 (指示元素的类型)
ArrayListfiles = new ArrayList();
优点:
1.可读性好
2.编译器充分利用
String filename = files.get(0);//返回类型为String, 而不是Object
files.add(new File("..."));//error 无法通过编译
2 泛型类
泛型类(generc class)就是有一个或多个类型变量的类。
举例:
public class Pair{ private T first; private T second; public T getFirst() { return first; } // 返回类型为T的变量 public T getSecond() { return second; } public void setFirst(T first) { this.first = first; } // 局部类型变量的类型为T public void setSecond(T second) { this.second = second; } public Pair(T first, T second) { this.first = first; this.second = second; } }
类型变量T 用<>括起来放在类命的后面,泛型类可以有多个类型变量。
public class Pair{...}
注:
类型变量一般使用大写字母,而且简短。Java中E表示集合的元素类型,K和V分别表示键和值的类型。T(U和S)表示“任意类型”。
可以用具体的类型替换类型变量来实例化(instantiate)泛型类型。
举例:
Pair3 泛型方法
类型变量放在修饰符之后,返回值之前。
泛型方法既可以在普通方法中定义,也可以在泛型类中定义。
举例:
class ArrayAlg {
public static T getMiddle(T... a) {
return a[a.length / 2];
}
}
调用泛型方法,可以将具体类型放在尖括号中,放在方法名前面。
举例:
String middle = ArrayAlg.getMiddle("1","2","3");
大部分下情况下可以省略,编译器会自动推断。
不可省略的情况举例:
double middle = ArrayAlg.getMiddle(3.14, 1, 2);
编译器将参数自动装箱为1个Double对象和2个Integer对象,然后寻找这些类的共同超类型。事实上找到了2个超类型Number和Comparable接口。编译工具会隐晦的提示:
Static member 'ArrayAlg.getMiddle(java.lang.Number & java.lang.Comparable extends java.lang.Number & java.lang.Comparable>>...)' accessed via instance reference4类型变量的限定
有时候, 类或方法需要对类型变量需要加以约束。
举例:
public class ArrayAlg{ public static 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]; } } }
这段代码有一定的问题,min方法中,smallest的类型为T,意味它可以是任何一个类的对象,但不是每一个类都有一个compareTo方法。
如何改正:只能限制T为实现了Comparable接口(包含一个compareTo的方法)的类。
举例:
public staticT min(T[] a)... // 为什么Comparable是接口却用extends ? // 仅仅因为它更加接近子类型的概念
一个类型变量或通配符可以有多个限定
举例:
ArrayAlg5 泛型代码 1类型擦除
无论何时定义一个泛型类型,都会自动提供一个相应的原始类型(raw type)。这个原始类型的名字就是去掉类型参数后的泛型类型名。类型变量都会被擦除(erased),并且替换为限定类型(或者,对于无限定的变量则替换为Object)。
原始类型用第一个限定来替换类型变量,或者没有给限定就替换为Object。
举例:
Pair2转换泛型表达式//原始类型为Pair Interval //第一个限定类型为Comparable
编写一个泛型方法调用时,如果擦除了返回类型,编译器会插入强制类型转换。
举例:
Pairbuddies = ...; Employee buddy = buddies.getFirst();
编译器擦除类型后返回类型是Object,调用getFirst会自动插入转换到Employee的强制类型转换。
即:1.对原始方法Pair.getFirst的调用 2.将返回的Object类型强制转换为Employee类型
3转换为泛型方法类型擦除也会出现在泛型方法
举例:
public static6限制性与局限性 1不能使用基本类型实例化类型参数T min(T[] a); // 泛型擦除后 public static Comparable min(Comparable[] a);
举例:
不能使用Pair 只有Pair
原因在于类型擦除,擦除之后Pair类含有Object类型的字段,而Object类型不能存储double值。
2运行时类型查询只适用于原始类型虚拟机对象中总有一个特定类型的非泛型类型。因此,所有类型查询只产生原始类型。
举例:
if (a instanceof Pair) //error if (a instanceof Pair ) //error Pair p = (Pair ) a; // warning--can only test that a is a Pair
为提醒这一风险,如果试图查询一个对象是否属于某个泛型类型,会得到一个错误或警告。
同样道理,由于泛型擦除会有以下结果出现:
Pair3不能创建参数化类型的数组stringPair = ...; Pair employeePair = ...; if(stringPair.getClass() == employeePair.getClass()) //true
举例:
var table = new Pair[10]; // error
**原因:**一般来说数组会记住自己的元素类型,如果试图存储其他类型的元素,会抛出ArrayStore-Exception异常,而泛型擦除后 var table = new Pair[10];会使这种机制无效。
Object[] arr = table; arr[0] = new Pair5不能实例化类型变量(); //编译通过, 但是会导致类型错误
不能再类似new T(…)的表达式中使用类型变量
举例:
public Pair() {first = new T(); second = new T();} //error
类型擦除将T变成Object
6不能构造泛型数组举例:
public staticT[] minmax(T... a) { T[] mm = new T[2]; // error }
类型擦除会使得构造Comparable[2]数组
String[] names = ArrayAlg.minmax("Tom", "Dick");
// 在Comparable[]引用强制转换为String[]时候,会出现ClassCastException
7泛型类的静态上下文中类型变量无效
不能在静态字段或方法中引用类型变量。
举例:
public class Singlenton8不能抛出或捕获泛型类的实例{ private static T singleInstance; //error private static T getSingleInstance() {//error if(singleInstance == null) return singleInstance; } }
不能抛出或捕获泛型类的的对象。事实上泛型类扩展Throwable都是不合法的。
举例:
public class Problem7泛型类型的继承规则extends Exception {...} // error public static void doWork(Class t) { try { // do work } catch (T e) { // error } }
无论S与T有什么关系,通常Pair与Pair都没有关系。
举例:
Manager[] topHonchos = ...; Pairresult = ArrayAlg.minmax(topHonchos); //error
minmax返回Pair ,而不是Pair,这样的赋值不合法。
这一点与数组不同
Employee[] employeeBuddies = new Manager[]{ceo, cfo}; // ture
将一个参数化类型转换为原始类型的时。也可能产生类型错误。
举例:
Pair rawBuddies = new Pair; rawBuddies.serFirst(new File("...")); // error
最后,泛型类也可以扩展或实现其他泛型类。
**例如:**ArrayList类实现List接口
8通配符类型 1通配符的概念在通配符类型中允许类型参数发生变化。
举例:
Pair< ? extends Employee> public static void printBuddies(Pairp) { Employee first = p.getFirst(); Employee second = p.getSecond(); System.out.println(first.getName() + " and " + secong.getName() + " are buddies."); }
这个方法的局限性就是不能将Pair 传给这个方法。但是利用通配符就可以解决。
Pair是Pair< ? extends Employee>的子类型
Pair< ? extends Employee> ? extends Employee getFirst() void setFirst(? extends Employee)
以上代码不可能调用setFirst方法,编译器只需要知道Employee的某个子类型,但不知道具体是什么类型,导致直接拒绝传递任何特定的类型。毕竟?不能匹配。
使用getFirst不存在这个问题,将getFirst的返回值赋予一个Employee引用完全合法
2通配符的超类型限定举例:
Pair< ? super Manager> ? super Manager getFirst() void setFirst(? super Manager)
编译器无法知道setFirst方法的具体类型,所以不能接受参数类型为Employee 或Object的方法调用,只能传递Manager类型的对象,或某个子类型的对象。而调用getFirst,不能保证返回对象的类型,只能把它赋值给一个Object。
Pair是Pair< ? super Manager>的子类型
某些情况下使用通配符可以帮程序员去除对调用参数的不必要限制
举例:
LocalDate extends ChronLocalDate ChronLocalDate extends Comparable3无限定通配符// 所以 LocalDate 实现的是Comparable 而不是Comparable // 这种情况下可以利用超类型解决 public static > T min(T[] a)...
举例:
Pair> ? getFirst(); void setFirst(?)
getFirst的返回值只能赋给一个Object。setFirst方法不能被调用。



