泛型相关概念

Java 泛型的使用方法

泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数

泛型一般有三种使用方式:泛型类、泛型接口、泛型方法。你可以在自己编写的工具类中使用泛型

泛型标记符

E - Element (在集合中使用,因为集合中存放的是元素,使用时将该集合定义为E)
T - Type(普通Java 类)
K - Key(Map中的键)
V - Value(Map中的值)
N - Number(数值类型)
? - 表示不确定的 java 类型,可以表示任何类型

你也可以用自己定义的类型

    class JsonResult<asdfdsaf> implements Serializable {
        private asdfdsaf datatype;
    }

泛型标记符只是一个标记,你可以随便定义

泛型方法

例子:

    public <E> void function(E[] a){
    }

必须在返回值前写<?>,否则直接调用找不到对象的

有些代码已经定义了泛型类但是还是在方法中继续定义T,这么做是为了告诉编译器对它说:这是新指定的一个类型,跟ClassName< T >类对象中的T没有关系

从上面看,那就是 这个方法返回值前也加个< T >的话,这个T就代表该方法自己独有的某个类,而不去和类中限定的T产生冲突

public class Test<T> {
 
    private T name;
 
    public T getT(){
        return name;
    }
 
    public void setT(T name){
        this.name = name;
    }
 
    public <T> void setT2(T name){
        System.out.println(name);;
    }
 
    public T getT2(T name){
        return name;
 
    }
}

有界的类型参数

java 可以限制那些被允许传递到一个类型参数的类型种类范围。例如,一个操作数字的方法可能只希望接受Number或者Number子类的实例。要声明一个有界的类型参数,首先列出类型参数的名称,后跟extends关键字,最后紧跟它的上界。

   public static <T extends Comparable<T>> T maximum(T x, T y, T z){
   }            

泛型类与泛型接口

泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分。和泛型方法一样,泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开

public class Box<T> {}

类型擦除

类型擦除是 Java 泛型的实现,Java 的泛型是伪泛型,这是因为 Java 在编译期间,所有的泛型信息都会被擦掉,生成的字节码中是不包含泛型中的类型信息的,这也就是通常所说类型擦除
在这里插入图片描述
真实泛型是指泛型无论在源码、编译后还是运行期都是真实存在,例如在 C# 中 List< Integer > 和 List< String > 就是两个不同的类型,它们在系统运行期生成,有自己的虚方法表(执行方法的字节码入口)和类型数据,这种实现称为类型膨胀(因为同一个类会生成很多不同的类型),基于这种方法实现的泛型称为真实泛型。C# 中的泛型就是真实泛型

原始类型

如果在代码中定义 List< Object > 和 List< String > 等类型,在编译后都会变成 List。而在擦除去了泛型信息,最后在字节码中的类型变量的真正类型,就是原始类型,无限定类型的变量用 Object,有限定类型用限定类型的上限来表示

裸类型被视为所有该类型泛型化实例的共同父类型,而在元素访问时、即执行这个方法的时候自动插入一些强制类型转换以及检查类型语句

因为这个特性,所以:

1,Java 编译器是通过先检查代码中泛型的类型,然后在进行类型擦除,再进行编译
2,我们在获取泛型的时候,编译器自动地在结果字节码中插入强制类型转换

在这里插入图片描述

多态冲突与桥方法

java 的泛型实现会引发很多问题,比如多态冲突

我们在子类中重写父类的多态方法,变成上限范围内的一个子类。这样的话,类型擦除后,父类的泛型类型全部变为了原始类型 Object,所以父类编译之后和子类的参数类型不一样,因为 java 要求重写的定义是和限定符完全相同,如果是在普通的继承关系中,根本就不会是重写,而是重载

public class Parent<T> {
    public void sayHello(T value) {
        System.out.println("This is Parent Class, value is " + value);
    }
}

public class Child extends Parent<String> {
	@Override
    public void sayHello(String value) {
        System.out.println("This is Child class, value is " + value);
    }
}

但是事实上,虚拟机确实认为它重写了。怎么做到这一点呢?

重写父类这个方法的子类,会生成两个方法,而这多生成的一个方法,就是编译器自己生成的桥方法。而桥方法的参数类型都是父类中泛型类型,这样就解决了类型擦除和多态的冲突

public void sayHello(Object value) {
    sayHello((String) value);
}

桥方法被 ACC_BRIDGE 标记
在这里插入图片描述
但是事实上,这么处理的反编译后的代码,是不展示桥方法的
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值