Java复习笔记(9)——泛型

时间:2022-09-20 08:47:46

泛型基础

1、从Java程序设计语言1.0发布以来,变化最大的部分就是泛型。

2、使用泛型机制编写的程序代码要比那些杂乱地使用Object变量,然后在进行强制类型转换的代码具有更好的安全性和可读性。

3、泛型为类提供了类型参数,用来表示其中包含元素的类型:

ArrayList<String> files = new ArrayList<String>();

4、在Java 7 及以后的版本中,构造函数中可以省略泛型类型,省略的类型可以从变量的类型推断得出:

ArrayList<String> files = new ArrayList<>();

5、定义泛型类时,类型变量用尖括号包围(<>),并放在类名的后面:

public class Pair<T> {...}

6、泛型类也可以有多个类型变量:

public class Pair<T, U> {...}

7、类型变量使用大写形式,且比较短,这是很常见的。在Java库中,使用变量E表示集合的元素类型,KV分别表示关键字与值的类型。T(需要时可以用临近的字母US)表示“任意类型”。

8、可以在普通类中定义含有类型参数的泛型方法,类型变量放在修饰符之后,返回类型之前:

public static <T> T getMiddle(T... a)

9、可以在普通类中定义泛型方法,如下:

class ArrayAlg {

public static <T> getMiddle(T... a) {

return a[a.length / 2];

}

}

10、当调用一个泛型方法时,在方法名前的尖括号中放入具体的类型:

String middle

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

11、可以通过多类型变量设置限定来保证调用安全:

public static <T extends Comparable> T min(T[] a)

注意这里使用关键字extends,而非implements

12、一个类型变量或通配符可以有多个限定,限定类型之间用&分隔:

T extends Comparable & Serializable

在Java的继承中,可以根据需要拥有多个接口超类型,但限定中至多有1个类。如果用一个类作为限定,它必须是限定列表中的第一个。

Java虚拟机与泛型

1、Java虚拟机没有泛型类型对象,所欲对象都属于普通类。

2、无论何时定义一个泛型类型,都自动提供了一个相应的原始类型(row type)。原始类型的名字就是删去类型参数后的泛型类型名。擦除类型类型变量并替换为限定类型(无限定的变量用Object)。

3、为了提高效率,应将标签接口(没有方法的接口)放在边界列表的末尾。

4、当程序调用泛型方法时,如果擦除返回类型,编译器将插入强制类型转换。

5、在某些情况下,类型擦除和多态将会发生冲突,此时编译器将会生成一个桥方法(bridge method)来解决此问题:

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;

    }

}

 

class DateInterval extends Pair<Date> {

public void setSecond(Date second) {

if(second.compareto(getFirst)) {

super.setSecond(second);

}

}

}

类型擦除后:

class DateInterval extends Pair {

// 桥方法

public void setSecond(Object second) {

setSecond((Date) second);

}

public void setSecond(Date second) {

if(second.compareto(getFirst)) {

super.setSecond(second);

}

}

}

6、在虚拟机中,用方法名、参数类型和返回类型来确定一个方法。

7、有关Java泛型转换的事实:

·虚拟机中没有泛型,只有普通的类和方法。

·所有的类型参数都用它们的限定类型替换。

·桥方法被合成来保持多态。

·为保持类型安全性,必要时插入强制类型转换。

泛型中的约束与局限性

1、不能用基本类型实例化类型参数。

2、运行时类查询只适用于原始的类型:

if(a instanceof Pair<String>) //Error

实际上仅仅测试a是否为任意类型的Pair

类似地,强制类型转换:

Pair<String> p = (Pair<String>)a;

//WARNING!--can only test that a is a pair.

同样的道理,getClass()将总是返回原始的类型:

Pair<String> stringPair = ...

Pair<Employee> employeePair = ...

if(stringPair.getClass() == employeePair.getClass())

//they are equal.

其结果将返回true,因为两次调用getClass()返回的都是Pair.class

3、不能创建参数化类型数组:

Pair<String>[] table = new Pair<String>[10]; //ERROR

以上代码无法通过编译,因为泛型的擦除机制会绕过数组的类型检查,导致类型错误。

可以声明类型为Pair<String>[]的变量,但不能用new Pair<String>[10]来初始化它。

4、Varargs警告。向参数个数可变的方法传递一个泛型类的实例:

public static <T> void addAll(Collection<T> coll, T... ts) {

for(t:ts) coll.add(t);

}

