泛型概述
泛型在java中有很重要的地位,在面向对象编程及各种设计模式中有非常广泛的应用。
什么是泛型?为什么要使用泛型?
《Java 核心技术》中对泛型的定义是:“泛型” 意味着编写的代码可以被不同类型的对象所重用。
Java泛型的内部原理:
Java有Java编译器和Java虚拟机,编译器将Java源代码转换为.class文件,虚拟机加载并运行.class文件。对于泛型类,Java编译器会将泛型代码转换为普通的非泛型代码,就像的普通ArrayList<T>类代码及其使用代码一样,将类型参数T擦除,替换为Object,插入必要的强制类型转换。Java虚拟机实际执行的时候,它是不知道泛型这回事的,它只知道普通的类及代码。
再强调一下,Java泛型是通过擦除实现的,类定义中的类型参数如T会被替换为Object,在程序运行过程中,不知道泛型的实际类型参数,比如ArrayList<Integer>,运行中只知道ArrayList,而不知道Integer,认识到这一点是非常重要的,它有助于我们理解Java泛型的很多限制。
值得注意一点,并不是所有的擦除都是T会被替换为Object,Java中还支持多个上界,多个上界之间以&分隔,类似这样:
T extends Base&Comparable&Seiralizable
Base 为上界类,Comparable和Serializable为上界接口,如果有上界类,类应该放在第一个,类型擦除时,会用第一个上界Base替换T
指定边界后,类型擦除时就不会转换为Object了,而是会转换为它的边界类型。
所以有时候,我们说Java的泛型其实是假泛型,或者叫擦除泛型(java),和C++的真泛型或者模版泛型是有区别的。
Java为什么要这么设计呢?泛型是Java 1.5以后才支持的,这么设计是为了兼容性而不得已的一个选择。
例子
public static void main(String[] args) {
ArrayList arrayList=new ArrayList();
arrayList.add(1);
arrayList.add(2);
arrayList.add("泛型测试");
for(Object item:arrayList){
String t=(String)item;
System.out.println(t);
}
}
ArrayList 可以存放任意类型,例子中添加了Integer类型,也添加了String类型,使用时都以String的方式使用,因此程序崩溃了。为了解决这个问题,泛型应运而生,在编译阶段就可以解决。
public static void main(String[] args) {
ArrayList<String> arrayList=new ArrayList();
arrayList.add(1); //编译器直接报错
arrayList.add(2); //编译器直接报错
arrayList.add("泛型测试");
for(Object item:arrayList){
String t=(String)item;
System.out.println(t);
}
}
我们将ArrayList声明初始化list的代码更改一下 ArrayList<String> ,编译器会在编译阶段就能够帮我们发现类似这样的问题。
泛型的使用
泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法
泛型类
泛型类型用于类的定义中,被称为泛型类。通过泛型可以完成对一组类的操作对外开放相同的接口。最典型的就是各种容器类,如:List、Set、Map。
泛型类的最基本写法
class 类名称 <泛型标识:可以随便写任意标识号,标识指定的泛型的类型>{
private 泛型标识 /*(成员变量类型)*/ var;
.....
}
}
例子:
//此处E可以随便写为任意的标识,比如T、E、K、V 等形式的参数,常用于表示泛型
//实例化泛型类时,必须制定E 的具体类型
public class GenericDemo<E> {
private E val;
//泛型构造方法形参key的类型也为E,E的类型由外部指定
public GenericDemo(E val){
this.val=val;
}
//泛型方法getKey的返回值类型为E,E的类型由外部指定
public E getVal(){
return val;
}
}
public static void main(String[] args) {
GenericDemo<Integer> integerGenericDemo=new GenericDemo<Integer>(123456789);
GenericDemo<String> stringGenericDemo=new GenericDemo<String>("泛型测试");
//也可以不指定具体类型 相当于GenericDemo<Object>
GenericDemo generic1 = new GenericDemo("111111");
GenericDemo generic2 = new GenericDemo(false);
GenericDemo<Object> objectGenericDemo=new GenericDemo<>("tttt");
System.out.println(objectGenericDemo.getVal());
System.out.println(objectGenericDemo.getClass().equals(integerGenericDemo.getClass()));
}
注意:
1、泛型的类型参数只能是类类型,不能是简单类型。
2、不能对确切的泛型类型使用instanceof操作。如下面的操作是非法的,编译时会出错。
if(integerGenericDemo instanceof GenericDemo<Integer> ){
}
泛型接口
泛型接口和泛型类一样,泛型接口在接口名后添加类型参数,比如下面的GenericInterface<T>,接口声明类型后,接口方法就可以直接使用这个类型。
实现类在实现泛型接口时需要指明具体的参数类型,不然默认类型是Object,这就失去了泛型接口的意义。
//定义一个泛型接口
public interface GenericInterface<T> {
T getEntity();
}
未指明类型的实现:
public class GenericIImpl implements GenericInterface {
@Override
public Object getEntity() {
return null;
}
}
指明了类型的实现:
public class GenericStringImpl implements GenericInterface<String> {
@Override
public String getEntity() {
return "我是泛型";
}
}
public class GenericInterImpl implements GenericInterface<Integer> {
@Override
public Integer getEntity() {
return 100;
}
}
泛型方法
泛型类,是在实例化类的时候指明泛型的具体类型,泛型方法,是在调用方法的时候指明泛型的具体类型。
泛型类中的泛型方法
//这个类是个泛型类
public class GenericMethod<T>{
private T val;
public GenericMethod(T val){
this.val=val;
}
//虽然在方法中使用了泛型,但是这个并不是一个泛型方法
//这只是类中的一个普通成员方法,只不过它的返回值是在声明泛型类已经声明过的泛型
//所以在这个方法中才可以继续使用T 这个泛型
public T getVal(){
return val;
}
/**
* 编译器会报错 “cannot reslove symbol E”
* 因为在类的声明中并未声明泛型E ,所以在使用E做形参和返回值类型时,编译器无法识别
*/
// public E setVal(E val){
// this.val=val;
// }
/**
* 真正的泛型方法
* 首先在public 与返回值之间的<E> 必不可少,这表明这是一个泛型方法,并且声明了一个泛型E
* 这个E可以出现在这个泛型方法的任意位置
* 泛型的数量也可以任意多个
* 如 public <T,K> K showKeyName(Generic<T> container){...}
*
*
*/
public <E> E showValName(GenericMethod<E> container){
E e=container.getVal();
return e;
}
//这也不是一个泛型方法,这就是一个普通的方法,只是使用了Generic<Number>这个泛型类做形参而已。
public void showValName2(GenericMethod<Number> obj){
}
//静态方法不能使用泛型
// public static T getStaticVal(){
// return new GenericMethod<Integer>(222).getVal();
// }
}
调用方法
public class GenericMethodMain {
public static void main(String[] args) {
GenericMethod<Integer> genericMethod = new GenericMethod(123456);
Integer t = genericMethod.<Integer>showValName(genericMethod);
System.out.println(t);
}
}
普通类中的泛型方法
public class GenericMethodNT {
public <T> void copy(List<T> source, List<T> dest,T t) {
System.out.println("普通类的泛型方法!");
}
public static void main(String[] args) {
GenericMethodNT genericMethodNT=new GenericMethodNT();
genericMethodNT.<String>copy(new ArrayList<>(),new ArrayList<>(),"泛型方法测试");
}
}
泛型的通配符
有时候希望传入的类型有一个指定的范围,从而可以进行一些特定的操作,这时候通配符边界登场了。
泛型中有三种通配符形式:
1.<?> 无限制通配符
2.<? extends E> extends 关键字声明了类型的上界,表示参数化的类型可能是所指定的类型,或者是此类型的子类
3.<? super E> super 关键字声明了类型的下界,表示参数化的类型可能是指定的类型,或者是此类型的父类
看个例子:
public class GenericExtend<E> {
public void add(E e) {
System.out.println("GenericExtend.add");
}
public void addAll(GenericExtend<E> genericExtend){
System.out.println("GenericExtend.addAll");
}
public <T extends E> void addAllExtend(GenericExtend<T> genericExtend) {
System.out.println("GenericExtend.addAllExtend");
}
}
下面我们操作一下:
public static void main(String[] args) {
GenericExtend<Number> numberGenericExtend = new GenericExtend<>();
GenericExtend<Integer> integerGenericExtend = new GenericExtend<>();
integerGenericExtend.add(100);
integerGenericExtend.add(200);
//numberGenericExtend.addAll(integerGenericExtend); //会报错
numberGenericExtend.addAllExtend(integerGenericExtend); //正确
}
我们看到numberGenericExtend.addAll(integerGenericExtend); 会报错!
这行代码上提示编译错误,提示,addAll需要的参数类型为GenericExtend<Number>,而传递过来的参数类型为GenericExtend<Integer>,不适用,Integer是Number的子类,怎么会不适用呢?
再看下面的例子:
GenericExtend<Integer> integerGenericExtend1 = new GenericExtend<>();
//假设下面这行是合法的
GenericExtend<Number> numberGenericExtend1 = integerGenericExtend1;
numberGenericExtend1.add(new Double(12.34));
这时,GenericExtend<Integer>中就会出现Double类型的值,而这,显然就破坏了Java泛型关于类型安全的保证。
虽然Integer是Number的子类,但GenericExtend<Integer>并不是GenericExtend<Number>的子类,GenericExtend<Integer>的对象也不能赋值给GenericExtend<Number>的变量。
将Integer添加到Number容器中其实是很合理,那我们怎么解决这个问题呢?调用addAllExtend方法来解决这个问题
public <T extends E> void addAllExtend(GenericExtend<T> genericExtend) {
System.out.println("GenericExtend.addAllExtend");
}
这个写法有点啰嗦,它可以替换为更为简洁的通配符形式:
public void addAllExtendSimple(GenericExtend<? extends E> genericExtend) {
System.out.println("GenericExtend.addAllExtendSimple");
}
这个方法没有定义类型参数,genericExtend的类型是GenericExtend<? extends E>,?表示通配符,<? extends E>表示有限定通配符,匹配E或E的某个子类型,具体什么子类型,我们不知道
<T extends E>与<? extends E>
那么问题来了,同样是extends关键字,同样应用于泛型,<T extends E>和<? extends E>到底有什么关系?
它们用的地方不一样,我们解释一下:
<T extends E>用于定义类型参数,它声明了一个类型参数T,可放在泛型类定义中类名后面、泛型方法返回值前面。
<? extends E>用于实例化类型参数,它用于实例化泛型变量中的类型参数,只是这个具体类型是未知的,只知道它是E或E的某个子类型。
虽然它们不一样,但两种写法经常可以达成相同目标,比如,前面例子中,下面两种写法都可以:
public void addAll(GenericExtend<? extends E> c)
public <T extends E> void addAll(GenericExtend<T> c)
网友评论