泛型是从Java1.5开始引进的,所谓的泛型可以理解成 参数化类型 ,即类型是以参数的方式传入泛型类或者泛型方法。 泛型这个术语的意思是:“适用于许多许多的类型”。
泛型可以使编写的代码被很多不同的类型对象所重用。
使用泛型机制编写的程序代码要比那些杂乱地使用Object变量,然后再进行强制类型转换的代码具有 更好的安全性和可读性 。泛型对于集合类尤其有用,例如,ArrayList就是一个无处不在的集合。
泛型类可以看成普通类的工厂。
package com.corejava.genericprogramming; public class ArrayList{ private Object[] elementData; ... public Object get(int i ){...} public void add(Object o){...} } 复制代码
这种方法有两个问题。
ArrayList files = new ArrayList(); String filename = (String) files.get(0); 复制代码
//ArrayList类有一个类型参数用来指示元素的种类 ArrayList<String> files = new ArrayList<String>(); //JavaSE 7后,构造函数可以省略泛型类型。 ArrayList<String> files = new ArrayList<>(); 复制代码
当调用get的时候,不需要进行强制类型转换。 编译器就知道返回值类型为String,而不是Object。
String filename = files.get(0); 复制代码
下面是一个泛型类。
package com.corejava.genericprogramming; public class Pair<T> { private T first; private T second; public Pair(T first, T second){ this.first = first; this.second = second; } public T getFirst() { return first; } public void setFirst(T first) { this.first = first; } public T getSecond() { return second; } public void setSecond(T second) { this.second = second; } } 复制代码
由上段代码可见,类型变量位于类名之后的尖括号<>中。
可以引进多个类型变量。
public class Pair<T, U> {...} 复制代码
在Java库中,E表示集合的元素类型,K和V表示关键字和值,T或者U或者S等表示任意类型。
泛型方法可以定义在普通类中,也可以定义在泛型类中。
注意,类型变量放在修饰符后面,返回类型前面。
package com.corejava.genericprogramming; public class ArrayAlg { //如果没有<T>声明,不能称之为泛型方法 public static <T> T getMiddle(T... a) { return a[a.length / 2]; } public static void main(String[] args) { //System.out.println(ArrayAlg.<String>getMiddle("Matthew","Xu","CoreJava")); //一般情况下可以省略<String>类型参数。 //编译器有足够的信息推断出所调用的方法。 System.out.println(ArrayAlg.getMiddle("Matthew","Xu","CoreJava")); } } 复制代码
如下定义一个泛型接口。
package com.corejava.genericprogramming; public interface IGeneric<T> { public T test(); } 复制代码
package com.corejava.genericprogramming; public class TestGeneric<T> implements IGeneric<T>{ @Override public T test() { return null; } } 复制代码
package com.corejava.genericprogramming; public class TestGeneric implements IGeneric<String>{ @Override public String test() { return null; } } 复制代码
有的时候,类或方法需要对类型变量加以约束。 下面是一个计算最小元素的例子。
package com.corejava.genericprogramming; public class ArrayAlg { public static <T> T getMin(T[] a) { if( a == null || a.length == 0) return null; T min = a[0]; for (T t : a) { if(min.compareTo(t) > 0) min = t; } return min; } } 复制代码
这里有个问题,T是任意类型,但是不能保证T含有compareTo方法,在编写代码过程中会直接报错。
解决方案便是给T加上限定,使其实现Comparable接口。
public static <T extends Comparable> T getMin(T[] a){...} 复制代码
一个类型变量或者通配符可以有多个限定。
无论限定是接口还是类, 只用extends来连接。
限定类型用&分隔,而逗号用来分隔类型变量。
类型变量的限定类型中可以有多个接口,但最多一个类。
如果需要用一个类作为限定,它必须是限定列表中的第一个。
T extends Comparable & Serializable 复制代码
请注意,虚拟机没有泛型类型对象——所有对象属于普通类。
泛型类型的原始类型是删除类型参数后的泛型类型名。
擦除类型变量,并且替换成限定类型。
如果没有限定类型,使用Object。
例如,Pair的原始类型如下所示。
package com.corejava.genericprogramming; public class Pair { private Object first; private Object second; public Pair(Object first, Object second){ this.first = first; this.second = second; } public Object getFirst() { return first; } public void setFirst(Object first) { this.first = first; } public Object getSecond() { return second; } public void setSecond(Object second) { this.second = second; } } 复制代码
如果泛型类型含有限定的类型变量,那么原始类型可以使用第一个限定的类型变量替换。如果没有限定就用Object替换。
代码示例:
package com.corejava.genericprogramming; import java.io.Serializable; public class Interval <T extends Comparable & Serializable> implements Serializable{ private T lower; private T upper; public Interval(T first, T second) { if(first.compareTo(second) <= 0) { lower = first; upper = second; }else { lower = second; upper = first; } } } 复制代码
原始类型:
package com.corejava.genericprogramming; import java.io.Serializable; public class Interval implements Serializable{ private Comparable lower; private Comparable upper; public Interval(Comparable first, Comparable second) { if(first.compareTo(second) <= 0) { lower = first; upper = second; }else { lower = second; upper = first; } } } 复制代码
以之前的Pair类为例,只有Pair<Double>,没有Pair<double>。
原因:
擦除之后,Pair类含有Object类的域,而Object不能存储double值。
笔者在这里其实有个疑惑,就算擦除之后,为什么Object不能存放double?
double是可以自动装箱成Double,而Double作为一个对象类型是可以被Object存储的。
请看下列两段代码。
package com.corejava.genericprogramming; public class Generic<T> { private T first; private T second; public Generic(T first, T second) { super(); this.first = first; this.second = second; } public T getFirst() { return first; } public void setFirst(T first) { this.first = first; } public T getSecond() { return second; } public void setSecond(T second) { this.second = second; } } 复制代码
package com.corejava.genericprogramming; import org.junit.jupiter.api.Test; class GenericTest { @Test void test() { Generic<String> genericStr = new Generic<>("first", "second"); //error:Cannot perform instanceof check against parameterized type Generic<String>. //Use the form Generic<?> instead since further generic type information will be erased at runtime //System.out.println(genericStr instanceof Generic<String>); //error:Cannot perform instanceof check against parameterized type Generic<String>. //Use the form Generic<?> instead since further generic type information will be erased at runtime //System.out.println(genericStr instanceof Generic<T>); //输出:true System.out.println(genericStr instanceof Generic); //输出:class com.corejava.genericprogramming.Generic System.out.println(genericStr.getClass()); } } 复制代码
由此可见,如果想查询一个对象是否属于某个泛型类型时,使用instanceof会得到一个编译器错误。
如果使用getClass方法则会返回一个原始类型。
不能实例化参数化类型的数组
package com.corejava.genericprogramming; import org.junit.jupiter.api.Test; class GenericTest { @Test void test() { //实际这行代码会报错,报错信息:Cannot create a generic array of Generic<String> Generic<String>[] array = new Generic<String>[10]; //类型擦除后,array的类型即为Generic[]。 //可以把它转换成Object[]类型。 Object[] objarray = array; //数组会记住它的元素类型,当传入其他类型元素,本应会报错。 //但是对于泛型来说,擦除使检查机制无效。 //出于这个原因,参数化类型的数组不允许被创建。 objarray[0] = "hello"; } } 复制代码
不能使用new T(...)或者new T[...]或T.class这样的表达式中的类型变量。 下面的构造器是非法的。
public Generic(){ first = new T(); second = new T(); } 复制代码
public static <T extends Comparable> T[] minmax(T[] a) { //error: T[] mm = new T[2]; } 复制代码
不能在静态域或方法中引用类型变量。 请看代码:
package com.corejava.genericprogramming; public class Generic<T> { private T first; private T second; public Generic(T first, T second) { super(); this.first = first; this.second = second; } public T getFirst() { return first; } public void setFirst(T first) { this.first = first; } public T getSecond() { return second; } public void setSecond(T second) { this.second = second; } public void test(T t) { System.out.println("hello"); } //如果不声明T,会给出下列报错信息。 //error: Cannot make a static reference to the non-static type T public static <T> void name(T t) { System.out.println("hello"); } } 复制代码
既不能抛出也不能捕获泛型类对象。
实际上,甚至泛型类拓展Throwable都是不合法的。
//The generic class Generic<T> may not subclass java.lang.Throwable public class Generic<T> extends Exception{...} 复制代码
无论S与T有什么联系,通常,Pair<E>与Pair<T>没有什么联系。 请看下列代码。
package com.corejava.genericprogramming; import org.junit.jupiter.api.Test; class GenericTest { @Test void test() { Manager ceo = new Manager(); Manager cfo = new Manager(); Employee employee = new Employee(); Generic<Manager> managerGroup = new Generic<>(ceo, cfo); //error:Type mismatch: cannot convert from Generic<Manager> to Generic<Employee> Generic<Employee> employeeGroup = managerGroup; employeeGroup.setFirst(cfo); } } 复制代码
注意泛型和数组之间的区别。
package com.corejava.genericprogramming; import org.junit.jupiter.api.Test; class GenericTest { @Test void test() { Manager ceo = new Manager(); Manager cfo = new Manager(); Employee lowerEmployee = new Employee(); Manager[] managerGroup = new Manager[] {ceo, cfo}; //操作合法 Employee[] employeeGroup = managerGroup; //error: Type mismatch: cannot convert from Employee to Manager managerGroup[0] = lowerEmployee; } } 复制代码
通配符类型中,允许类型参数变化。
例如,通配符类型Pair< ? extends Employee>表示任何泛型Pair类型,它的类型参数是Employee的子类。
假设要编写一个方法。
package com.corejava.genericprogramming; public class GenericTest { public static void printGroup(Generic<Employee> g) { System.out.println(g.getFirst().getName() + g.getSecond().getName()); } } 复制代码
但是这个方法不能将Generic传入。
public static void main(String[] args) { Manager m1 = new Manager(); Manager m2 = new Manager(); m1.setName("mat"); m2.setName("xu"); Generic<Manager> gm = new Generic<Manager>(m1, m2); //error:The method printGroup(Generic<Employee>) in the type GenericTest is not applicable for the arguments (Generic<Manager>) printGroup(gm); } 复制代码
解决的办法便是使用通配符类型。
public static void printGroup(Generic<? extends Employee> g) {...} 复制代码
但笔者在这里有个疑惑,为什么不能这么写。
public static void printGroup(Generic<T extends Employee> g) {...} 复制代码
Eclipse给出的报错信息是:
Incorrect number of arguments for type Generic<T>; it cannot be parameterized with arguments <T, Employee> 复制代码
也就是说其实<? extends Employee>算一个参数,而算两个参数。 对应的泛型类Generic只包含一个类型参数,所以后者不能通过。
本文参考了大量文章,未来会加入《Java编程思想》和《Effective Java》的内容和观点。目前主要内容还是来自《Java核心技术》,通过这篇博文的撰写,原先比较陌生的Java泛型现在也比较熟悉了。但是仍然未涉及其复杂之处。如果需要转载,请注明出处!如果有什么疑问或者见解,请分享你的观点!谢谢大家!