Java5增加泛型支持在很大程度上都是为了让集合能记住其元素的数据类型。在没有泛型之前,一旦把一个对象放进集合中,集合就会忘记对象的类型,把所有的对象当成Object类型处理。当程序从集合中取出对象后,就需要进行强制类型转换。而泛型可以让集合记住其元素的数据类型。
一、认识泛型
java7之前的语法:
List<String> strList = new ArrayList<String>();
Map<String,String> scores = new HashMap<String,String>();
从Java7开始使用菱形语法:
List<String> strList = new ArrayList<>()
二、深入泛型
泛型:允许在定义类、接口、方法时使用类型形参,这个类型形参将在声明变量、创建对象、调用方法时动态地指定。Java5为集合框架中的全部接口和类增加了泛型支持。
定义泛型接口、类
//定义接口时指定类型形参
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){
}
}
Iterator<E>
、Set<K>
表面他们是一种特殊的数据类型,是一种和Iterator
、Set
不同的数据类型,可以认为是他们的子类。
包含泛型声明的类型可以在定义变量、创建对象时传入一个类型参数,从而可以动态地生成无数多个逻辑上的子类,但这种子类在物理上并不存在。
创建带泛型声明的自定义类,为该类定义构造器时,构造器名还是原来的类名,不要增加泛型声明。
从泛型类派生子类
//错误的
public class A extends Apple<T>{}
//正确的
public class A extends Apple<String>{}
//会出现警告
public class A extends Apple{}
并不存在泛型类
//l1.getClass()==l2.getClass()
List<String> l1 = new ArrayList<>();
List<String> l1 = new ArrayList<>();
不管为泛型形参传入哪一种类型的实参,他们依然被当作同一个类处理,在内存中也只占用一块内存空间,因此在静态方法、静态初始化块或者静态变量的声明和初始化中不允许使用类型形参。
public class R<T>{
//错误,不能在静态变量声明中使用类型形参
static T info;
T age;
public void foo(T msg){}
//错误,不能在静态方法声明中使用类型形参
public static void var(T msg){}
}
java.util.Collection<String> cs = new java.util.ArrayList<>();
//错误,并不会生成泛型类
if (cs instanceof java.util.ArrayList<String>){}
三、类型通配符
//会有泛型警告
public void test(List c){
for(int i=0; i<c.size(); i++){
System.out.println(c.get(i));
}
}
改成
//会有泛型警告
public void test(List<Object> c){
for(int i=0; i<c.size(); i++){
System.out.println(c.get(i));
}
}
List<String> str = new ArrayList<>();
test(str);
上面代码产生异常。
无法将Test中的test(java.util.List<java.lang.Object>)应用于(java.util.List<java.lang.String>)
如果Foo
是Bar
的子类型(子类或者子接口),而G
是一个具有泛型声明的类或接口,G<Foo>
并不是G<Bar>
的子类型。
数组与泛型不同,Foo[]
是bar[]
的子类型。
使用类型通配符
//其类型为Object
public void test(List<?> c){
for(int i=0; i<c.size(); i++){
System.out.println(c.get(i));
}
}
c中包含的是Object
。
这种带通配符的List仅表明它是各种泛型List的父类,并不能把元素加入进去。
List<?> c = new ArrayList<String>();
//编译错误
c.add(new Object());
c中放的类型是Object
,而add的参数是E
类的对象或者其子类的对象。该例中不知道E
是什么类型,产生编译错误。null
是所有引用类型的实例,它可以添加进去。
get返回值是一个未知类型,可以赋值给Object
类型的变量。
设定类型通配符的上限
public abstract class Shape{
public abstract void draw();
}
public class Circle extends Shape{
public void draw(Canvas c){
System.out.println(c+"圆");
}
}
public class Rectangle extends Shape{
public void draw(Canvas c){
System.out.println(c+"长方形");
}
}
public class Canvas{
public void drawAll(List<? extends Shape> shapes){
for (Shape shape:shapes){
shape.draw(this);
}
}
}
List<Circle> c = new ArrayList<>();
Canvas cv = new Canvas();
c.drawAll(c);
以上程序会将List<Circle>
对象当成List<? extends Shape>
使用。List<? extends Shape>
可以表示他们的父类--只要List尖括号里的类型是Shape
的子类型即可。
当前程序无法确定这个受限制的通配符的具体类型。所以不能把Shape
对象或者其子类型的对象加入这个泛型集合中。
设定类型形参的上限
public class Apple<T extends Number>{
T col;
public static void main(String[] args){
Apple<Integer> a = new Apple<Integer>();
//报错
Apple<String> b = new Apple<String>();
}
}
程序可以为类型形参设置多个上限(至多一个父类上限,可以有多个接口上限)。
public class Apple<T extends Number&java.io.Serializable>{
}
和类同时继承父类和实现接口相似,所有的接口上限必须位于类上限之后。
四、泛型方法
泛型方法的格式如下:
修饰符 <T,S> 返回值类型 方法名(形参){
//方法体
}
import java.util.ArrayList;
import java.util.Collection;
import com.sun.org.apache.xpath.internal.operations.Number;
public class GenericMethodTest{
static <T> void fromArrayToCollection(T[] t, Collection<T> c){
for(T o: t){
c.add(o);
}
}
public static void main(String[] args) {
Object[] oa = new Object[100];
Collection<Object> co = new ArrayList<>();
String[] sa = new String[100];
Collection<String> cs = new ArrayList<>();
//T为Object类型
fromArrayToCollection(sa, co);
Integer[] ia = new Integer();
Collection<Number> cn = new ArrayList<>();
//T为Number类型
fromArrayToCollection(ia, cn);
}
}
泛型方法和类型通配符的区别
大多数时候可以使用泛型方法代替类型通配符。
public interface Collection<E>{
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);
...
}
采用泛型方法实现:
public interface Collection<E>{
<T> boolean containsAll(Collection<T> c);
<T extends E> boolean addAll(Collection<T> c);
...
}
通配符被设计用来支持灵活的子类化的。
泛型方法允许类型形参被用来表示方法的一个或多个参数之间的类型依赖关系,或者方法返回值与参数之间的类型依赖关系。如果没有这样的类型依赖关系,就不应该使用泛型方法。
如果又需要,也可以同时使用泛型方法和通配符:
public class Collections{
public static <T> void copy(List<T> dest, List<? extends T> src){}
}
可以使用下面的泛型方法替换:
public static <T, S extends T> void copy(List<T> dest, List<S> src)
S
仅使用了一次,其他参数的类型和方法返回值都不依赖于它,没有存在的必要,可以用通配符替换。
显著区别:
类型通配符既可以在方法签名中定义形参的类型,也可以用于定义变量的类型;但泛型方法中的类型形参必须在对应方法中显示声明。
菱形语法与泛型构造器
java允许在构造器签名中声明类型形参。
class Foo{
public <T> Foo(T t){
...
}
}
class Foo <E>{
public <T> Foo(T t){
}
}
public class GenericTest{
public static void main(String[] args){
Foo<String> s1 = new Foo<>(5);
Foo<String> s2 = new <Integer> Foo<String>(5);
//下面代码出错
Foo<String> s3 = new <Integer> Foo<>();
}
}
设定通配符下限
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
int srcSize = src.size();
if (srcSize > dest.size())
throw new IndexOutOfBoundsException("Source does not fit in dest");
if (srcSize < COPY_THRESHOLD ||
(src instanceof RandomAccess && dest instanceof RandomAccess)) {
for (int i=0; i<srcSize; i++)
dest.set(i, src.get(i));
} else {
ListIterator<? super T> di=dest.listIterator();
ListIterator<? extends T> si=src.listIterator();
for (int i=0; i<srcSize; i++) {
di.next();
di.set(si.next());
}
}
}
java8改进的类型推断
- 可通过调用方法的上下文来推断类型参数的目标类型
- 可在方法调用链中,将推断得到的类型参数传递到最后一个方法。
五、擦除和转换
如果没有为一个泛型类制定实际的类型参数,则该类型参数被成为raw type
,默认是声明该类型参数时制定的第一个上限类型。
public class Test{
public static void main(String[] args){
List<Integer> li = new ArrayList<>();
List list=li;
//擦除,提示"未经检查的转换"
List<String> li = list;
//运行时异常
System.out.println(li.get(0));
}
}
六、泛型与数组
数组的类型不可以是类型变量,除非是采用通配符的方式。
数组元素类型不能包含泛型变量或者泛型形参,除非是无上限的泛型通配符,但可以声明元素类型包含泛型变量或泛型形参的数组。
//不允许
List<String>[] lsa = new List<String>[10];
//允许
List<String>[] lsa = new ArrayList[10];
Object[] oa = (Object[])lsa;
List<integer> li = new ArrayList<>();
li.add(3);
oa[1] = li;
//下面代码将不会有警告,但引发ClassCastException
String s = lsa[1].get(0);
Object target = lsa[1].get(0);
if(target instanceof String){
String s =(String)target;
}
创建元素类型是泛型变量的数组对象也会导致编译错误:
<T> T[] makeArray(Collection<T> coll){
//下面语句出错
return new T[coll.size()];
}