一、简介

泛型是指在定义类、接口或方法时将所操作的数据类型指定为一个参数(类型形参),这个参数在声明变量、创建对象时确定;类型形参在整个类或接口中可以当成类型使用。

二、泛型

1、泛型接口

public interface IGeneric<E> {
	public E doSomething(E x);
}

2、泛型类

public class Counter<T> {

	private T count;
	
	public Counter(T initial) {
		this.count = initial;
	}
	
	public void add(T t){
		
	}

	public T getCount() {
		return this.count;
	}
}
Counter<String> counter = new Counter<String>("零");
System.out.println(counter.getCount());

Counter<Integer> intCounter = new Counter<Integer>(0);
System.out.println(intCounter.getCount());
  • 派生子类

子类仍为泛型类:

public class ChildCounter<T> extends Counter<T>{

	public ChildCounter(T initial) {
		super(initial);
	}

	@Override
	public T getCount() {
		return super.getCount();
	}
}

子类使用具体的类型:

public class StringCounter extends Counter<String>{

	public StringCounter(String initial) {
		super(initial);
	}

	@Override
	public String getCount() {
		return super.getCount();
	}
}
  • 说明

使用泛型可以让不同的类型参数具有相同的行为,从而可以把相同的类当成许多不同的类来处理。由于静态变量、静态方法是所有实例共享的,因此在静态变量、静态方法、静态初始化块中不允许使用类型参数。

另外,由于并不存在泛型类,因此,instanceof运算符后面不能使用泛型:

正确的写法为:

if(list instanceof List<?>){ }
//或
if(list instanceof List){ }

3、泛型方法

在声明方法时也可以定义一个或多个类型参数,格式如下:

修饰符 <T, S> 返回值类型 方法名(形参列表){

}

与普通方法声明相比,多了对类型参数的声明,类型参数的声明放在方法修饰符和返回值之间,多个类型参数之间以逗号分隔。

public <T> void copy(T[] from, Collection<T> to){
	for(T t : from){
		to.add(t);
	}
}

与类、接口使用泛型参数不同,在方法中使用泛型参数不需要显式指定类型实参,因为编译器会根据传入的实参判断类型形参的值。

4、泛型擦除

泛型擦除是指当把一个具有泛型信息的对象赋值给另一个没有泛型信息的对象时,类型信息会丢失。例如:将一个List<String>对象转为List时,该List中的集合元素类型变成了其上限类型(Object)。

  • 样例:
List<Integer> list = new ArrayList<>();
list.add(1);
List ls = list;
List<String> intList = ls;
System.out.println(intList.get(0));

运行上面的代码时,会在最后一行报错:java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

三、类型通配符

类型通配符是一个问号(?),例如:List<?>表示一个存放未知类型元素的List,?可以匹配任何类型。

  • 未使用通配符

  • 使用通配符

从上面的例子中可以看出:如果Foo类是Bar类的一个子类,G为具有泛型声明的接口或类,那么G<Foo>产不是G<Bar>的子类,这与数组不同,Foo[]依然是Bar[]的子类。

1、设置通配符上限

  • 未设置上限
public static void print(List<?> list){
	for(Object object : list){
		Fruit fruit = (Fruit) object;
		System.out.println(fruit.getInfo());
	}
}

public static void main(String[] args) {
	List<Fruit> fruits = new ArrayList<>();
	fruits.add(new Apple());
	fruits.add(new Lemon());
	print(fruits);
}
  • 设置上限

    可以在声明泛型的接口或类的尖括号(<>)中使用extends关键字指定上限:

      public class NumberCounter<T extends Number>{}
    

    可以设置多个上限,但最多只能有一个父类上限,可以有多个接口上限:

      public class NumberCounter<T extends Number & Serializable & Cloneable>{}
    
    • 方法
      public static void print(List<? extends Fruit> list){
          for(Fruit fruit : list){
              System.out.println(fruit.getInfo());
          }
      }
    

    与前面类似的是,设置上限的List<? extends Fruit>集合中的?依然是一个未知类型,因此,不能把Fruit类或其子类对象添加到这个集合中:

2、设置通配符下限

Java使用<? super Type>来设定通配符的下限,?表示的类型是Type本身或是Type的父类型。

  • 使用上限的不足之处
public static <T> T copy(List<? extends T> from, List<T> to){
	T last = null;
	for(T t : from){
		to.add(last = t);
	}
	return last;
}

使用上面的copy方法拷贝时,返回最后的一个元素,但此时仍会有编译错误,因为返回的是父类类型。

  • 使用通配符下限

使用通配符下限时,指定了返回值类型为子类型,因此不会出现编译错误。

public static <T> T copy(List<T> from, List<? super T> to){
	T last = null;
	for(T t : from){
		to.add(last = t);
	}
	return last;
}

public static void main(String[] args) {
	List<Fruit> fruits = new ArrayList<>();
	List<Apple> apples = new ArrayList<>();
	apples.add(new Apple());
	Apple apple = copy(apples, fruits);
}
参考资料:
  • 《疯狂Java》