Java 泛型,参数类型T和通配符?的边界问题

目录

一、概述

二、和的使用及区别

(1)类型参数

(2)无界通配符

三、有界通配符、

四、泛型擦除


一、概述

1、定义:Java泛型(generics)泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。

2、优势:Java语言引入泛型的优势在于安全、重用。 泛型在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,以提高代码的重用率。

3、作用:相比杂乱地使用Object声明变量,再进行强制类型转换的代码而言,使用泛型机制编写的程序代码具有更好的安全性和可读性。泛型对于集合类尤其有用,例如,List<T>容器就是一个无处不在的泛型集合类。

List<String> list=new ArrayList<>();

二、<T>和<?>的使用及区别

好奇List<T>和List<?>有什么区别呢?其实这个问题就是问无限定类型变量“<T>”无界通配符“<?>”的区别。

讨论“<T>"和"<?>",首先要区分开两种不同的场景:

  • 第一,声明泛型类或泛型方法。
  • 第二,使用泛型类或泛型方法。

类型参数“<T>”主要用于第一种,声明泛型类或泛型方法; 无界通配符“<?>”主要用于第二种,使用泛型类或泛型方法。

 


(1)类型参数<T>

 

1、<T>声明泛型类

List<T>最应该出现的地方,应该是定义一个泛型List容器。看看ArrayList的源码:

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, 
Cloneable, java.o.Serializable {
    ... ...
}

ArrayList<E>中的“E”也是类型参数。只是表示容器中元素Element的时候,习惯用“E”。

换一个简单的例子,我们自己定义一个新泛型容器叫Box<T>。

class Box<T>{
    private T item1;
    private T item2;
}

<T>类型参数能够对类型起到‘约束的作用,例子中就是为了保证Box里的item1, item2都是同一个类型T。如,Box<String>,代表两个item都是String;Box<Integer>里两个item都是Integer。

*那什么时候会使用泛型类呢?

答:就是作为泛型类的成员字段或成员方法的参数间接出现。

class Box<T>{
    private List<T> item;//泛型类成员字段

    public List<T> get(){return item;}//方法返回值

    public void set(List<T> t){item=t;}//方法参数
}

这里写成List<T>为了表示和Box<T>类型参数保持一致。


2、<T>声明泛型方法

Function类的reduce是个静态泛型方法,注意这方法是在普通类中定义的,而不是在泛型类中定义的。这里的List<T>出现在reduce方法参数\函数返回值\函数内部,也是为了保持泛型类型的一致性。

class Fuction{
    public static <T> List<T> reduce(List<T> list){
       
    }
}

注意,类型变量<T> 放在修饰符(public static)的后面,返回类型的前面。

 


(2)无界通配符<?>

 

1、声明泛型类不能用<?>

 首先,要明确通配符<?>不能拿来声明泛型。错误案例,如下:

class Box<?>{
//Error Example
    private ? item1;
    private ? item2;
}

通配符<?>作用:是使用定义好的泛型类

例如,用<?>声明List容器的变量类型,然后用一个实例对象给它赋值的时候就比较灵活。

List<?> list = new ArrayList<String>();

2、 <?>的CAP#1问题

List<?>这个写法非常坑。因为,通配符<?>会捕获具体的String类型,但编译器不叫它String,而是起个临时的代号,比如”CAP#1“。因此,list不能存String,任何元素都不行,只能存null。

List<?> list = new ArrayList<String>();

list.add("hello");    //ERROR
//argument mismatch; String cannot be converted to CAP#1

list.add(111);    //ERROR
//argument mismatch; int cannot be converted to CAP#1

另外,如果拿List<?>做参数,也会有奇妙的事情发生。还是刚才Box<T>的例子,有get()和set()两个方

class Box<T>{
    private List<T> item;
    public List<T> get(){return item;}
    public void set(List<T> t){item=t;}

    //把item取出来,再放回去, ERROR
    public void getSet(Box<?> box){box.set(box.get());
    //error: incompatible types: Object cannot be converted to CAP#1
}    
}

新的getSet()方法,只是把item先用get()方法读出来,然后再用set()方法存回去。按理说不可能有问题。实际运行却会报错。原因和前面一样,用通配符<?>声明的类型泛型类Box,调用box.set()的参数类型被编译器捕获,命名为'CAP#1',和box<?>.get()返回的Object对象无法匹配。

3、解决方法

通过写一个helper()辅助函数。

class Box<T>{
    private List<T> item;
    public List<T> get(){return item;}
    public void set(List<T> t){item=t;}

    //getSet()方法存取元素
    public void getSet(Box<?> box){helper(box);}
    //helper()辅助函数
    public <V> void helper(Box<V> box){box.set(box.get());}
}

三、有界通配符<? extends X>、<? super X>

常用的是<? extends X>或者<? super XXX>两种,带有上下界的通配符。

其中,上界可以有多层<? extends X extends XX>。

 

注意:

  • 对于无界通配符<?> extends 的通配符限定泛型,我们无法向里面添加元素(只可以添加null),只能读取其中的元素。
  • 对于无界通配符<?> super 的通配符限定泛型,我们可以读取其中的元素,但读取出来的元素会变为 Object 类型。

 

 


四、泛型擦除

Java的泛型基本上都是在编译器这个层次上实现的,在运行期生成的字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程成为类型擦除

我们先看一个例子:

 ArrayList<String> list1 = new ArrayList<String>();
        list1.add("abc");

ArrayList<Integer> list2 = new ArrayList<Integer>();
        list2.add(123);

System.out.println(list1.getClass() == list2.getClass());

 打印结果:true

结果显示list1和list2居然是同一个类型的ArrayList,在运行时我们传入的类型变量String和Integer都被擦除掉了,只剩下原始类型。原因是Java语言泛型在设计的时候为了兼容原来的旧代码,虚拟机并不知道泛型的存在,因为泛型在编译阶段就已经被处理成普通的类和方法了。类型擦除规则:

  • 若泛型类型没有指定具体类型,用Object作为原始类型;

  • 若有限定类型< T exnteds XClass >,使用XClass作为原始类型;

  • 若有多个限定< T exnteds XClass1 & XClass2 >,使用第一个边界类型XClass1作为原始类型;

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章