Java泛型详解

一、简介

泛型,即“参数化类型”。换句话说,我们将原先具体的类型变成参数,类似方法中的参数,在使用/调用时传入具体的类型。如果我们在一个包含泛型的类或方法上生成Java文档注释,泛型类型将作为一种参数注释(@param)生成。
综上所述,泛型类型也是一种类型,它可以被用在类(泛型类)、接口(泛型接口)和方法(泛型方法)中。
一些常见的泛型类型标识名称及含义如下:

变量标识含义
E元素(Element)
T类型(Type)
N数字(Number)
K关键字(Key)
V值(Value)
?泛型通配符

二、泛型示例

泛型概念比较抽象,所以我们先上一个小栗子。在日常开发中最常见的泛型应用就是集合,泛型的使用将避免强制类型转换带来的类型不安全,因为编译器会在编译而非运行时检查类型。

List<String> stringList = new ArrayList<>();
stringList.add("1");
// stringList.add(2);	编译错误,因我们在声明集合时使用泛型约定集合中只能存在String类型数据。

三、泛型擦除

虽然使用泛型能够避免强制类型转换带来的类型不安全,但泛型只在编译前是有效的,在编译后的class文件中是没有泛型的,未知的泛型都将被当作Object处理。一个简单的例子可以帮我们验证这一点:

List<Integer> integerList = new ArrayList<>();
List<String> stringList = new ArrayList<>();

System.out.println(integerList.getClass() == stringList.getClass());    // true
System.out.println(integerList.getClass());    // class java.util.ArrayList
System.out.println(stringList.getClass());    // class java.util.ArrayList

这个特性称为泛型擦除,主要是为了兼容JDK1.5之前的代码。正因为泛型只在编译前有效,所以我们可以通过反射越过泛型检查,感兴趣的同学请出门左转:Java反射机制

四、泛型类

/**
 * 泛型类
 *
 * @param <T>
 */
public class GenericTest<T> {

	private T data;

	public T getData() {
		return data;
	}

	public void setData(T data) {
		this.data = data;
	}

	public static void main(String[] args) {
		GenericTest<String> stringGenericTest = new GenericTest<>();
		// stringGenericTest.setData(1);	编译错误
		stringGenericTest.setData("GenericTest<String>");
		System.out.println(stringGenericTest.getData());        // GenericTest<String>

		GenericTest genericTest = new GenericTest();
		genericTest.setData(1);
		System.out.println(genericTest.getData());    // 1
		genericTest.setData("GenericTest");
		System.out.println(genericTest.getData());    // GenericTest
	}
}

观察泛型类示例,该泛型类接受一个泛型类型参数T(T可以由任何类型标识代替),在实例化时指定泛型类型。同时,泛型类包含一个泛型类型的成员变量,它的具体类型由泛型类的泛型类型指定。
在实例化时当然可以不指定泛型类型,此时泛型类中的泛型类型在类型擦除后可以为任意类型(Object)。

五、泛型接口

(一)定义一个泛型接口,包含一个泛型类型形参的方法。

public interface IGenericTest<T> {

	void print(T data);
}

(二)泛型接口的两种实现方式

1、指定泛型接口的泛型类型,此时实现类仍可以拥有自己的泛型类型。例如上文泛型类可实现本泛型接口并指定泛型类型为Integer。

/**
 * 泛型类
 *
 * @param <T>
 */
public class GenericTest<T> implements IGenericTest<Integer> {

	private T data;

	public T getData() {
		return data;
	}

	public void setData(T data) {
		this.data = data;
	}

	/**
	 * 实现泛型接口方法
	 *
	 * @param data
	 */
	@Override
	public void print(Integer data) {
		System.out.println(data);
	}

	public static void main(String[] args) {
		GenericTest<String> stringGenericTest = new GenericTest<>();
		// stringGenericTest.setData(1);	编译错误
		stringGenericTest.setData("GenericTest<String>");
		System.out.println(stringGenericTest.getData());        // GenericTest<String>

		stringGenericTest.print(1);    // 1
	}
}

2、不指定泛型接口的泛型类型,而通过实现类实例化时指定,此时泛型接口泛型类型与实现类泛型类型相同。

