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

您所在的位置:网站首页 泛型类和普通类的区别 Java 泛型,参数类型T和通配符?的边界问题

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

2023-12-21 22:58| 来源: 网络整理| 查看: 265

目录

一、概述

二、和的使用及区别

(1)类型参数

(2)无界通配符

三、有界通配符、

四、泛型擦除

一、概述

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

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

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

List list=new ArrayList(); 二、和的使用及区别

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

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

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

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

 

(1)类型参数

 

1、声明泛型类

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

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

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

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

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

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

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

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

class Box{ private List item;//泛型类成员字段 public List get(){return item;}//方法返回值 public void set(List t){item=t;}//方法参数 }

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

2、声明泛型方法

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

class Fuction{ public static List reduce(List list){ } }

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

 

(2)无界通配符

 

1、声明泛型类不能用

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

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

通配符作用:是使用定义好的泛型类。

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

List list = new ArrayList();

2、 的CAP#1问题

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

List list = new ArrayList(); 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的例子,有get()和set()两个方

class Box{ private List item; public List get(){return item;} public void set(List 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{ private List item; public List get(){return item;} public void set(List t){item=t;} //getSet()方法存取元素 public void getSet(Box box){helper(box);} //helper()辅助函数 public void helper(Box box){box.set(box.get());} } 三、有界通配符 extends 的通配符限定泛型,我们无法向里面添加元素(只可以添加null),只能读取其中的元素。对于无界通配符 super 的通配符限定泛型,我们可以读取其中的元素,但读取出来的元素会变为 Object 类型。

 

 

四、泛型擦除

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

我们先看一个例子:

ArrayList list1 = new ArrayList(); list1.add("abc"); ArrayList list2 = new ArrayList(); 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作为原始类型;

 

 



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3