泛型是从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 files = new ArrayList();
//JavaSE 7后,构造函数可以省略泛型类型。
ArrayList files = new ArrayList<>();
当调用get的时候,不需要进行强制类型转换。
编译器就知道返回值类型为String,而不是Object。
String filename = files.get(0);
具有一个或多个类型变量的类称之为泛型类。
下面是一个泛型类。
package com.corejava.genericprogramming;
public class Pair {
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 {...}
在Java库中,E表示集合的元素类型,K和V表示关键字和值,T或者U或者S等表示任意类型。
泛型方法可以定义在普通类中,也可以定义在泛型类中。
注意,类型变量放在修饰符后面,返回类型前面。
package com.corejava.genericprogramming;
public class ArrayAlg {
//如果没有声明,不能称之为泛型方法
public static T getMiddle(T... a) {
return a[a.length / 2];
}
public static void main(String[] args) {
//System.out.println(ArrayAlg.getMiddle("Matthew","Xu","CoreJava"));
//一般情况下可以省略类型参数。
//编译器有足够的信息推断出所调用的方法。
System.out.println(ArrayAlg.getMiddle("Matthew","Xu","CoreJava"));
}
}
如下定义一个泛型接口。
package com.corejava.genericprogramming;
public interface IGeneric {
public T test();
}
泛型接口未传入泛型实参时
package com.corejava.genericprogramming;
public class TestGeneric implements IGeneric{
@Override
public T test() {
return null;
}
}
泛型接口传入泛型实参时
package com.corejava.genericprogramming;
public class TestGeneric implements IGeneric{
@Override
public String test() {
return null;
}
}
有的时候,类或方法需要对类型变量加以约束。
下面是一个计算最小元素的例子。
package com.corejava.genericprogramming;
public class ArrayAlg {
public static 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 getMin(T[] a){...}
一个类型变量或者通配符可以有多个限定。
无论限定是接口还是类,只用extends来连接。
选择关键字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 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,没有Pair。
原因:
擦除之后,Pair类含有Object类的域,而Object不能存储double值。
笔者在这里其实有个疑惑,就算擦除之后,为什么Object不能存放double?
double是可以自动装箱成Double,而Double作为一个对象类型是可以被Object存储的。
请看下列两段代码。
package com.corejava.genericprogramming;
public class Generic {
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 genericStr = new Generic<>("first", "second");
//error:Cannot perform instanceof check against parameterized type Generic.
//Use the form Generic> instead since further generic type information will be erased at runtime
//System.out.println(genericStr instanceof Generic);
//error:Cannot perform instanceof check against parameterized type Generic.
//Use the form Generic> instead since further generic type information will be erased at runtime
//System.out.println(genericStr instanceof Generic);
//输出: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
Generic[] array = new Generic[10];
//类型擦除后,array的类型即为Generic[]。
//可以把它转换成Object[]类型。
Object[] objarray = array;
//数组会记住它的元素类型,当传入其他类型元素,本应会报错。
//但是对于泛型来说,擦除使检查机制无效。
//出于这个原因,参数化类型的数组不允许被创建。
objarray[0] = "hello";
}
}
需要说明的是,声明类型为Generic[]的变量仍是合法的,但是不能用new Generic[10]进行初始化。
如果需要收集参数类型化对象,只能使用ArrayList。
不能使用new T(…)或者new T[…]或T.class这样的表达式中的类型变量。
下面的构造器是非法的。
public Generic(){
first = new T();
second = new T();
}
public static T[] minmax(T[] a) {
//error:
T[] mm = new T[2];
}
不能在静态域或方法中引用类型变量。
请看代码:
package com.corejava.genericprogramming;
public class Generic {
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 void name(T t) {
System.out.println("hello");
}
}
既不能抛出也不能捕获泛型类对象。
实际上,甚至泛型类拓展Throwable都是不合法的。
//The generic class Generic may not subclass java.lang.Throwable
public class Generic extends Exception{...}
无论S与T有什么联系,通常,Pair
与Pair 没有什么联系。
请看下列代码。
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 managerGroup = new Generic<>(ceo, cfo);
//error:Type mismatch: cannot convert from Generic to Generic
Generic 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 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 gm = new Generic(m1, m2);
//error:The method printGroup(Generic) in the type GenericTest is not applicable for the arguments (Generic)
printGroup(gm);
}
解决的办法便是使用通配符类型。
public static void printGroup(Generic extends Employee> g) {...}
但笔者在这里有个疑惑,为什么不能这么写。
public static void printGroup(Generic g) {...}
Eclipse给出的报错信息是:
Incorrect number of arguments for type Generic; it cannot be parameterized with arguments
也就是说其实 extends Employee>算一个参数,而算两个参数。
对应的泛型类Generic只包含一个类型参数,所以后者不能通过。
本文参考了大量文章,未来会加入《Java编程思想》和《Effective Java》的内容和观点。目前主要内容还是来自《Java核心技术》,通过这篇博文的撰写,原先比较陌生的Java泛型现在也比较熟悉了。但是仍然未涉及其复杂之处。如果需要转载,请注明出处!如果有什么疑问或者见解,请分享你的观点!谢谢大家!
参考链接