/**
 * 泛型类
 *
 * @param <T>
 */
public class GenericTest<T> implements IGenericTest<T> {

	private T data;

	public T getData() {
		return data;
	}

	public void setData(T data) {
		this.data = data;
	}

	/**
	 * 实现泛型接口方法
	 *
	 * @param data
	 */
	@Override
	public void print(T data) {
		System.out.println(data);
	}

	public static void main(String[] args) {
		GenericTest<String> stringGenericTest = new GenericTest<>();
		// stringGenericTest.setData(1);	编译错误
		stringGenericTest.setData("GenericTest<String>");
		System.out.println(stringGenericTest.getData());        // GenericTest<String>

		// stringGenericTest.print(1);    编译错误
		stringGenericTest.print("IGenericTest<String>.print()");	// IGenericTest<String>.print()
	}
}

六、泛型方法

大多数泛型类中都拥有使用了泛型的方法,但使用泛型的方法不一定就是泛型方法,这对初学者来说非常容易混淆。
通过上面的介绍我们知道泛型类是在类实例化时指定泛型类型,以此类推,泛型方法也是在使用/调用时才指定泛型类型。观察泛型类的示例,对于成员变量data的getter/setter方法都使用了泛型,但此时的泛型类型T与泛型类泛型相同。也就是说,在实例化泛型类时就已经指定了getter/setter方法的泛型类型,所以它们不属于泛型方法。
那什么是真正的泛型方法呢?下面我们看一个最简单的例子:

public <T> T genericMethod(T t) {
	return t;
}

1、<T>声明了该方法是一个泛型方法,此时在方法中才可以使用泛型T,当然此处的T也可以由任何类型标识代替。
2、紧随<T>的T说明该方法返回一个T类型返回值。
3、参数T t说明该方法接受一个T类型形参t

/**
 * 泛型类
 *
 * @param <T>
 */
public class GenericTest<T> implements IGenericTest<T> {

	private T data;

	public T getData() {
		return data;
	}

	public void setData(T data) {
		this.data = data;
	}

	/**
	 * 实现泛型接口方法
	 *
	 * @param data
	 */
	@Override
	public void print(T data) {
		System.out.println(data);
	}

	/**
	 * 泛型方法
	 *
	 * @param t
	 * @param <T> 此时泛型类型T与泛型类T类型无关,它们可以是同一个类型也可以不同
	 * @return
	 */
	public <T> T genericMethod(T t) {
		return t;
	}

	public static void main(String[] args) {
		GenericTest<String> stringGenericTest = new GenericTest<>();

		// 泛型方法的泛型类型只在使用/调用时指定,与泛型类泛型类型无关
		System.out.println(stringGenericTest.genericMethod("GenericTest<String>.genericMethod(String)"));    // GenericTest<String>.genericMethod(String)
		System.out.println(stringGenericTest.genericMethod(1));    // 1
	}
}

七、泛型通配符

使用具有继承关系泛型类型的泛型类之间没有继承关系。例如Integer是Number的子类,但是List<Integer>和List<Number>没有任何关系。

private static void allowNumber(Number number) {
	System.out.println(number);
}

private static void allowGenericNumber(List<Number> numberList) {
	System.out.println(numberList);
}

public static void main(String[] args) {
	Number number = 128;
	Integer integer = 128;
	allowNumber(number);
	allowNumber(integer);

	List<Number> numberList = new ArrayList<Number>() {{
		add(128);
	}};
	List<Integer> integerList = new ArrayList<Integer>() {{
		add(128);
	}};
	allowGenericNumber(numberList);    // [128]
	// allowGenericNumber(integerList);	编译错误
}

那我们难道要还需要写一个allowGenericInteger(List<Integer>)方法处理List<Integer>么?答案当然不用,显然这违背了Java的多态思想,因此类型通配符?应运而生。类型通配符和其他类型标识符一样也是一种类型,只不过它可以代表任意类型,所以我们可以将它看作是所有类型的父类型(类似Object)。我们使用类型通配符修改上面的代码,完美通过!

private static void allowNumber(Number number) {
	System.out.println(number);
}