调用该方法的过程中将有可能创建一个以参数化类型的数组,违法规则3,但对于这种情况,编译器有所放松,仅仅会得到一个警告。

5、不能实例化类型变量。new T(...)new T[...]T.class的表达式均为非法。但是可以通过反射调用Class.newInstance()方法来构建新对象:

public static <T> Pair<T> makePair(Class<T> cl) {

try {

return new Pair<>(cl.newInstance(), cl.newInstance());

} catch (Exception ex) {

return null;

}

}

上面的方法可以按照下列方式调用:

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

Class本身是泛型,String.class 是一个Class<String>的实例。

6、泛型类的静态上下文中类型变量无效。不鞥呢在静态域或方法中引用类型变量,例如:

public class Singleton<T> {

private static T singleInstance; //ERROR

 

public static T getSingleInstance() {//ERROR

if(singleInstance == null)construct new instance of T

return singleInstance;

}

}

如果这个程序能够运行,就可以声明一个Singleton<Random>,同时声明一个Singleton<JFileChooser>,但是这个程序无法工作,类型擦除以后,只剩下Singleton类,它只包含一个singleInstance域。

7、不能抛出或捕获泛型类实例:

public class Problem<T> extends Exception {/* ... */}

//Error--cant extend Throwable.

不过,在异常规范中使用类型变量是允许的。以下方法是合法的:

public static <T extends Throwable>

void doWork(T t) throws T {//OK

try {

do work

} catch (Throwable realCause) {

t.initCause(realCause);

throw t;

}

}

8、注意擦除后的冲突。当泛型类被擦除时,无法创建引发冲突的条件,下面的代码无法通过编译:

public boolean equals(T t) {...};

类型擦除后,它将于Object类中的equals方法发生冲突。

泛型类的另一个原则是:要想支持擦除的转换,就需要限制一个类或类型变量不能同时成为两个接口类型的子类,而这两个接口是同一接口的不同参数化。例如,下述代码是非法的:

class Calendar implements Comparable<Calendar>{...}

class GrogorianCalendar extends Calendar

implements Comparable<GregorianCalendar>

{...} // ERROR

泛型类型的继承与通配符

1、通常,无论类型S与类型T之间是什么关系,泛型类Pair<S>Pair<T>没有什么联系。

2、永远可以将参数化类型的对象转换为原始类型,在与遗留代码衔接时,这个转换非常有必要,但是,转成原始类型后,有可能会出现类型错误例如:

Pair<Manager> managerBuddies = new Pair<>(ceo, cfo);

Pair rawBuddies = managerBuddies; //OK

rawBuddies.setFirst(new File(...));

// only a compile-time warning.

3、泛型类可以拓展或实现其他的泛型类,如下图:

Java复习笔记(9)——泛型 

4、使用通配符可以表示一组泛型类型,例如:Pair<? extends Employee>表示任何类型参数是Employee子类的泛型类,包括Pair<Manager>,但不包括Pair<String>

5、子类型限定通配符泛型类无法向其中的方法传递参数,例如调用Pair<? extends Employee>类中的setFirst方法将引发编译错误:

Pair<Manager> managerBuddies = new Pair<>(ceo, cfo);

Pair<? extends Employee> wildcardBuddies = managerBuddies; //OK

wildcardBuddies.setFirst(lowlyEmployee); // compile-time error.

以上代码编译不通过,因为 ?不可以匹配任何类型。

6、可以以超类型的方式指定通配符限定,如下:

? super Manager

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

7、超类型限定通配符泛型无法提供返回值,例如,不可以调用Pair<? super Manager>getFirst方法:

? super Manager getFirst();

因为编译器不知道返回值的确切类型,只能把它赋给一个Object(不会编译出错)。

8、直观地讲,带有超类型限定的通配符可以向泛型类写入(但是,可写入null),带有子类型对象的通配符可从泛型类中读取。

9、还可以使用无限定通配符,例如:Pair<?>,它和原始的Pair类不同。Pair<?>类的getter方法只能赋给ObjectSetter方法则不能调用,甚至不能用Object调用。

10、通配符不是类型变量,因此,不能在编写代码中使用“?”作为一种类型,下述代码是非法的:

public static void swap(Pair<?> p) {

? t = p.getFirst() // ERROR

p.setFirst(p.getSecond);

p.setSecond(t);

}

11、可以通过一个辅助方法来解决上述通配符捕获的问题:

public static <T> void swapHelper(Pair<T> p) {

T t = p.getFirst();

p.setFirst(p.getSecond());

p.setSecond(t);

}

