工学1号馆

home

java核心系列10-泛型

By Wu Yudong on June 11, 2015

原创文章,转载请注明: 转载自工学1号馆

泛型是对 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类。可以把类型参数看作是使用参数化类型时指定的类型的一个占位符,就像方法的形式参数是运行时传递的值的占位符一样。

为什么使用泛型

在java引用泛型之前,是通过继承来实现的

class ArrayList {
	private Object[] elementData;
	        //...
	public Object get(int i) {
		//...
	}
	public void add(Object o) {
		//...
	}
}

上面的实现存在2个问题:

当获取一个值的时候,必须进行强制类型转换:

ArrayList files = new ArrayList();
String filename = (String)files.get(0);

其次没有类型检查,可以向数组中添加任何类型的对象:

files.add(new File("..."));

而泛型则提供了一种更好的解决方案:类型参数

ArrayList<String> files = new ArrayList<String>();

当然在java SE 1.7后,构造函数中可以省略泛型类型:

ArrayList<String> files = new ArrayList<>();

定义简单的泛型类

class Pair<T> {
	private T first;
	private T second;
	
	public Pair() {
		first = null;
		second = null;
	}
	public Pair(T first, T second) {
		this.first = first;
		this.second = second;
	}
	public T getFirst() {
		return first;
	}
	public T getSecond() {
		return second;
	}
	public void setFirst(T newValue) {
		first = newValue;
	}
	public void setSecond(T newValue) {
		second = newValue;
	}	
}

用具体的类型替换类型类型变量就可以实例化泛型类型:

public class PairTest1 {
	public static void main(String[] args) {
		String[] words = {"wu", "yu", "dong"};
		Pair<String> mm = ArrayAlg.minmax(words);
		System.out.println("min=" +  mm.getFirst());
		System.out.println("max=" + mm.getSecond());
	}
}
class ArrayAlg {
	public static Pair<String> minmax(String[] a) {
		if(a == null || a.length ==0) return null;
		String min = a[0];
		String max = a[0];
		for(int i = 1; i < a.length; i++) {
			if(min.compareTo(a[i]) > 0) min = a[i];
			if(max.compareTo(a[i]) < 0) max = a[i];
		}
		return new Pair<>(min, max);
	}
}

泛型方法

刚才通过实例学习了如何定义一个泛型类,下面来看看如何定义泛型方法

public static <T> T getMiddle(T ... a) {
	return a[a.length / 2];
}

注意:类型变量<T>放在修饰符public static的后面,返回值T的前面

泛型方法既可以定义在普通类中,也可以定义在泛型类中

调用泛型方法如下:

String middle = ArrayAlg.<String>getMiddle("wu", "yu", "dong");

当然也可以 省略<String>

类型变量的限定

有的时候,类或方法需要对类型变量加以约束,来看一个例子:

public static <T> T min(T[] a) {
	if(a == null || a.length ==0) return null;
	T smallest = a[0];
	for(int i = 1; i < a.length; i++) {
		if(smallest.compareTo(a[i]) > 0) smallest = a[i];
	}
	return smallest;
}

由于smallest为任意类型T,所以不能确定是否具有compareTo方法

解决办法为T限制为Comparable接口:

public static <T extends Comparable<T>> T min(T[] a) {
	if(a == null || a.length ==0) return null;
	T smallest = a[0];
	for(int i = 1; i < a.length; i++) {
		if(smallest.compareTo(a[i]) > 0) smallest = a[i];
	}
	return smallest;
}

一个类型变量或通配符可以有多个限定,此时需要用逗号分隔类型变量,用"&" 分隔类型变量

T extends Comparable & Serializable

下面使用泛型方法重写上面的程序:

public class PairTest2 {

	public static void main(String[] args) {
		String[] words = {"wu", "yu", "dong"};
		Pair<String> mm = ArrayAlg.minmax(words);
		System.out.println("min=" +  mm.getFirst());
		System.out.println("max=" + mm.getSecond());
	}
}

class ArrayAlg {
	public static <T extends Comparable<T>> Pair<T> minmax(T[] a) {
		if(a == null || a.length ==0) return null;
		T min = a[0];
		T max = a[0];
		for(int i = 1; i < a.length; i++) {
			if(min.compareTo(a[i]) > 0) min = a[i];
			if(max.compareTo(a[i]) < 0) max = a[i];
		}
		return new Pair<>(min, max);
	}
}

建立类型为泛型类的数组

如果要建立泛型类的数组,需要注意new关键字后面不要加入泛型的实际类型名,如下所示:

Generic<String>[] gs;  //声明泛型类的数组

//先对泛型数组进行初始化
gs = new Generic[5]; //不要写成new Generic<String>[5]

//再分别为每一个数组元素进行初始化
gs[0] = new Generic<String>();//为第一个数组元素赋值
//....

包含有两个泛型定义的类声明和实例化:

