由于这是总结性的,所以须要你之前已经接触过泛型。至少应该写过操作过集合元素的代码。


Java的一个聚集对象能够存储多种类型的数据,比方Set中能够存整数也能够存字符串。没有泛型之前有下面两个问题:

  • 从聚集(collection)中取出一个元素,必须把这个元素转换为详细类型。
  • 插入元素时。编译器不检查你插入的对象的类型与聚集中元素的类型是否同样。

有泛型之前:

List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0);

有泛型之后:

List<String> list = newArrayList<String>();
list.add("hello");
String s = list.get(0);   // no cast

泛型的作用就是告诉编译器你希望聚集中的元素是什么类型,这样的优点有两:编译器能够在编译阶段告诉你你要存入聚集的元素类型是否正确;从聚集中取出元素时元素不须要强制转换,由于编译器知道聚集中的元素类型。

泛型定义

所谓泛型是指数据的类型非常宽泛,在定义方法的时候我们也不确定这种方法将来会操作什么详细类型的数据。因此,才有了泛型的概念。通过定义泛型。就能够使类、接口或方法能够接受随意类型或多种类型的參数。

定义泛型,我们常会碰到<?>或<T>。当中<T>中的T能够是满足类名命名规范的随意字符串。只是为了简化代码,通常是用一个大写的字母表示。因此经常使用的类型參数名称有:

  • E - Element
  • K - Key
  • N - Number
  • T - Type
  • V - Value

<>有点像方法的括号(),T就是參数名。由于使用泛型的时候T必须被详细的类替换。

泛型 - Generic Type

Java中的泛型是指有类型參数的类或接口,定义格式例如以下:

class ClassName<T1, T2, ..., Tn> { /* ... */ }

上面这段代码声明在这个类中有n个不确定的数据类型。

当我们须要使用这个类时,我们就会用详细的类来替换这些类型參数,告诉这个对象。在这里。你将操作的是这些类型的数据。比方集合的定义是Set<E>,当我们使用集合时,就须要替换这个E參数:

Set<Integer> intSet;

这个样例中。用Integer替换T,表明这个Set集合我要用来存储整数。

泛型相应接口Type,都是Type的实现类。

原始类型 – Raw Type

尽管泛型声明了类型參数,但假设使用时不设置类型參数。那么这个泛型就变成了原始类型。

比方Set就是Set<T>的原始类型。但须要注意。原始类型仅仅相对泛型而言,非泛型的类或接口都不是原始类型。也就是说假设类或接口的定义没有相似Name<T>结构的都不是原始类型。

泛方法

在泛型应用之前,方法的參数类型都是确定的,假设要操作不同类型的參数就须要定义多个方法。假设方法參数的类型也能够參数化,就能够用一个方法操作多种类型的參数。

泛方法的泛类型參数需在返回类型前定义:

public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) {

    returnp1.getKey().equals(p2.getKey()) &&

           p1.getValue().equals(p2.getValue());

}

调用举例:

Pair<Integer, String> p1 = new Pair<>(1,"apple");

Pair<Integer, String> p2 = new Pair<>(2,"pear");

boolean same = Util.<Integer, String>compare(p1,p2);

// boolean same = Util.compare(p1, p2); // this worksin JDK1.8

受限的类型參数-上界

由于泛型定义的时候是不指定详细类型的。所以使用的时候,在默认情况下能够接受随意类型。但有时候我们须要给能接受的类型设置一个范围。比方我这种方法仅仅接受Number或它的子类。假设我们能够这样定义,那么Number就是能接受的类型的上界。

public <U extends Number> void inspect(U u){

    System.out.println("U:" + u.getClass().getName());

}

定义上界的还有一个优点:既然由于已知上界,就能够使用上界的属性和方法。

前面仅仅举了上界为单个类的样例。事实上上界还能够更严格,比方:

<T extends B1 & B2 &B3>

这个样例中。T必须是B1。B2和B3的子类。一般来说要么B1、B2和B3都是接口,要么B1是类。B2和B3是接口。假设B1是类就必须放最前面。

注:<T>没有下界,仅仅有通配类型<?>才有下界

子类型

知道了类型上界。当你看到List<Integer>和List<Number>,你会不会觉得List<Integer>是List<Number>的子类型?告诉你。这是一种大错特错的想法。

子类型除了继承或实现关系,还需满足<>中的类型參数同样,或有扩展:

所以,List<Integer>和List<Number>是两个平行的类,在方法參数是List<Number>的地方,不能用List<Integer>对象赋值。

通配类型

依据子类型的解释,你应该已经明确方法參数是List<Number>的地方不能接受List<Integer>对象实例。

但假设你确实须要使一个方法就可以接受List<Number>对象做參数又能够让List<integer>做參数。你就须要使用通配类型。

通配类型的參数名是“?

”,须要注意:通配符仅仅能用在方法參数、变量或字段的类型上。不能用于定义类。

另外,仅仅有通配符才有无界和下界,其它类型仅仅有上界

由于通配类型不能定义类。所以在工具方法中用的最多。对象的方法一般不用通配类型。

通配上界

通配类型的上界格式:

List<? extends Number>

方法的參数是上面格式,就能够接受List<Integer>、List<Double>等对象作为參数。

无界通配类型

无界通配,也就是说不论什么类型都能够。无界通配类型的格式:

List<?>

使用这样的格式一般有两种情况:

  • 你写的方法使用Object的属性和方法就能够。
  • 你写的方法的功能与參数的类型没有关系。

通配下界

下界是指方法參数能够是该下界,也能够是该下界的父类对象。通配类型的下界格式:

List<? super Integer>

方法的參数是上面格式,能够接受List<Integer>、List<Number>、List<Object>对象。

泛型擦除器

泛型作为一种编译信息仅仅在编译时存在,编译完毕之后即被编译器擦掉。这样做的理由是保证泛型代码和没有泛型參数的旧代码兼容。缺点是执行时没法获取參数的泛型信息,自己主动产生的类型转换与旧代码可能不兼容。

聚集是不保证聚集中的元素都是同样类型的,比方一个字符串集合中能够插入整数。

由于泛型仅仅有编译时存在。所以执行时插入不同类型的元素不会出问题。

但执行时再获取插入的元素时则会导致类型转换异常:

Set strSet = new HashSet();

strSet.add(newInteger(2));

strSet.add("hello");

为了解决执行时的类型匹配问题。聚集类又提供了一些包装类来确保执行时的安全:

Set<String> s =Collections.checkedSet(new HashSet<String>(), String.class);

这些包装类后面的逻辑无非是在插入数据时实时检查插入的元素类型是否与聚集要求的元素类型同样。

可想而知,检查是要付出代价的,于是拖慢了执行速度。因此,一般来说这些安全的包装类一般仅仅用于调试,到了真正的生产环境最好去掉。

GenericDeclaration声明

获取类或方法上的泛型參数。仅仅需调用类或方法的getTypeParameters()方法就可以。

眼下仅仅有Class、Method和Counstructor实现了GenericDeclaration接口,所以仅仅有在Class、Method和Countructor上能够调用getTypeParameters()方法。

类型变量- TypeVariable接口

类型变量是泛型以后出来才有的概念,它并不表示类型能够作为变量。而是指某个类或方法将来会操作这样的类型的变量。

当中方法getTypeParameters()返回的就是类或方法上的全部泛型变量。

因此。类型变量和类型声明通常是在一起使用的。

GenericDeclaration接口 & Type接口 & Class的差别

  • GenericDeclaration和Type是两个平行的接口。
  • Class是GenericDeclaration接口和Type接口的实现类。因此,一个对象假设是Type(或GenericDeclaration),则不一定是Class;假设是Class则一定也是Type(或GenericDeclaration)。

  • 尽管Class实现了GenericDeclaration,但Class能够不声明泛型。

Type

Type是尾随泛型而生的,有了泛型。參数的类型变的不再确定。可能是Class也可能是接口。所以。Type成了Class的父接口,用于表示不确定的类。Type是Java中全部类型(Name<T>)的超级接口,包含原始类型、參数类型、数组类型、类型变量和基本类型。

參数化类 - ParameterizedType

ParameterizedType是Type的子接口,用于表示相似Collection<String>的类,最经常使用于反射中。这个类的实例必须实现equals()方法,用于比較两个拥有同样泛型声明和同样类型參数的实例。

和Type的差别:

Type是指Class<Type>中的Type,而ParameterizedType指的是Class<Colleciton<Type>>中的Collection<Type>。

泛型数组类-GenericArrayType

这样的表示这个类是一个数组,而数组的元素要么是ParameterizedType,要么是TypeVariable。

通配类 – WildcardType

通配类型表示一个通配类型表达式:比方?

,? extends Number等。