然后,调用该方法来解决上述问题:

public static void swap(Pair<?> p) {

swapHelper(p);

}

12、在某些情况下,通配符捕获机制是不可避免的:

public static void maximinBonus(Manager[] a,

Pair<? super Manager> result) {

minmax(a, result);

PairAlg.swap(result);

// OK--swapHelper captures wildcard type

}

13、通配符捕获只有在许多限制的情况下才是合法的。编译器必须能够确信通配符表达的是单个、确定的类型。例如,ArrayList<Pair<T>>中的T永远不能捕获ArrayList<Pair<?>>中的通配符。数组列表中可以保存两个Pair<?>,分别针对?的不同类型。

反射和泛型

1、Class类是泛型的。例如,String.class实际上是一个Class<String>类的对象,并且是唯一的对象。

2、Class 类中的泛型方法:

T newInstance();

T cast(Object obj)

T[] getEnumConstants();

Class<? super T> getSuperClass()

Constructor<T> getConstructor(Class... parameterTypes);

Constructor<T> getDeclaredConstructor(Class.. parameterTypes);

3、常用方法:

java.lang.Class<T> 1.0

·T newInstance()

返回默认构造器构造的一个新实例。

·T cast(Object obj) 5.0

如果objnull或有可能转换成类型T,则返回obj

否则抛出BadCastException异常。

·T[] getEnumConstants() 5.0

如果T是枚举类型,则返回所有值组成的数组,否则返回null

·Class<? super T> getSuperclass() 5.0

返回这个类的超类,如果T不是一个类或Object类,则返回null

·Constructor<T> getConstructor(Class... parametertypes) 5.0

·Constructor<T> getDeclaredConstructor(Class... parametertypes) 5.0

获得共有的构造器或带有指定参数类型的构造器。

java.lang.reflect.Constructor<T> 1.1

·T newInstance(Object ... paramters) 5.0

返回用指定参数构造的新实例。

4、为了表达泛型类型声明,JavaSE 5.0在java.lang.reflect包中提供了一个新的接口Type。这个接口包含下列子类型:

·Class类,描述具体类型。

·TypeVariable接口,描述类型变量(如T extends Comparable<? super T>)。

·WildcardType接口,描述通配符(如? super T)。

·ParameterizedType接口,描述泛型类或接口类型(如Comparable<? super T>)。

·GenericArrayType接口,描述泛型数组(如T[])。

 Java复习笔记(9)——泛型

5、常用方法:

java.lang.Class<T> 1.0

·TypeVariable[] getTypeParameters() 5.0

如果这个类型被声明为泛型类型,则获得泛型类型变量,否则获得一个长度为0的数组。

·Type getGenericSuperclass() 5.0

获得被声明为这一类型的超类的泛型类型;如果这个类型是Object或不是一个类类型(class type),则返回null

·Type[] getGenericInterfaces() 5.0

获得被声明为这个类型的接口的泛型类型(以声明次序),否则,如果这个类没有实现接口,返回长度为0的数组。

java.lang.reflect.Method 1.1

·TypeVariable[] getTypeParameters() 5.0

如果这个方法被声明为泛型方法,则获得泛型类型变量,否则返回长度为0的数组。

·Type getGenericReturnType() 5.0

获得这个方法被声明为泛型的返回类型。

·Type[] getGenericParameterTypes() 5.0

获得这个方法被声明的泛型参数类型。如果这个方法没有参数,返回长度为0的数组。

java.lang.reflect.TypeVariable 5.0

·String getName()

返回类型变量的名字。

·Type[] getBounds()

获得这个类型变量的子类限定,否则,如果该变量无限定,则返回长度为0的数组。

java.lang.reflect.WildcardType 5.0

·Type[] getUpperBounds()

获得这个类型变量的子类(extends)限定,否则,如果没有子类限定,则返回长度为0的数组。

·Type[] getLowerBounds()

获得这个类型变量的超类(super)限定,否则,如果没有超类限定,则返回长度为0的数组。

java.lang.reflect.ParameterizedType 5.0

·Type getRowType()

获得这个参数化类型的原始类型。

·Type[] getActualTypeArguments()

获得这个参数化类型声明时所使用的类型参数。

·Type getOwnerType()

如果是内部类型,则返回其外部类型,如果是一个*类型,则返回null

java.lang.reflect.GenericArrayType 5.0

·Type getGenericComponentType()

获得声明该数组类型的泛型组合类型。