Java generics

简介

泛型程序设计是程序设计语言的一种风格或范式。允许程序员在强类型程序设计语言中编写代码时使用一些以后才指定的类型,在实例化时作为参数指明这些类型。

Java generics 是JDK 5中引入的一个新特性,允许在定义类和接口的时候使用类型参数(type parameter)。声明的类型参数在使用时用具体的类型来替换。

从好的方面来说,泛型的引入可以解决之前的集合类框架在使用过程中通常会出现的运行时候类型错误,因为编译器可以在编译时刻就发现很多明显的错误。

泛型的本质是参数化类型( Parameterized Type)的应用,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口和泛型方法。

Java语言中的泛型只在程序源码中存在,在编译后的字节码文件中,就已经被替换为原来的原生类型(Raw Type)了,并且在响应的地方插入了强制转型代码,Java语言中的泛型实现方法称为类型擦除,基于这种方法实现的泛型被称为伪泛型。

Why use generics?

In a nutshell, generics enable types (classes and interfaces) to be parameters when defining classes, interfaces and methods. Much like the more familiar formal parameters used in method declarations, type parameters provide a way for you to re-use the same code with different inputs. The difference is that the inputs to formal parameters are values, while the inputs to type parameters are types.

Generic Types

The most commonly used type parameter names are:

  • E - Element (used extensively by the Java Collections Framework)
  • K - Key
  • N - Number
  • T - Type
  • V - Value
  • S,U,V etc. - 2nd, 3rd, 4th types

Type Parameter and Type Argument Terminology: Many developers use the terms “type parameter” and “type argument” interchangeably, but these terms are not the same. When coding, one provides type arguments in order to create a parameterized type. Therefore, the T in Foo is a type parameter and the String in Foo f is a type argument. This lesson observes this definition when using these terms.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Box<T> {
public T t;

public void set(T t){
this.t = t;
}

public T get(){
return t;
}

public static void main(String[] args) {
Box<Integer>integerBox = new Box<Integer>();
integerBox.set(100000);
System.out.println(integerBox.get());
Box<Long>longBox = new Box<Long>();
longBox.set(100000l);
System.out.println(longBox.get());
Box<String>stringBox = new Box<String>();
stringBox.set("100000");
System.out.println(stringBox.get());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public interface Pair<K, V> {
public K getKey();
public V getValue();
}

public class OrderedPair<K, V> implements Pair<K, V>{

private K k;
private V v;

public OrderedPair(K k, V v) {
this.k = k;
this.v = v;
}

@Override
public K getKey() {
return k;
}

@Override
public V getValue() {
return v;
}

public static void main(String[] args) {
Pair<String, Integer> p1 = new OrderedPair<String, Integer>("Even", 8);
Pair<String, String> p2 = new OrderedPair<String, String>("hello", "world");
}

}

Wildcard Guidelines:

An “in” variable is defined with an upper bounded wildcard, using the extends keyword.

An “out” variable is defined with a lower bounded wildcard, using the super keyword.

In the case where the “in” variable can be accessed using methods defined in the Object class, use an unbounded wildcard.

In the case where the code needs to access the variable as both an “in” and an “out” variable, do not use a wildcard.

类型擦除(type erasure)

Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节代码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉。这个过程就称为类型擦除。

通配符与上下界

在使用泛型类的时候,即可以指定一个具体的类型,也可以通过通配符?来表示未知类型,如List<?>就表明了List中包含的元素类型是未知的。通配符所赛标的其实是一组类型,但具体的类型是未知的。

对于List<?>中的元素只能用Object来引用,在有些情况下不是很方便。在这些情况下,可以使用上下界来限制未知类型的范围。如List<? extends Number>说明List中可能包含的元素类型是Number及其子类。而List<? super Number>则说明List中包含的是Number及其父类。当引入了上界之后,在使用类型的时候就可以使用上界类中定义的方法。比如访问 List<? extends Number>的时候,就可以使用Number类的intValue等方法。

泛型不是协变的

Java 语言中的数组是协变的(covariant),也就是说,如果 Integer扩展了 Number(事实也是如此),那么不仅 Integer是 Number,而且 Integer[]也是 Number[],在要求 Number[]的地方完全可以传递或者赋予 Integer[]。(更正式地说,如果 Number是 Integer的超类型,那么 Number[]也是 Integer[]的超类型)。您也许认为这一原理同样适用于泛型类型 —— List是 List的超类型,那么可以在需要 List的地方传递 List。不幸的是,情况并非如此。

这样做将破坏要提供的类型安全泛型。

Restrictions on Generics

  1. Cannot Instantiate Generic Types with Primitive Types不能用原始类型实例泛型类型。
  2. Cannot Create Instances of Type Parameters不能使用Type Parameters创建实例
  3. Cannot Declare Static Fields Whose Types are Type Parameters不能为Type Parameters类型生命为静态Fields.
  4. Cannot Use Casts or instanceof with Parameterized Types不能在Parameterized Types中使用类型转换或者instanceof
  5. Cannot Create Arrays of Parameterized Types不能创建Parameterized Types的数组
  6. Cannot Create, Catch, or Throw Objects of Parameterized Types不能为Parameterized Types创建,捕捉或者Throw Objects
  7. Cannot Overload a Method Where the Formal Parameter Types of Each Overload Erase to the Same Raw Type

在使用泛型的时候可以遵循一些基本的原则,从而避免一些常见的问题。

1.在代码中避免泛型类和原始类型的混用。比如List和List不应该共同使用。这样会产生一些编译器警告和潜在的运行时异常。当需要利用JDK 5之前开发的遗留代码,而不得不这么做时,也尽可能的隔离相关的代码。

2.在使用带通配符的泛型类的时候,需要明确通配符所代表的一组类型的概念。由于具体的类型是未知的,很多操作是不允许的。

3.泛型类最好不要同数组一块使用。你只能创建new List<?>[10]这样的数组,无法创建new List[10]这样的。这限制了数组的使用能力,而且会带来很多费解的问题。因此,当需要类似数组的功能时候,使用集合类即可。

4.不要忽视编译器给出的警告信息。

References

http://en.wikipedia.org/wiki/Generics_in_Java

http://www.infoq.com/cn/articles/cf-java-generics

http://docs.oracle.com/javase/tutorial/java/generics/

http://en.wikipedia.org/wiki/Liskov_substitution_principle

More than your eyes can see