泛型:允许在定义类,接口,方法时使用类型形参,这个类型形参将在声明变量,创建对象,调用方法时动态指定(类型实参),Java5为集合框架的全部类和接口增加了泛型支持,可以在声明集合变量,创建集合对象时传入类型实参,就是我们经常看到的List
public interface List<E> extends Collection<E> {
Iterator iterator();
boolean add(E var1);
}
public interface Set<E> extends Collection<E> {
Iterator iterator();
boolean add(E var1);
}
public interface Map<K, V> {
V put(K var1, V var2);
Set keySet();
Collection values();
Set> entrySet();
void putAll(Map extends K, ? extends V> var1);
}
泛型实质:除了在定义接口,类时声明类型形参,类型形参在整个接口,类中可当做类型使用。
可以为任何类增加泛型支持,请看下面实例。
// 定义Apple类时使用了泛型声明
public class Apple
{
// 使用T类型形参定义实例变量
private T info;
public Apple(){}
// 下面方法中使用T类型形参来定义构造器
public Apple(T info)
{
this.info = info;
}
public void setInfo(T info)
{
this.info = info;
}
public T getInfo()
{
return this.info;
}
public static void main(String[] args)
{
// 由于传给T形参的是String,所以构造器参数只能是String
Apple a1 = new Apple<>("苹果");
System.out.println(a1.getInfo());
// 由于传给T形参的是Double,所以构造器参数只能是Double或double
Apple a2 = new Apple<>(5.67);
System.out.println(a2.getInfo());
}
}
当创建了带泛型声明的接口和父类时,可以为该接口创建实现类或者为该父类创建子类,但当使用这些父类或接口时不能再包含类型形参。
//Apple类不能跟类型形参
public class A extends Apple<T>
//这种是可以的
public interface List<E> extends Collection<E>
public abstract class TestClass<E> implements List<E>
在定义类,接口,方法时可以声明类型形参,在使用类,接口,方法时应该为类型形参传入实际类型,如下:
public class A extends Apple<String>
//如下示例
public class A1 extends Apple<String>
{
// 正确重写了父类的方法,返回值
// 与父类Apple的返回值完全相同
public String getInfo()
{
return "子类" + super.getInfo();
}
/*
下面方法是错误的,重写父类方法时返回值类型不一致
public Object getInfo()
{
return "子类";
}
*/
}
List<String> l1 = new ArrayList<String>();
List<Double> l2 = new ArrayList<Double>();
System.out.println(l1.getClass() == l2.getClass());
//结果为true,系统不会生成真正的泛型类
注:静态方法,静态初始化块,静态变量的声明和初始化中不允许使用类型形参,如:
public class R
{
// 下面代码错误,不能在静态变量声明中使用类型形参
// static T info;
T age;
public void foo(T msg){}
// 下面代码错误,不能在静态方法声明中使用类型形参
// public static void bar(T msg){}
//instanceof也不可用于判断泛型
}
List<String> list = new ArrayList<String>();
//错误代码
List<Object> obj = list;
List
类型通配符是?,它可以匹配任何类型的元素,如List>,Map,?>等,但这种带通配符的的List表示各种泛型List的父类,不可将元素加入其中。
List> list1 = new ArrayList();
//以下代码是报错的
list1.add("1");
list1.add("2");
list1.add("3");
// 定义一个抽象类Shape
public abstract class Shape
{
public abstract void draw(Canvas c);
}
// 定义Shape的子类Circle
public class Circle extends Shape
{
// 实现画图方法,以打印字符串来模拟画图方法实现
public void draw(Canvas c)
{
System.out.println("在画布" + c + "上画一个圆");
}
}
// 定义Shape的子类Rectangle
public class Rectangle extends Shape
{
// 实现画图方法,以打印字符串来模拟画图方法实现
public void draw(Canvas c)
{
System.out.println("把一个矩形画在画布" + c + "上");
}
}
public class Canvas{
//List不是List的子类,无法添加
public void drawAll(List shapes)
{
for (Shape s : shapes)
{
s.draw(this);
}
}
//类型丢失,需要强转
public void drawAll(List> shapes)
{
for (Object obj : shapes)
{
Shape s = (Shape)obj;
s.draw(this);
}
}
}
//最合理的使用方法,使用类型通配符上限
public void drawAll(List extends Shape> shapes)
{
//由于这里无法确定具体类型,所以不能将Circle添加进去
//list.add(new Circle());
for (Shape s : shapes)
{
s.draw(this);
}
}
泛型不仅允许在使用通配符形参时设定上限,在定义类型形参时也可设定上限。
public class Apple<T extends Number>
{
T col;
public static void main(String[] args)
{
Apple ai = new Apple<>();
Apple ad = new Apple<>();
// 下面代码将引起编译异常,下面代码试图把String类型传给T形参
// 但String不是Number的子类型,所以引发编译错误
Apple as = new Apple<>(); // ①
}
}
//(1)
static void fromArrayToList(Object[] a, List<Object> b) {
for (Object o : a) {
b.add(o);
}
}
//(2)
static void fromArrayToList(Object[] a, List> b) {
for (Object o : a) {
//无法将对象加入通配符集合
//b.add(o);
}
}
(1)
String[] arr = {"a", "b"};
List<String> list = new ArrayList<String>();
//报错,List不是List
//fromArrayToList(arr,list);
泛型方法的格式如下:
修饰符 <T,S> 返回值类型 方法名(形参列表){
}
上面的方法将可以修改成如下方式:
static void fromArrayToList(T[] a, List b) {
for (T o : a) {
b.add(o);
}
}
//调用
String[] arr = {"a", "b"};
List list = new ArrayList();
fromArrayToList(arr, list);
上面的这种方式需要传入的数组和集合是同一种数据类型,也可使用如下方式。
static void fromArrayToList(List extends T> a, List b) {
for (T o : a) {
b.add(o);
}
}
//src元素复制到dest中,dest元素应该是src元素的父类,同时使用通配符和泛型参数来表示
public static T copy(List dest, List extends T> src) {
for (T ele : src) {
dest.add(ele);
}
}
现在假设该方法需返回最后一个被复制的元素,则可以如下写法。
public static T copy(List dest, List extends T> src) {
T last = null;
for (T ele : src) {
last = ele;
dest.add(ele);
}
return last;
}
//调用
List list1 = new ArrayList();
List list2 = new ArrayList();
//这句将有错误
Integer last = copy(list1, list2);
最后返回的元素是Number型,并不是我们需要的Integer型,但实际上复制的最后一个元素确实是Integer型。
通配符下限:
public static T copy(List super T> dest, List src) {
T last = null;
for (T ele : src) {
last = ele;
dest.add(ele);
}
return last;
}
//调用
List list1 = new ArrayList();
List list2 = new ArrayList();
Integer last = copy(list1, list2);
我们来看一下Collections类中原先的copy原型。
public static void copy(List super T> dest,
List extends T> src)
泛型擦除
List<String> list = new ArrayList<String>();
List list1 = list;
泛型常用标记符含义:
E - Element (在集合中使用,因为集合中存放的是元素)
T - Type(Java 类)
K - Key(键)
V - Value(值)
N - Number(数值类型)
? - 表示不确定的java类型