简单谈谈泛型

一、理解泛型

在简单理解泛型之前,应该看下以下代码示例,当我们在代码中写下如下代码,将一个整型赋值给字符串变量时会报错

String x = 1;

会爆出以下错误:
Error:(63, 20) java: 不兼容的类型: int无法转换为java.lang.String

仅仅是一个简单的变量类型赋值尚会出现错误。在来举例一个常用例子,先看看Java标准库中的 ArrayList 其内部主要也是存储一个Object[]数组

public class ArrayList {
private Object[] array;
private int size;
public void add(Object e) {…}
public void remove(int index) {…}
public Object get(int index) {…}
}

当ArrayList中想要添加和获取String类型字符串时,需要在get时取出的Object对象强转为Sting对象,在这个过程中,有可能就转型错误,报错ClassCastException

ArrayList list = new ArrayList();
list.add(“Hello”);
// 获取到Object,必须强制转型为String:
String first = (String) list.get(0);

所以为了解决上面的问题,可以自己封装一个StrArrayList类,里面的add和get都是对String类型进行操作,但这样这个类只能保证存储获取的是String类的数据

public class StrArrayList {
private String[] array;
private int size;
public void add(String e) {…}
public void remove(int index) {…}
public String get(int index) {…}
}

但是各种自定义类和基础类会有茫茫多,ArrayList想要做到通用就不能针对每种情况单独写一个封装类,所以就需要吧ArrayList设置成一个模板ArrayList

public class ArrayList {
private T[] array;
private int size;
public void add(T e) {…}
public void remove(int index) {…}
public T get(int index) {…}
}

在这个其中的代码,T可以是任何类型的数据,就避免了重复定义类的问题。这就是泛型。简而言之,泛型就是一种可定义的类的模板。

泛型总结

泛型就是编写模板代码来自适应多种类型

泛型的好处使用时不需要类的强制转换

注意泛型的继承关系:可以把ArrayList向上转型为List(T不能变!),但不能把ArrayList向上转型为ArrayList(T不能变成父类)。

二、泛型使用

在泛型的自定义过程中,泛型类型不适用于静态方法,也就是如下类中,K不能替换为T,在create方法中使用,编译不会通过。或者说即使写了T编译通过,但使用中和Pair中的T也不是同一个T。

public class Pair {
private T first;
private T last;
public Pair(T first, T last) {
this.first = first;
this.last = last;
}
public T getFirst() { … }
public T getLast() { … }
// 静态泛型方法应该使用其他类型区分:
public static Pair create(K first, K last) {
return new Pair(first, last);
}
}

小结

编写泛型时,需要定义泛型类型;

静态方法不能引用泛型类型,必须定义其他类型(例如)来实现静态泛型方法;

泛型可以同时定义多种类型,例如Map<K, V>。

三、泛型擦除

泛型是前文说到的,是一种模板代码的技术,可能不同语言实现方式不同。Java中使用的泛型实现方式是擦拭法,也就是虚拟机对泛型一无所知,所有工作都是编译器做的。
也就是我们写的代码如下

public class Pair {
private T first;
private T last;
public Pair(T first, T last) {
this.first = first;
this.last = last;
}
public T getFirst() {
return first;
}
}

编译器进行处理,给到虚拟机的代码

public class Pair {
private Object first;
private Object last;
public Pair(Object first, Object last) {
this.first = first;
this.last = last;
}
public Object getFirst() {
return first;
}
}

在使用过程中,编译器也将代码通过Ojbect对象强转为我们代码中定义的类,进行安全强转。

所以知道了Java的泛型是在编译器时执行的。了解了实现方法就知道了局限性。
局限一:T在虚拟机中运行其实是Ojbect类型,所以T中的数据不能放入基本类型

Pair p = new Pair<>(1, 2); // 这种就是编译报错,int要改成Integer包装类

局限二:因为T实际虚拟机中运行时Object,自己写的代码是String或自定义类,那么久无法获取类的class。

因为T是Object,我们对Pair和Pair类型获取Class时,获取到的是同一个Class,也就是Pair类的Class。
换句话说,所有泛型实例,无论T的类型是什么,getClass()返回同一个Class实例,因为编译后它们全部都是Pair。

局限三:无法判断带泛型的类型

Pair p = new Pair<>(123, 456);
// 编译会失败,获取判断不了
if (p instanceof Pair) {
}

局限四:不能实例化T类型

小结

Java的泛型是采用擦拭法实现的;
擦拭法决定了泛型:

不能是基本类型,例如:int;### 不能获取带泛型类型的Class,例如:Pair.class;

不能判断带泛型类型的类型,例如:x instanceof Pair;

不能实例化T类型,例如:new T()。

泛型方法要防止重复定义方法,例如:public boolean equals(T obj);

子类可以获取父类的泛型类型。

四、extends通配符

使用类似<? extends Number>通配符作为方法参数时表示:

方法内部可以调用获取Number引用的方法,例如:Number n = obj.getFirst();;

方法内部无法调用传入Number引用的方法(null除外),例如:obj.setFirst(Number n);。

即一句话总结:使用extends通配符表示可以读,不能写。

使用类似定义泛型类时表示:泛型类型限定为Number以及Number的子类。

五、super通配符

使用类似<? super Integer>通配符作为方法参数时表示:

方法内部可以调用传入Integer引用的方法,例如:obj.setFirst(Integer n);;

方法内部无法调用获取Integer引用的方法(Object除外),例如:Integer n = obj.getFirst();。

即使用super通配符表示只能写不能读。

使用extends和super通配符要遵循PECS原则。

无限定通配符<?>很少使用,可以用替换,同时它是所有类型的超类。

六、泛型和反射

部分反射API是泛型,例如:Class,Constructor;

可以声明带泛型的数组,但不能直接创建带泛型的数组,必须强制转型;

可以通过Array.newInstance(Class, int)创建T[]数组,需要强制转型;

同时使用泛型和可变参数时需要特别小心。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值