private static void allowGenericNumber(List<?> numberList) {
	System.out.println(numberList);
}

public static void main(String[] args) {
	Number number = 128;
	Integer integer = 128;
	allowNumber(number);
	allowNumber(integer);

	List<Number> numberList = new ArrayList<Number>() {{
		add(128);
	}};
	List<Integer> integerList = new ArrayList<Integer>() {{
		add(128);
	}};
	allowGenericNumber(numberList);	// [128]
	allowGenericNumber(integerList);	// [128]
}

八、泛型的限定(上下边界)

在使用泛型时,我们可以指定泛型类型的上下边界,例如我们可以指定泛型类型只能是某类型的父类型或者子类型。

(一)泛型类型上界<? extends Parent>

为泛型类型指定上界即传入的泛型类型只能是指定类型的子类型。

List<? extends Number> numbers = new ArrayList<Number>() {{
	add(1);
	add(2.0);
}};

System.out.println(numbers); // [1, 2.0]

(二)泛型类型下界<? super Child>

与上界相反,为泛型类型指定下界即传入的泛型类型只能是指定类型的父类型。

(三)无界泛型<?>

无界类型就是类型通配符<?>,它可以代表任意类型。

(四)PECS原则

使用泛型上下界虽然使得类型转换更加容易,但同时也会让泛型类容器损失部分功能。以泛型上界为例,当我们指定了泛型上界,对编译器来说只知道容器内是上界类型和其子类型。此时,由于不能确定新元素的具体类型,但容器内元素一定可以通过上界类型接收,所以容器只能读取但不能新增。下界的情况正好与之相反。
所以我们提出PECS(Producer Extends Consumer Super)原则来衡量我们如何合理使用上下界:当我们需要频繁读取容器时使用上界,反之如果我们需要频繁新增的使用下界。

九、泛型约束

1、基本类型(Eg:int)不能作为泛型类型,但包装类(Eg:Integer)可以。
2、泛型类不能继承Exception或者Throwable。

//	private class MyGenericException<T> extends Exception{}	编译错误,Generic class may not extend 'java.lang.Throwable'
//	private class MyGenericException<T> extends Throwable{}	编译错误,Generic class may not extend 'java.lang.Throwable'

3、静态变量或静态方法均不能引用泛型类型,但静态泛型方法可以。

	/**
	 * 泛型方法
	 *
	 * @param e
	 * @param <E>
	 * @return
	 */
	public <E> E genericMethod(E e) {
		return e;
	}

//	public static T staticField;	编译错误,静态变量不能引用泛型类型

//	public static void staticMethod(T a) {	编译错误,静态方法不能引用泛型类型
//		System.out.println(a);
//	}

	/**
	 * 静态泛型方法
	 *
	 * @param e
	 * @param <E>
	 * @return
	 */
	public <E> E staticGenericMethod(E e) {
		return e;
	}

4、不能使用instanceof关键字或==判断泛型类的泛型类型。

public class GenericTypeTest<T> {

	public static void main(String[] args) {
		GenericTypeTest<Integer> integerGenericTypeTest = new GenericTypeTest<>();
		GenericTypeTest<String> stringGenericTypeTest = new GenericTypeTest<>();

		System.out.println(integerGenericTypeTest instanceof GenericTypeTest);    // true
//		System.out.println(integerGenericTypeTest instanceof GenericTypeTest<Integer>);	编译错误,不能使用instanceof关键字判断泛型类的泛型类型
//		System.out.println(integerGenericTypeTest == stringGenericTypeTest);	编译错误,不能使用==判断泛型类的泛型类型
	}
}

5、由于泛型擦除机制,所以不能创建确定泛型类型的泛型数组,除非使用通配符,因为对于通配符来说,数组元素均需进行显示类型转换。

// GenericTest<String>[] ls = new GenericTest<String>[10];
GenericTest<String>[] genericTests = new GenericTest[10];
GenericTest<?>[] genericTests1 = new GenericTest<?>[10];

在日常开发中,泛型被经常用在各种开发模式中。同时泛型和反射的配合可以为代码提供更高的灵活性。对反射还不熟悉的同学请出门左转:Java反射机制

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值