java基础进阶泛型

最近在巩固java知识,学到java泛型,在此总结一下,以便以后查看。

(一)什么是泛型?

Java泛型是JDK5中引入的一种参数化类型特征。
参数化类型就是把类型当做参数传递。
eg:

Plate<T> //“T”称为类型参数
Plate<Apple> //“Apple”称为实际类型参数
Plate<T> //整个称为泛型类型
Plate<Apple> //整个称为参数化类型

(二)为什么要用泛型

1.代码更健壮(将运行期检查提前到编译期,运行期不会出现ClassCastException)
2.代码更简洁 (不用强转)
3.代码更灵活 (复用)

public static void main(String[] args) {
        int[] ints = new int[10];
        sort(ints);
        double[] doubles = new double[10];
        sort(doubles);
        Object[] objects = new Object[10];
        sort(objects);
    }
   // public static void sort(int[] array) {}
   // public static void sort(double[] array) {}
    public static <T> void sort(T[] array) {} //一个方法就可以运行

(三)泛型的三种使用

1.泛型接口

public interface Plate<T> {} //在接口后面加<T>

2.泛型类

public class Plate<T> {} //在类后面加<T>

static class A {}
static interface B {}
static interface C {}
//多个限制时,用&拼接,并且类要放在第一个
static class D<T extends A & B & C> {}

3.泛型方法 在可见修饰符和返回值之间加

public class ApplePlate<T> {}
public <T> ApplePlate<T> getApple() {
    return new ApplePlate<T>();
    }

(四)java是如何处理泛型的

泛型类的本质
ArrayList<Apple> apples = new ArrayList<>();
ArrayList<Banana> bananas = new ArrayList<>();
System.out.println("比较 :" + (apples.getClass() == bananas.getClass()));

打印结果为 true。意味着这两个list是同一类型,泛型类型被擦除了,擦除后只留下原始类型,这里也就是ArrayList。

接下来用一个小例子来看看泛型擦除是怎么回事,将下面代码使用javac指令编译成.class文件,再用javap -c反编译.class文件,查看生成的字节码文件。或者直接用android Studio自带的ASM插件查看字节码文件

public interface Plate<T> {
    public void set(T t);
    public T get();
}

.class文件
在这里插入图片描述

字节码文件
在这里插入图片描述

如上图所示源码中的泛型信息都没有了,在字节码中变成了Object,说明泛型T都被擦除成Object了。然后我们再看看下面的例子

public class AIPlate<T extends Comparable<T>> implements Plate<T> {

    private List<T> items = new ArrayList<T>(10);

    public AIPlate(){
        super();
    }

    @Override
    public void set(T t) {
        items.add(t);
        Collections.sort(items);
    }

    @Override
    public T get(){
        int index = items.size() -1;
        if(index>= 0){
            return items.get(index);
        }else{
            return null;
        }

    }

    @Override
    public boolean equals(Object obj) {
        return super.equals(obj);
    }

    @Override
    public String toString() {
        return "Plate{" +
                "items=" + items +
                '}';
    }
}

用上面的步骤,查看字节码文件。
在这里插入图片描述
可以看到这里的set方法,泛型被插除成Comparable类型了,因为T限定为了Comparable类型了。
所以可以看出泛型并不是都插除成Object,当它有限定的时候,就会被插除成限定的那个类型。
再看看字节码,发现在字节码中有两个set方法,还有一个如下
在这里插入图片描述
这里是为了java的多态特性不被破坏,看上面的例子,因为AIPlate类是实现Plate接口的,那么AIPlate就要实现在Plate中定义的接口。而在Plate中的set方法,泛型插除后T变成了Object,在AIPlate中的set方法,T是被擦除成Comparable类型,所以还生成了一个set的桥方法, 其实可以看出在桥方法中,调用的就是上面的set方法,然后把Object强转成Comparable。

到这里我有个疑惑,泛型信息既然插除了,为什么在.class文件中还是T呢?
这是由于泛型擦除的残留,在反编译后的字节码文件中才是真正的方法,.class中泛型的信息其实只是签名,泛型类中有独有的标记,标记定义时的成员签名。这些泛型信息会存在类的常量池中。
总结一下
java泛型原理是什么,什么是反省擦除机制?
java泛型是JDK5新引入的特性,为了向下兼容,虚拟机其实是不支持泛型的,所以java实现的是一种伪泛型机制,也就是说java在编译期擦除了所有的泛型信息,这样java就不需要产生新的类型到字节码,所有的泛型类型最终都是一种原始类型,在java运行时根本就不存在泛型信息。
java编译器对泛型的擦除步骤
1.检查泛型类型,获取目标类型
2. 擦除类型变量,并替换为限定类型
a.如果泛型类型的类型变量没有限定,则用Object作为原始类型
b.如果有限定(< T extends XClass>),则用XClass作为原始类型
c.如果有多个限定,则使用第一个边界XClass作为原始类型
3. 在必要时插入类型转换以保持类型安全
4. 生成桥方法以在扩展时保持多态性

(五)泛型插除带来的副作用

1.泛型类型变量不能使用基本数据类型
比如没有Array,只有ArrayList,类型擦除后,ArrayList的原始类中的类型变量T替换成Object,但Object类型不能存放int值
2.不能使用instanceof运算符
因为擦除后,ArrayList< String>只剩下原始类型,泛型信息String不存在了。
在这里插入图片描述
3.泛型在静态方法和静态类中的问题
因为泛型类中的泛型参数的实例化是在定义泛型类型对象的时候指定的,而静态成员是不需要使用对象来调用的,所以对象都没创建,就不能确定这个泛型参数是什么了。

class Test<T> {
    public static T one; //报错
    public static T test(T t) {} //报错

    public static <T> T test1(T t) {return t;} //这种可以,因为这是泛型方法,这里的T并不是泛型类中的T,当调用的时候才确定泛型类型。
}

4.泛型类型中的方法冲突
5.没法创建泛型实例
因为类型不确定,如果给出泛型的class类型,可以通过反射创建。
6.没有泛型数组
因为数组是协变,擦除后就没法满足数组协变的原则
eg
Apple extends Fruit
Apple[] extends Fruit[] 这就是数组的协变

(六)通配符

为了让泛型在转换类型的时候更灵活,引入了通配符。

上届通配符

用< ? extends T>表示,表达的意思是T类型及它子类型的基类。
用上届通配符的后遗症,相当于只读,但是通过反射可以写数据进去,但是泛型安全性无法保证。

下届通配符

用< ? super T>表示,表达的是T类型及它基类的基类,可以设置值,但是取值的时候,因为泛型信息的丢失,只能用Object存放。

非限定通配符

< ?>,不能读,也不能写数据。主要是为了保证编译器能进行类型的安全检查。

(七)PECS原则

Producer extends Consumer super.
如果你只需要从集合获取类型T, 使用< ? extends T> 通配符
如果你只需要将类型T放到集合中,使用< ? super T>
使用PECS原则,可以提升API的灵活性。

(八)实例

public static <T> void copy(List<? super T> dest, List<? extends T> src) {
        Collections.copy(dest, src); //java集合的copy方法其实就是采用的上面的通配符配合方式。
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值