在集合中,集合是不知道向里面添加的是什么的,为了通用性,全都作为Object,导致取出来后要进行强制类型转换.而泛型允许程序在创建集合时指定集合元素的类型.

泛型入门

使用泛型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.util.ArrayList;

/**
* @author YL
*/
public class TestGeneric {
public static void main(String[] args) {
ArrayList<String> strList = new ArrayList<String>();
strList.add("你好");
strList.add("再见");
strList.forEach(str -> System.out.println(str.length()));
}
}

上面程序成功创建了一个特殊的List集合:strList,这个List集合只能保存字符串对象,不能保存其他类型的对象。创建这种特殊集合的方法是:在集合接口、类后增加尖括号,尖括号里放一个数据类型,即表明这个集合接口、集合类只能保存特定类型的对象.

在上面的代码中我指定了集合所能装入的类型:String.这样就可以避免一不小心加入类型错误的元素,而且程序 更加简洁,集合自动记住所有集合元素的数据类型,从而无须对集合元素进行强制类型转换.泛型从JDK5开始支持.

JDK7中的变化

在jdk7中,不需要这么写了ArrayList<String> strList = new ArrayList<String>();,可以把后面一个尖括号里的内容省略:ArrayList<String> strList = new ArrayList<>();.

深入泛型

所谓泛型,就是允许在定义类、接口、方法时使用类型形参,这个类型形参将在声明变量、创建对象、调用方法时动态地指定(即传入实际的类型参数,也可称为类型实参).

定义泛型接口,类

下面是JDK5改写之后的List接口,Iterator接口,Map接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public interface List<E>
{
void add(E x);
Iterator<E> Iterator();
...
}
public interface Iterator<E>
{
E next();
boolean hasNext();
...
}
public interface Map<K , V>
{
Set<K> keyset();
V put(K key, V value);
...
}

观察发现:泛型允许在定义接口、类时声明类型形参,类型形参在整个接口、类体内可当成类型使用,几乎所有可使用普通类型的地方都可以使用这种类型形参。

我们可以将ArrayList<String>理解为ArrayList的一个子类,但仅仅是这么理解,实际上并没有产生这种子类.我倾向于把它解释为一种”标记”.

泛型不仅仅只是用于集合,任何类,接口都可以增加泛型声明.

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
public class Apple<T>
{
private T info;
public Apple(){}
public Apple(T info)
{
this.info = info;
}
public void setlnfo(T info)
{
this.info = info;
}
public T getlnfo()
{
return this.info;
}
public static void main(String[] args)
{
/ / 由于传给T 形参的是String,所以构造器参数只能是String
Apple<String> al = new Apple<>("苹果") ;
System.out.println(al.getlnfo());
/ / 由于传给T 形参的是Double,所以构造器参数只能是Double或double
Apple<Double> a2 = new Apple<>(5.67);
System.out.println(a2.getlnfo());
}
}

注意:构造器名仍是Apple,不是Apple<T>.

从泛型类派生普通子类

从泛型派生子类,泛型类型需具体化

也就是说这种写法是错误的:public class A extends Apple<T>{ }

如果需要这样做有两种方法:

  1. 指定泛型类型(传入实际的类型参数)
1
public class A extends Apple<String>

这种写法会使得Apple类中的T全部替换为String,A继承了这个具体的Apple类.

  1. 不指定泛型类型(不给T传入实际的类型参数)
1
public class A extends Apple

这样子做会使得T形参被当做Object处理.

并不存在泛型类

前面提到可以把ArrayList<String>类当成ArrayList的子类,事实上,ArrayList<String>类也确实像一种特殊的ArrayList类:该 ArrayList<String>对象只能添加String对象作为集合元素。但实际上,系统并没有为ArrayList<String>生成新的class文件,而且也不会把ArrayList<Strin>当成新类来处理。

如果用==来比较ArrayList<String>和ArrayList<Integer>,结果是true.并且对于泛型类的实例,不能用instanceof来检测它是否是ArrayList<T>的实例.

从本质上说,泛型只是为编译器提供了一种编译前的静态检查,在编译时不管你指定的泛型是什么,都会被视作Object,这是为了兼容JDK5之前的代码,这种叫做类型擦除,后文还会再讲.

类型通配符

如果Foo是Bar的一个子类型(子类或者子接口),而 G是具有泛型声明的类或接口,G<Foo>并不是G<Bar>的子类型!这一点非常值得注意,因为它与大部分人的习惯认为是不同的。

数组和泛型有所不同 ,假设Foo是Bar的一个子类型(子类或者子接口),那么Foo[]依然是Bar[]的子类型;但 G<Foo>不是G<Bar>的子类型。

1
2
3
4
Integer[] ia = new Integer[5];
Number[] na = ia;//编译通过,但是na[0]=0.5会出错
List<Integer> iList = new ArrayList();
List<Number> nList = iList;//编译错误

使用类型通配符

既然不能向上面那样表示父类,那就换个东西表示,这就是?.类型通配符是一个问号(?),将一个问号作为类型实参传给List集合,写作:List<?> (意思是元素类型未知的List)。这个问号(?) 被称为通配符, 它的元素类型可以匹配任何类型。所以?其实不是代表父类,而是代表任意,只是效果相同.

