JAVA 基础知识点复习(十五)泛型

定义

在编译时期就对元素的类型进行检查,一旦发现不匹配就编译失败。在编译时泛型就被擦除了,所以无法在运行时得知其类型参数的类型。通过泛型可以定义类型安全的数据结构(类型安全),而无须使用实际的数据类型(可扩展),这能够显著提高性能并得到更高质量的代码(高性能),因为可以重用数据处理算法,而无须复制类型特定的代码(可重用)。

泛型可以定义在类,接口,方法上

  • 定义时尖括号里的每个元素都代指一种未知类型
  • 尖括号的位置非常讲究,必须在类名之后或者方法返回值之前
  • 泛型在定义处只具备Object方法的能力
  • 基本数据类型不能作为泛型参数
// 泛型接口
public interface Genericity<T> {
    T test();
}
// 泛型类 在编译时是无法知道K和V具体是什么类型,只有在运行时才会真正根据类型来构造和分配内存。
// 这里K super Number 是非法的,由于擦除操作,泛型参数会被边界替换(用最顶级的父类型),所以不支持声明的super操作
@Data
public class Container<K extends Number, V> {

    private K key;

    private V value;

    public Container(K key, V value) {
        this.key = key;
        this.value = value;
    }

    public Container() {
    }

    // 这里使用了类上的泛型 不能用static修饰 静态方法无法访问类上定义的泛型。
    public K test(V v) {
        return null;
    }

    // <String> 声明泛型,并在形参和返回值上使用了泛型
    // String不等于java.lang.String,只是一个自定义的泛型类型
    public static <String> String test2(String str) {
        return str;
    }

    // 定义了泛型并添加了上界
    public static <T extends Number> Number test3(T str) {
        return str;
    }

    // 定义了泛型并添加了上界 上界也是泛型类
    public static <T extends Comparator<T>> T test4(T str) {
        return str;
    }

    // 定义了泛型并添加了上界 上界也是泛型类并且带通配符
    public static <T extends Comparator<? super T>> T test5(T str) {
        return str;
    }

}

在JDK7之前需要在声明并赋值的时候,两侧都要加上泛型

// idea 报红 类型不匹配
Container<String, String> container = new Container<String, String>(1, 2);

而在JDK7中,加入了类型推断,编译器会根据变量声明时的泛型类型自动推断出实例化时的泛型类型,而不需要在右边指定

Container<String, String> container = new Container<>();
// jdk7
List<String> list = new ArrayList<>();
list.add("A");
// 以下代码在jdk7中会报错,这里期望得到的是 Collection<? extends String> c
list.addAll(new ArrayList<>());
// 正常
list.addAll(new ArrayList<String>());

而jdk8在类型推断上做出了优化,编译器能够根据你调用的方法和相应的声明来确定需要的参数类型的能力

// jdk8
List<String> list = new ArrayList<>();
list.add("A");
// 正常
list.addAll(new ArrayList<>());

但是在创建泛型实例时的类型推断是有限制的;只有构造器的参数化类型在上下文中被显著的声明了,才可以使用类型推断,否则不行。

// idea 报红 Cannot infer arguments
Container<Integer, String> container = new Container<>(i, s);   

// 可以正常推断
Integer i = 1;
String s = "";
Container<Integer, String> container = new Container<>(i, s);

并且只有右边也加上了<>才表示是自动类型推断,否则就是原始类型

// idea 报黄 Unchecked assignment: 'Container' to 'Container<java.lang.String,java.lang.String>'
// 这里无法检查泛型,但是在方法调用的时候可以通过上下文推断出来
Container<String, String> container = new Container(1, 2);
// idea 报红 Required type:String Provided:int
container.setKey(1);

原始类型:

Container container = new Container(1, 2); // 不报错,但是会飘黄,有如下警告

Such raw uses of parameterized types are valid in Java, but defeat the purpose of using type parameters, and may mask bugs. This inspection mirrors the rawtypes warning of javac.

翻译过来就是:这种对参数化类型的原始使用在java中是有效的,但是会破坏使用类型参数的目的,并可能掩盖错误,此检查反映了编译阶段对原始类型的警告

如果使用原始类型,就会失去泛型的安全性和表现力

那么为什么java允许原始类型的使用呢?为了兼容之前大量未使用泛型的代码

泛型的限定

<? extends E> 泛型上限 接收E或者E的子类,一般在存储元素时使用上限,取值时用E来接收,不会存在类型安全问题

public static void printCollection(List<? extends Number> list){
    Number number = list.get(0);
}

<? super E> 泛型下限 接收E或者E的父类,从集合取值时可以用E或者E的父类接收

public TreeSet(Comparator<? super E> comparator) {
    this(new TreeMap<>(comparator));
}

泛型擦除

泛型在运行时会被擦除

Container<String, String> container  = new Container<>();
Container<Integer, Integer> container1 = new Container<>();
System.out.println(container.getClass() == container1.getClass()); // true

// 这里直接修改会报错
container.setKey(1);
// 通过反射可以
Class<?> clazz = container.getClass();
Method method = clazz.getDeclaredMethod("setKey", Object.class);
method.invoke(container, 5);
Object o = container.getKey();
System.out.println(o);

这里有个现象,都说泛型在编译后会被擦除,但是通过idea查看生成的字节码文件还是能看到泛型存在,这是为什么呢?其实是JDK1.5版本之后class文件的属性表中添加了Signature和LocalVariableTypeTable等属性来支持泛型识别的问题,所以在各类ide查看class文件时是进行了类型复原的。

通过Jad反编译Container.class文件

public class Container
{

    public Container(Number key, Object value)
    {
        this.key = key;
        this.value = value;
    }

    public Container()
    {
    }

    public Number test(Object v)
    {
        return null;
    }

    public static Object test2(Object str)
    {
        return str;
    }

    public static Number test3(Number str)
    {
        return str;
    }

    public static Comparator test4(Comparator str)
    {
        return str;
    }

    public static Comparator test5(Comparator str)
    {
        return str;
    }
}

泛型擦除后还会发生ClassCastException的原因:

Container<String, String> container = new Container(1, 2);
// 在调用时会发生强转操作
System.out.println(container.getKey()); // ClassCastException

// .class 字节码中加上相应的强制类型转换
Container<String, String> container = new Container(1, 2);
System.out.println((String)container.getKey());

泛型擦除带来的问题

  • 不能使用泛型来重载方法
  • 不能catch同一个异常的不同泛型实例
  • 不同泛型类的静态变量是共享的
  • 泛型声明时不支持super操作
  • 泛型间不存在继承关系,不能使用List<Number> 来接收List<Integer>类型的参数
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值