概述
1.为什么要使用泛型程序?
泛型程序设计意味着编写的代码可以对多种不同的类进行重用。
2.定义简单的泛型类
泛型类:有一个或者多个类型变量的类。
例子:
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;}
}
注释:Java库使用变量E表示集合的元素类型,K和V分别表示键和值的类型。T(必要时还可以用相邻的字母U和S)表示"任意类型".
可以使用具体的类型替换类型变量来实例化(instantiate)泛型类型如:Pair<String>
3.泛型方法
例子:
class ArrayAlg{
public static <T> T getMiddle(T...a){
return a[a.length/2];
}
}
1.泛型方法可以在普通类中定义,也可以在泛型类中定义。
2.方法调用:
String middle = ArrayAlg.<String>getMiddle("John","Q.","Psd");//拿具体类型替换
或
String middle = ArrayAlg,getMiddle("John","Q.","Psd");//这种情况可以省略,因为编译器可以推断你想要的方法,这里不深究
4.类型变量的限定
例子:
public static <T extends Comparable> T min(T[] a)...
记法:
<T extends BoundingType> //T应该BoundingType的子类型,它们既可以是类,也可以是借口 T extends Comparable & Serializable //可以根据需要拥有多个接口超类型,但最多只有一个可以是类,并且必须是限定列表中的第一个限定
5.泛型代码和虚拟机
虚拟机没有泛型类型对象----所有对象都属于普通类(所以有了"擦除"概念)
5.1 类型擦除
无论何时定义一个泛型类型,都会自动提供一个相应的原始类型(其名字就是去掉类型参数后的泛型类型名)。类型变量会被擦除,并替换为其限定类型(对无限定类型的替换为Object)
T ---擦除后---> Object
Pair<String>或Pair<LocalDate> ---擦除后---> Pair
注释:<T extends Serializable & Comparable> 会用Serializable替换T,编译器在必要时向Comparable插入强制类型转换
5.2 转换泛型表达式
Pair<Employee> buddies = ...;
Employee buddy = buddies.getFirst();
编译器在调用该方法时走二步:
1.对原始方法Pair.getFirst的调用
2.将返回的Object类型强转位Employee类型
访问泛型字段时也要插入强制类型转换
5.3 转换泛型方法
会合成桥接法来保持多态。(这里不细究)
5.4调用遗留代码
观察代码:
void setLabelTable(Dictionary table)//方法
Dictionary<Integer,Component>//对象类型
slider.setLabelTable(labelTable);
//warning 因为编译器无法确定setLabelTable会对Dictionary对象做什么操作,
//擦除后,可能会有糟糕的强制类型转换,这就打破了了键类型必须为Integer的承诺。
//实际上,JSlider只读取这个信息,因此可以忽略这个警告。
//相反的情况,由一个遗留类得到一个原始类型的对象。可以将它赋给一个类型使用了泛型的变量
Dictionary<Integer,Component> labelTable = slider.getLabelTable;//warning
//为了忽略这个警告
@SuppressWarnings("unchecked")
Dictionary<Integer,Component> labelTable = slider.getLabelTable;//no warning
//该注解会关闭对方法中所有代码的检查
6.限制与局限性
6.1 不能用基本类型实例化参数类型
例子:没Pair<double>只有Pair<Double>
6.2 运行时类型查询只适用于原始类型
例子:
a instanceof Pair<T> // ERROR,一般会编译错误
例子:
Pair<String> stringPair = ...;Pair<Employee> employeePair = ...;
if(stringPair.getClass() == employeePair.getClass()) // true因为getClass的返回都是Pair.Class
6.3 不能创建参数化类型的数组
Pair table = new Pair<String>[10];// ERROR
6.4 Varargs警告
public static <T> void addAll(Collection<T> coll,T...ts){
for (T t : ts) coll.add(t);
}
Collection<Pair<String>> table = ...;
Pair<String> pair1 = ...;
Pair<String> pair2 = ...;
addAll(table,pair1,pair2);
为了调用这个方法,虚拟机必须建立一个Pair<String>数组,这就违反了之前的规定。不过这种情况规则会放松。你会得到一个警告
@SuppressWarnings("unchecked") @SafeVarargs
这二个注解可以忽略警告
注释:@SafeVarargs只能用于声明为static、final或private的构造器和方法。否则有可能没意义。
小拓展:
//可以使用@SafeVarags注解来消除创建泛型数组的有关限制
@SafeVarags static <E> E[] array(E...array) {return array;}
//现在可以调用
Pair<String>[] table = array(pair1,pair2);
//这看起来很方便,不过隐藏着危险。以下代码:
Object[] objarray = table;
obj array[0] = new Pair<Employee>();
//能顺利运行而不会报ArrayStoreException 异常(因为数组存储只会检查擦除的类型),但在处理table[0]时,你会在别处得到一个异常
6.5 不能实例化类型变量
public Pair(){first = new T();second = new T();}//ERROR 类型擦除将T变成Object,而这肯定不是你想要的
//在java8之后,最好的解决办法就是让调用者提供一个构造器表达式。例如:
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
//表达式T.class是不合法的,因为它会擦除为Object.class
//正确实现
public static <T> Pair<T> makePair(Class<T> clazz){
try {
return new Pair<>(clazz.getConstructor().newInstance(),clazz.getConstructor().newInstance());
}catch (Exception e){
return null;
}
}
Pair<String> p = Pair.makePair(String.class);
6.6 不能构造泛型数组 (略)
6.7 泛型类的静态上下文中类型变量无效
private static T singleInstance;//ERROR 编译就会报错
6.8 不能抛出或捕获泛型类的实例
public class Problem<T> extends Exception{...}//ERROR--can't extend Throwable catch(T e)//ERROR--can't catch type variable
6.9 可以取消对检查型异常的检查
Java异常处理的一个基本原则是,必须为所有检查型异常提供一个处理器。不过可以利用泛型取消这个机制。关键在于下面这个方法:
@SuppressWarnings("unchecked")
static <T extends Throwable> void throwAs(Throwable throwable) throws T{
throw (T)throwable;
}
假设这个方法在Task接口中,如果有一个检查型异常e,并调用
Task.<RuntimeException>throwAs(e);
编译器就会认为e是一个非检查型异常。
try{ do work }catch(Throwable t){ Task.<RuntimeException>throwAs(e); }//会把所有异常都转换为编译器所认为的非检查型异常
6.10 注意擦除后的冲突
考虑一个Pair<String> 从概念上说,它有二个equals方法
boolean equals(String) //defined in Pair<T> boolean equals(Object) //inherited from Object
补救办法:重命名
注意:如果二个接口类型是同一接口的不同参数化,一个类或类型变量就不能同时作为这二个接口类的子类
class Employee implements Comparable<Employee>{...}
class Manager extends Employee implements Comparable<Manager>{...}//ERROR
7.泛型类型的继承规则
例子:
Manager继承Employee,但Pair<Employee>和Pair<Manager>没有任何关系
8.通配符
8.1 通配符概念
例子 :Pair<? extends Employee> 表示任何泛型Pair类型,它的类型参数是Employee的子类
public static void printBuddies(Pair<Employee> p){ Employee first = p.getFirst(); Employee second = p.getSecond(); System.out.println("....."); }//该程序用于打印员工的信息,但不能传Pair<Manager>
解决办法:
public static void printBuddies(Pair< ? extends Employee> p)
8.2 通配符的超类型限定
?super Manager // 还可以这么搞
<T extends Comparable<? super T>> // 有兴趣可以留意,这里不深究,本意是帮助应用程序员去除对调用参数的不必要限制
8.3 无限定通配符
?// 还可以这么搞
?getFirst()
void getFirst(?)
Pair<?>和Pair本质的不同在于:可以用任意Object对象调用原始Pair类的setFirst方法(可以调用setFirst(null))
8.4 通配符捕获
通配符不是类型变量,所以 ?t = p.getFirst是非法的
通配符捕获只有在非常限定的情况下才是合法的。编译器必须能保证通配符表示单个确定的类型。例如ArrayList<Pair<T>>中的T永远都不能捕获ArrayList<Pair<?>>中的通配符。
9.反射和泛型(利用反射可以获得泛型类的哪些信息)
9.1 泛型Class类
Class类是泛型的。例如String.class实际上是一个class<String>类的对象。
java.lang.Class<T>
T newInstance() 返回无参数构造器构造的一个实例
T cast(Object obj) 如果 obj为null或者转换类型T,则返回obj;否则抛出一个BadCastException异常
T[] getEnumConstants 如果T是枚举类型,则返回所有值组成的数组,否则返回null
Class<? super T>getSuperClass() 返回这个类的超类。如果T不是一个类或Object类,则返回null
Constructor<T> getConstructor(Class...paramterTypes)
Constructor<T> getDeclaredConstructor(Class...paramterTypes)
获得公共构造器,或者有给定参数类型的构造器
java.lang.reflect.Constructor<T>
T newInstance(Object...parameters) 返回用指定参数构造的新实例
9.2 使用Class<T> 参数进行类型匹配
public static <T> Pair<T> makePair(Class<T> c) throws ReflectiveOperationException { return new Pair<>(c.newInstance(),c.newInstance()); }
如果调用makePair(Employee.class)编译器可以推断出方法返回一个Pair<Employee>
9.3 虚拟机中的泛型类信息
擦除的类仍保留原先泛型的微弱记忆。例如Pair类知道它源自Pair<T>,尽管无法区分类型参数。
java.lang.reflect包中的接口Type(为了表述泛型类型声明),该接口有以下子类:
Class类,描述具体类型
TypeVariable接口,描述类型变量(如 T extends Comparable<? super T>)
WildcardType接口,描述通配符(如 ? super T)
ParameterizedType接口,描述泛型类或者接口类(如 Comparable<? super T>)
GenericArrayType 接口,描述泛型数组(如 T[])
9.4 类型字面量
如果现在有一个需求:需要将Class对象与一个动作关联,但是泛型类,擦除会带来问题。比如ArrayList<Integer>和ArrayList<String>都擦除为同一个原始类型ArrayList,如何让它们有不同的动作捏?
小技巧(适用于某些情况)。可以捕获Type接口的一个实例,然后构造一个匿名子类。
class TypeLiteral{
private Type type;
public TypeLiteral(){
Type parentType = getClass().getGenericSuperclass();
if(parentType instanceof ParameterizedType){
type = ((ParameterizedType) parentType).getActualTypeArguments()[0];
}else {
throw new UnsupportedOperationException("Construct as new TypeLiteral<...>(){}");
}
}
}
如果运行时有一个泛型类型,就可以将它与TypeLiteral匹配。
9.5 其他
java.lang.Class<T>
TypeVariable getTypeParameters() 如果这个类型被声明为泛型类型,则获得泛型类型变量,否则获得一个长度为0的数组
Type getGenericSupperclass() 获得这个类型所声明超类的泛型类型;如果这个类型是Object或者不是类类型(class type),则返回null
Type[] getGenericInterfaces() 获得这个类型所声明接口的泛型类型(按照声明的次序),否则,如果这个类型没有实现接口,则返回长度为0的数组
java.lang.reflect.Method
TypeVariable getTypeParameters() 如果这个方法被声明为一个泛型方法,则获得泛型类型变量,否则返回长度为0的数组
Type getGenericReturnType() 获得这个方法声明的泛型返回类型
Type[] getGenericParameterTypes() 获得这个方法声明的泛型参数类型。如果这个方法没有参数,返回长度为0的数组
java.lang.reflect.TypeVariable
String getName() 获得这个类型变量的名字
Type[] getBounds() 获得这个类型变量的子类限定,否则,如果该变量无限定,则返回长度为0的数组
java.lang.reflect.WildcardType
Type[] getUpperBounds() 获得这个类型变量的子类限定,否则,如果没有子类限定,则返回长度为0的数组
Type[] getLowerBounds() 获得这个类型变量的超类限定,否则,如果没有子类限定,则返回长度为0的数组
java.lang.reflect.ParameterizedType
Type getRawType() 获得这个参数化类型的原本参数
Type getActualTypeArguments() 获取这个参数化类型声明的类型参数
Type getOwnerType() 如果是内部类型,则返回其他外部类类型;如果是一个顶级类型,则返回null。
java.lang.reflect.GenericArrayType
Type getGenericComponentType() 获取这个数组类型声明的泛型元素类型
最后
以上就是幽默蓝天为你收集整理的JavaSE笔记:泛型程序设计的全部内容,希望文章能够帮你解决JavaSE笔记:泛型程序设计所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复