1
2
3
List<Integer> iList = new ArrayList();
List<?> nList = iList;
nList.add(new Integer(5));//错误

但是?仅仅是用来表示是父类,但是不能向里面添加任何东西(除了null).我们只能以Object的形式将集合里的元素取出来,却不能加进去.因为它有可能List<Object>,有可能是List<String>等等,而泛型的核心思想是:**把一个集合中的内容限制为一个特定的数据类型.**只要违背了这个原则就会引起编译错误.

泛型方法

在java中,泛型类和接口的定义非常简单,但是泛型方法就比较复杂了。

泛型类,是在实例化类的时候指明泛型的具体类型;泛型方法,是在调用方法的时候指明泛型的具体类型 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 泛型方法的基本介绍
* @param tClass 传入的泛型实参
* @return T 返回值为T类型
* 说明:
* 1)public 与 返回值中间<T>非常重要,可以理解为声明此方法为泛型方法。
* 2)只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
* 3)<T>表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。
* 4)与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。
*/
public <T> T genericMethod(Class<T> tClass)throws InstantiationException ,
IllegalAccessException{
T instance = tClass.newInstance();
return instance;
}

请注意第二条.

泛型方法的基本用法

1
2
3
4
5
public <T> T genericMethod(Class<T> tClass)throws InstantiationException ,
IllegalAccessException{
T instance = tClass.newInstance();
return instance;
}

注意<T>,只有在方法中声明了泛型,这个方法才叫做泛型方法.除此之外的使用泛型类的标识的方法其实不是泛型方法.

也可以在一个泛型类中使用泛型方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Hero<T> implements Serializable {
private static final long serialVersionUID = 1L;
T name;
T hp;

Hero(T name, T hp) {
this.name = name;
this.hp = hp;
}
public T getInfo(){
return name;
}
public <E> E Info(T nmae){
return (E)name;
}
}

静态方法与泛型

静态方法有一种情况需要注意一下,那就是在类中的静态方法使用泛型:静态方法无法访问类上定义的泛型;如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。

即:如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法

因为静态方法是随着类加载就初始化的,而泛型是在实例化时才传入具体的泛型类型:

public static void show(T t){..} 这样是错误的.

泛型上下边界

使用?带来的问题是,它太宽泛了.

? extends

ArrayList heroList<? extends Hero> 表示这是一个Hero泛型或者其子类泛型

heroList的泛型可能是Hero

heroList 的泛型可能是APHero

heroList 的泛型可能是ADHero

所以,可以确凿的是,从heroList取出来的对象,一定是可以转型成Hero的.但是,不能往里面放东西,因为放APHero就不满足<ADHero>,放ADHero又不满足<APHero>.即使只存在Hero和APHero也不行,因为ArrayList<Hero>和ArrayList<APHero>仍然不是一样东西.

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
32
33
package generic;

import java.util.ArrayList;

import charactor.ADHero;
import charactor.APHero;
import charactor.Hero;

public class TestGeneric {

public static void main(String[] args) {

ArrayList<APHero> apHeroList = new ArrayList<APHero>();
apHeroList.add(new APHero());

ArrayList<? extends Hero> heroList = apHeroList;

//? extends Hero 表示这是一个Hero泛型的子类泛型

//heroList 的泛型可以是Hero
//heroList 的泛型可以使APHero
//heroList 的泛型可以使ADHero

//可以确凿的是,从heroList取出来的对象,一定是可以转型成Hero的

Hero h= heroList.get(0);

//但是,不能往里面放东西
heroList.add(new ADHero()); //编译错误,因为heroList的泛型 有可能是APHero

}

}

当然也可以不用这种方式:

1
2
List<Hero> Heros = new ArrayList<>();
Heros.add(new APHero());

这样子也能添加,但取出来的是Hero.

? super

ArrayList heroList<? super Hero>表示这是一个Hero泛型或者其父类泛型

heroList的泛型可能是Hero

heroList的泛型可能是Object.

可以往里面插入Hero以及Hero的子类,但是取出来有风险,因为不确定取出来是Hero还是Object.这和上面的代码效果一样.

总结

  • 如果希望只取出,不插入,就使用? extends Hero
  • 如果希望只插入,不取出,就使用? super Hero
  • 如果希望,又能插入,又能取出,就不要用通配符?

阅读过一些Java集合类的源码,可以发现通常我们会将两者结合起来一起用,比如像下面这样:

1
2
3
4
5
6
public class Collections {
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
for (int i=0; i<src.size(); i++)
dest.set(i, src.get(i));
}
}

有限制泛型

1
2
3
4
5
6
7
8
9
10
public class Node<T extends Comparable<T>> {
private T data;
private Node<T> next;
public Node(T data, Node<T> next) {
this.data = data;
this.next = next;
}
public T getData() { return data; }
// ...
}

类型参数T是有限制的,要求T必须继承Comparable接口,并且这个接口也是被泛型化的.