class Generic2<T1, T2> {
    private T1 f1;
    private T2 f2;
    //...
}

//给出泛型T1, T2的实际类型
Generic<Integer, Boolean> f = new Generic<Integer, Boolean>();

//没有给出泛型T1, T2的实际类型
Generic f1 = new Generic(); //T1, T2将默认为是Object类型

限制泛型上限类型

extends关键字用来指定泛型的上限,在实例化泛型类时,为该泛型指定的实际类型必须是指定类的子类或指定接口的子接口

import java.util.List;
public class ListGeneric<T extends List> {
    private T list;
    public void setList(T list) {
        this.list = list;
    }
    public T getList() {
        return list;
    }
}

在限定泛型的类型时,无论要限定的是接口或是类,都要使用extends关键词。测试例子:

ListGeneric<Vector> f1 = new ListGeneric<Vector>();
ListGeneric<ArrayList> f2 = new ListGeneric<ArrayList>();

如果不是List的类型,编译时就会发生错误:

ListGeneric<HashMap> f3 =  new ListGeneric<HashMap>();
type parameter java.util.HashMap is not within its bound
ListGeneric<HashMap> f3 = new ListGeneric<HashMap>();

默认的泛型限制类型

定义泛型类别时,如果只写以下代码:

class Generic<T> {
    //...
}

相当于下面的定义方式:

class Generic<T> extends Object {
    //...
}

限定泛型上限后的成员可用方法:

泛型类型的上限一经限定,类中的泛型成员就可使用上限类型中的方法和其他可用成员:
class ListGeneric<T extends List>{
    private T list;
    public void setList(T list) {
        this.list = list;
    }
    public void doSome() {
        //ad、get方法都是List接口中定义的方法
        list.add(new Integer(0));
        System.out.println(list.get(0));
    }
}

Object是所有类的父类,因此,所有的类型的实例都可赋值给声明为Object类型的变量

Boolean f1 = new Boolean(true);
Integer f2 = new Integer(1);
Object f = f1;    //ok
f = f2;        //ok

在实例化泛型类时,将泛型指定为Object类型却不存在着和其他类型之间的兼容性:

Generic<Boolean> f1 = new Generic<Boolean>();
Generic<Integer> f2 = new Generic<Integer>();
Generic<Object> f=f1; //f1和f类型并不兼容,发生编译错误
f=f2;  //f2和f类型同样不兼容,也会发生编译错误

泛型通配字符(Wildcard)

泛型类实例之间的不兼容性会带来使用的不便。使用泛型通配符(?)声明泛型类的变量可以解决这个问题

Generic<Boolean> f1 = new Generic<Boolean>();
Generic<Integer> f2 = new Generic<Integer>();
Generic<Object> f3 = new Generic<Object>();
Generic<?> f;
f = f1;    //ok
f = f2;    //ok
f = f3;    //ok

通配符也可以用于方法的参数类型的声明,表示该参数可接受对应泛型类型的任意实例。

以下类定义中的printCollection方法可以打印任意强类型集合中的内容

class test {
    //Collection<?>可以匹配任意强类型的集合
    static void printCollection(Collection<?> c) {
        for(Object o : c)
            System.out.println(o);
    }
}

和限制泛型的上限相似,同样可以使用extends关键字限定通配符匹配类型的上限:

Generic<? extends List> f = null;
f = new Generic<ArrayList>();    //ok
//...
f = new Generic<Vector>();    //ok
//...
//以下语句会发生编译错误,因为HashMap没有实现List接口
f = new Generic<HashMap>();
incompatible types
found : Generic<java.util.HashMap>
required: Generic<? extends java.util.List>
f = new Generic<HashMap>();

限定通配符匹配类型的下限

还可以使用super关键词将通配符匹配类型限定为某个类型及其父类型:

//将f限定为只能代表采用java.sql.Date的父类实例化的
Generic<? super java.sql.Date> f = null;
f = new Generic<java.sql.Date>();    //ok
        
//OK,java.util.Date是java.sql.Date的父类
f = new Generic<java.util.Date>();
    
//错误,因为String不是java.sql.Date的父类
f = new Generic<String>();

泛型方法

不仅类可以声明泛型,类中的方法也可以声明仅用于自身的泛型,这种方法叫做泛型方法。其定义格式为:

访问修饰符 <泛型列表> 返回类型 方法名(参数列表){

    实现代码

}

其中泛型列表为用逗号分隔的合法Java标识符。

在泛型列表中声明的泛型,可用于该方法的返回类型声明、参数类型声明和方法代码中的局部变量的类型声明。类中其他方法不能使用当前方法声明的泛型。使用泛型方法可以解决上述的泛型通配符造成的问题

泛型方法声明示例:

class cc{
    /*
     方法fun声明了一个泛型T,该方法将任意类型的数组a中的所有
     元素复制到相应的强类型集合c当中而不会导致编译错误。
     此处的泛型声明T仅作用于fun方法的声明部分和实现代码部分。
     */
     public static <T> void fun(T[] a, Collection<T> c){
         for(T o : a)
             c.add(o);    //不会出现类似通配符的编译错误
     }
}

调用泛型方法和调用普通方法没有任何不同,只需要传递含有具体类型的实参即可:

泛型方法的调用示例:

//对cc中定义的泛型方法fun进行调用测试
public class javatest {  
    public static void main(String args[ ]) { 
        String[] sa = new String[100];
        Collection<String> cs = new Vector<String>();
        Collection<Object> co = new Vector<Object>();
        
        cc.fun(sa, cs);    //fun中的泛型T此时匹配类型String
        cc.fun(sa, co);    //fun中的泛型T此时匹配类型Object
   }
}

限定泛型方法中泛型类型

泛型方法中的声明的泛型,同样可以使用extends关键字限定其类型的下限:

class cc{
  //限定aToC方法中的泛型T必须是实现了序列化接口的类型
   public static <T extends java.io.Serializable> void fun(T[] a,Collection<T> c){
         for(T o : a)
             c.add(o);
    }
}

原始类型和向后兼容

先看一个泛型类定义

public class GenericStack<E> {
    ArrayList<E> list = new ArrayList<E>();
    public int getSize() {
        return list.size();
    }
    public E peek() {
        return list.get(getSize() - 1);
    }
    public void push(E o) {
        list.add(o);
    }
    public E pop() {
        E o = list.get(getSize() - 1);
        list.remove(getSize() - 1);
        return o;
    }
    public boolean isEmpty() {
        return list.isEmpty();
    }
}

可以使用泛型类而无需指定具体类型:

GenericStack stack = new GenericStack();

//大体等价于于下面的语句
GenericStack<Object> stack = new GenericStack<Object>();

再看下面的一个例子:

class Max {
    public static Comparable max(Comparable o1, Comparable o2) {
        if(o1.compareTo(o2) > 0)
            return o1;
        return o2;
    }
}

public class javatest {  
    public static void main(String args[]) { 
        Max.max("welcome", 12);
   }
}

得到一个运行错误:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

结论:原始类型是不安全的,尽量使用泛型类型

一个更好的编写max方法的方式是使用泛型:

class Max {
    public static <E extends Comparable<E>> E max(E o1, E o2) {
        if(o1.compareTo(o2) > 0)
            return o1;
        return o2;
    }
}

再次调用上面的命令就会显示一个编译错误,由于max方法的两个参数必须是相同的类型

继承中的泛型

继承时如需保留父类泛型,需要在声明时加入父类泛型

class subGeneric<T1, T2, T3> extends Generic<T1, T2> {
    private T3 f3;
    public void setF3(T3 f3) {
        this.f3 = f3;
    }
    public T3 getF3() {
        return f3;
    }
}

如果不保留父类中的泛型声明,则继承下来的T1与T2自动变为Object类型。建议父类中的泛型声明在子类中都要保留

继承时指定父类的泛型类型

public class SubGeneric<T3> extends Generic<String, Object> {
    private T3 f3;
    public void setF3(T3 f3) {
        this.f3 = f3;
    }
    public T3 getF3() {
        return f3;
    }
}

泛型接口

接口也可包含泛型的声明:

interface I<T1, T2> {
    T1 getT1();
    T2 getT2();
    //...
}

实现泛型接口时,类在定义时可以不声明泛型接口中的泛型,此时接口中的泛型也会自动变为Object类型:

class IC implements I {
    public Object getT1() { }
    public Object getT2() { }
    //...
}

泛型接口的实现

class IC<T1, T2> implements I<T1, T2> {
    public T1 getT1() { }
    public T2 getT2() { }
    //...
}

I<String, Integer> i = new IC<String, Integer>();

实现泛型接口时指定泛型类型

在实现泛型接口时,也可直接指定接口中的泛型的实际类型:

interface I<T1, T2> {
    T1 getT1();
    T2 getT2();
    //...
}
//实现接口I时,直接指定泛型T1、T2的类型
class IC implements I<String, Integer> {
    //由于指定接口I中T1类型为String,getT1返回类型必须为String
    public String getT1() { }
    //由于指定接口I中T2类型为Integer,getT2返回类型必须为Integer
    public Integer getT2() { }
    //...
}

泛型和枚举

由于枚举类型不能直接实例化,所以枚举的定义中不能含有泛型的声明,但枚举中可包含泛型方法的定义。

public enum TrafficLight{
  Red,Amber,Green;
  private int duration;
  public static <T> void avgDuration(Collection<T> carType){
     //....
  }
  //....
}

如果文章对您有帮助,欢迎点击下方按钮打赏作者

Comments

No comments yet.
To verify that you are human, please fill in "七"(required)