位置: IT常识 - 正文

Java---泛型(java泛型E和T)

编辑:rootadmin
泛型出现的原因 Java的泛型是在JDK1.5开始才加上的。在此之前的Java是没有泛型的。 没有Java的泛型使用起来给人感觉非常的笨重,为了体会泛型带来的好处, 来看看如果没有泛型,我们将如何写代码,以下是样例。 List list = new ArrayList(); list.add(1); ... 泛型出现的原因

推荐整理分享Java---泛型(java泛型E和T),希望有所帮助,仅作参考,欢迎阅读内容。

文章相关热门搜索词:java泛型类型擦除,java泛型是什么意思,java泛型中通配符不包括,java泛型,java泛型和c++模板,java泛型中?和T的区别,java泛型中?和T的区别,java泛型中?和T的区别,内容如对您有帮助,希望把文章链接给更多的朋友!

Java的泛型是在JDK1.5开始才加上的。在此之前的Java是没有泛型的。没有Java的泛型使用起来给人感觉非常的笨重,为了体会泛型带来的好处,来看看如果没有泛型,我们将如何写代码,以下是样例。

List list = new ArrayList();list.add(1);list.add("Hello");list.add("World");list.add(2);// 现在将list中Integer类型的数据求和,并输出结果int res = 0;for (Object obj : list) { if (obj instanceof Integer) { res += (Integer)obj; }}System.out.println(res); // 输出:3

由样例我们可以看出,没有泛型 -> 可以存储任意类型 -> 使用Object类型存储 -> 取出使用时需判断并强转类型。以上流程可见其繁琐,这与当今愈见简洁的编程发展方向背道而驰。所以便有了Java泛型的出现。

泛型的演化

JDK1.5加入了泛型之后,Java代码的编写也开始变得简洁明了了。我们来试图使用泛型对上面的代码进行优化,以下是样例。

List<Integer> list = new ArrayList<Integer>();list.add(1);// list.add("Hello"); // 编译报错,不能在一个List<Integer>中存入String// list.add("World"); // 同上list.add(2);// 现在将list中Integer类型的数据求和,并输出结果int res = 0;for (Integer i : list) { res += i; // 本身就是Integer类型不需要类型判断和类型强转}System.out.println(res); // 输出:3

由优化后的样例可以看出,泛型的基本功能就是限制容器中能够存储的数据的类型。既然存任意类型会让程序在输出的时候需要大量的类型判断与类型强转,那索性就限制容器只能存放一种类型(包含其子类),这样就从源头解决了,写大量冗余代码的问题。

然而,只是这样的话真的没有问题吗?我们来看看下面这段代码。

List<Object> list1 = new ArrayList<Integer>(); // 编译报错,List<Integer>不能强转为List<Object>List list2 = new ArrayList<Integer>(); // 编译通过List<Object> list3 = new ArrayList<>(); // 编译通过list3.add(1);list3.add("2");

为啥会有上面的现象呢。我们分析一下。

第一行,我们假设编译通过,来看看有什么问题。申明类型是Object,而实例类型是Integer。申明类型表示list可以存放的类型,而实例类型是想处理的类型。可以存放的类型与想处理的类型不一致,这就违反了泛型出现的初衷,泛型就是就是为了解决存放类型与实际想处理类型不一致导致需要大量冗余代码问题的。矛盾,所以编译报错。

第二行,同理可知,想处理的类型是Integer,可以存储的类型未指定。由于申明类型未指定所以不检测泛型,编译通过。

第三行,由第一行代码的结论可知,使用泛型时,申明类型与实际类型必须一致。既然如此,右边的类型一定等于左边的类型,那右边索性就不写了,由此有了这种省略的写法。

解释完上述问题后,会发现这样的泛型并不能满足泛型特性的使用需求,这无疑使用起来变得麻烦,虽然不用写大量的类型判断等代码,但作为方法参数时也没办法接收泛型为子类的对象了,样例如下。

// 定义方法void test(List<Object> list) { /* do something */ }// 其它代码省略List<Integer> list = new ArrayList<>();// test(list); // 编译报错List<Object> list1 = new ArrayList<>();for (Integer i : list) { list1.add(i);}test(list1);

难道为了传参还需要把list中的Integer取出一个个放入List<Object>的新对象中再传入参数吗?为了解决这个问题,Java增加了一组新的特性:通配符,通配符的上界与下界

我们先来看看什么是通配符,以及通配符有什么特性。

通配符// 不使用通配符List list1 = List.of(1, 2); // 编译通过list1.add("1"); // 编译通过list1.add(2); // 编译通过// 使用通配符?List<?> list2 = List.of(1, 2);// list1.add("1"); // 编译报错// list1.add(2); // 编译报错for (Object obj : list2) { // 默认使用Object接收 // ...}

为什么会有上述现象?通配符?代表着不知道是何类型,那么就有了两个可能的发展放心,一是为了存储方便,使其可以存储任何类型的数据;二是为了元素取出后使用方便,使其不可以再存储任何类型的数据。因为Java泛型的基本职责是限制容器里存储的类型,所以这里不再能追加存储任何类型。

单纯的通配符会不会有什么限制,或者不方便的地方呢?一起来看看下面这段代码。

// 定义一个方法,使用通配符int sum(List<?> list) { int res = 0; for(Object obj : list) { res += (Integer)obj; } return res; // 返回集合中元素的和}// 省略调用位置的方法定义等int sum = sum(List.of("2", "1", "3")); // 编译通过// 上述代码执行后RuntimeException

由上述案例可知,单纯的通配符使用起来并不方便,而且容易出现意料之外的bug,为了解决这个问题,Java又拓展了两个新的性质:通配符的上界与下界。

通配符上界

语法:<? extends Integer>变量赋值时,实例的泛型允许使用Integer以及Integer的子类

使用通配符上界对上文的代码进行优化。

// 定义一个方法,使用通配符上界int sum(List<? extends Integer> list) { // list.add(4); // 编译报错 int res = 0; for(Integer i : list) { // 默认使用Integer接收 res += i; } return res; // 返回集合中元素的和}// 省略调用位置的方法定义等// int sum = sum(List.of("2", "1", "3")); // 编译报错int sum = sum(List.of(2, 1, 3)); // 编译通过

由上述案例可知,虽然还是通配符,虽然还是不知道是啥类型,但知道一个限制,是Integer类型或Integer的子类(虽然没有子类),并且不能追加元素。原因是<? extends Integer>定义当前容器的实例所有的元素都得是Integer或者其子类的其中一种类型,所有元素都得是这种类型,这点符合泛型的基本职能。又因为并不知道具体类型,即便知道一定是Integer及其子类也不能确定添加一个任意的Integer或者其子类的元素是不是当前实例类型或者其子类。所以干脆不允许添加。

有上界就有下界,接下来我们看看通配符的下界。

通配符下界

语法:<? super Integer>变量赋值时,实例的泛型允许使用Integer以及Integer的父类

// 定义一个方法,使用通配符上界int sum(List<? super Integer> list) { list.add(4); // 存入一个Integer的元素,通过 int res = 0; for(Object obj : list) { // 默认使用Object接收 res += (Integer)obj; } return res; // 返回集合中元素的和}// 省略调用位置的方法定义等int sum = sum(new ArrayList<Number>()); // 编译通过

由上述案例可知,使用通配符下界又能向容器里添加元素。这什么情况?其实不难理解,因为实例的泛型必须为Integer或者Integer的父类,那么我们添加Integer类型(包括子类)时满足Java泛型,也就不会报错可以正常使用了。

类型擦除Java---泛型(java泛型E和T)

Java的泛型并非真正的泛型,只存在于编译阶段,编译过后则不再保留泛型。虽然可以通过反射获取类中定义的泛型信息,但对象中并没有,见样例。

List<Integer> list1 = new ArrayList<>();List<String> list2 = new ArrayList<>();System.out.println(list1.getClass().getSimpleName()); // 输出:ListSystem.out.println(list2.getClass().getSimpleName()); // 输出:List

上述输出都是List而非List<Integer>与List<String>,Java泛型的类型擦除。

泛型的使用泛型类

泛型类是泛型最常见的使用方式。定义容器中存放的数据的类型。将类型参数化,有调用方来决定容器中的数据使用怎样的类型,如案例。

class Data<T extends Number> { // 只能制定Number及其子类 T t; // 传入Integer则T为Integer public Data(T t) { this.t = t; }}

将成员变量,成员方法所需要使用的数据类型参数化,并非在类定义时指定其类型,而是延迟到实例化的时候指定类型,这使得程序设计可以更加的灵活多变,适应性得到显著提升。

单纯只有泛型类实际上还是会有不方便的地方。如,当类中想要参数化的数据类型过多时,实例化的代码会很冗长,非常的不方便。为了解决这个问题,增加了一个新特性泛型方法。

泛型方法

泛型方法的特性是可以在方法定义时同时定义泛型(与反省类一样可以多个)。并且该泛型是独立于泛型类与其他泛型方法的泛型的,换而言之就是泛型方法所定义的泛型既不影响泛型类,也不影响其他的泛型方法,反之也不会受到泛型类与其他泛型方法的影响。详见样例。

class Test<T extends Number> { T t; // 使用泛型类定义的T void test(T t) { System.out.println("泛型类"); } <T> void test(T t) { // 使用泛型方法中定义的T System.out.println("泛型方法"); } // <E> void test(E e) { // System.out.println("泛型方法"); // } /* <E> void test(E e)与第6行的方法重复,编译报错 */}// 省略其他代码Test<Integer, String> test = new Test<>();// test.test<Object>(new Object()); // 编译报错,泛型方法无需指定会被自动推定出类型test.test(new Object()); // 输出:泛型方法test.test(1); // 输出:泛型类

由上述样例可知,泛型方法也可以重载,但如果只是使用的泛型(T,E,R等)不同入参列表的数量、顺序都相同会被认为方法重复定义。泛型方法中定义的泛型与泛型类的泛型重复时,泛型方法使用方法定义的泛型。

上述的泛型方法是成员方法,静态方法能不能也使用泛型呢?

class Test<T extends Number> { T t; // 使用泛型类定义的T void test(T t) { System.out.println("泛型类"); } <T> void test(T t) { // 使用泛型方法中定义的T System.out.println("泛型方法"); } // static void staticTest(T t) { // System.out.println("静态方法"); // } /* 事实上上面这种写法会编译报错, 原因是泛型类定义的泛型属于实例,只会在实例化的时候被指定 而静态方法是属于类的,当我们用类名调用静态方法的时候泛型还未指定 有的朋友就会说了:那静态方法还可以用对象调用呀,创建对象的时候不是指定了吗? 确实如此,但这经不起推敲,类名调用未指定,对象调用已指定, 为了添加一个泛型的性质,难道要让使用了泛型的静态方法都必须经由对象调用吗? 这难道不是违背了静态方法出现的初衷吗?只有一份,隶属于类,推荐使用类名调用等。 所以这种方式被舍弃了。 那么,静态方法就不能用泛型了吗?是吗,我们接着看。 */ static <T> void staticTest(T t) { // 编译通过 System.out.println("静态泛型方法"); }}

如上述,静态方法不可以直接使用泛型类中定义的泛型,如要使用泛型必须定义为静态泛型方法。

多说一句,结合程序的设计如无必要则尽量使用泛型方法而非泛型类。

泛型类(接口)间继承(实现)

与普通类间继承相比,泛型类的继承只是多了泛型的指定而已,见样例。

class A<T> {}class B<T, R> extends A<T> {} // 泛型类继承泛型类时,子类定义需包含父类的泛型class C extends A<String> {} // 非泛型类继承泛型类时,子类定义需指定父类泛型的实际类型interface D<T> {}interface E<T, R> implements D<T> {} // 泛型接口实现泛型接口时,子接口定义需包含父接口的泛型interface F implements D<Integer> {} // 非泛型接口实现泛型接口时,子接口定义需指定父接口泛型的实际类型

由于Java对象的实例顺序是先父类后子类,所以子类必须包含父类的泛型或者指定父类泛型的实际类型。否则,实例过程中无法指定父类泛型。

总结

泛型的演化

泛型的基本职能:统一容器中的元素类型。

泛型通配符:实例的泛型没有限定,可以为任何类型,但不能添加新元素。

泛型通配符上界:实例的泛型限定为指定类型及其子类,且不能添加新元素。

泛型通配符下界:实例的泛型限定为指定类型及其父类,且能添加新元素。

泛型的类型擦除:Java的泛型只存在于编译阶段,编译过后泛型便不复存在。

泛型的使用

泛型类:将容器中部分数据的类型定义为参数,将类型指定延迟到实例化。

泛型方法:不使用泛型类的泛型,而是定义独属于当前方法的泛型,是的程序设计更为灵活。

静态泛型方法:由于类与实例的关系,不能使用类的泛型,必须在方法中定义泛型并使用。

泛型类继承泛型类:子类需包含父类泛型。(接口同理)

非泛型类集成泛型类:子类需指定父类泛型。(接口同理)

本文中比较难懂的部分就是通配符的上下界,建议结合实践来消化这部分内容。

本文链接地址:https://www.jiuchutong.com/zhishi/304783.html 转载请保留说明!

上一篇:帝国cms备份王怎么使用(帝国cms目录)

下一篇:使用URLOS快速创建Discuz论坛的方法(urljoin使用)

  • 房地产开发企业资质证书
  • 生鲜配送公司财务制度
  • 社保缴费基数什么意思
  • 出口退税不退税主要适用于
  • 减资账务处理基准日 会计视野
  • 企业所得税预缴计算方法
  • 出口企业是外贸企业吗
  • 要注销的企业多久能注销
  • 开专票附加8个税点怎么计算?
  • 辞退福利应该计入什么科目
  • 汽车折旧年限与什么有关
  • 二甲醚增值税税率9%
  • 减免增值税可以税前扣除吗
  • 纳税人证明是完税证明吗
  • 年末会计账上应该注意哪些
  • 补发工资如何缴纳工人所得税的法条
  • 金税盘服务费计入什么会计科目
  • 全年一次性奖金计税方式2023
  • 企业分立账务处理办法
  • macbook和ipad怎么互通屏幕
  • 发生额对照表
  • 退回的发票怎么处理
  • 社会保险生育险能报多少
  • 浅谈12 条用于 Linux 的 MySQL/MariaDB 安全最佳实践
  • win11 zen2
  • gcasInstallHelper.exe是什么进程 作用是什么 gcasInstallHelper进程查询
  • csrsv.exe是什么
  • 承包经营所得税税率表
  • 公司员工社保由总公司代缴证明
  • 除湿器属于固定资产什么类别
  • 人力资源外包可以去吗
  • 小企业如何记账
  • 在项目进行过程中,一个开发人员
  • 3d人体骨骼模型软件
  • 利用Linux Find命令查找文件方法记录 快速查找文件位置
  • php运用
  • 工业企业采购流程
  • 运营费的税率
  • 无形资产和固定资产减值准备可以转回吗
  • uniapp组件使用
  • php变量名称中可以包含哪些元素
  • 不征税收入和免税收入有哪些项目
  • 公司收到社保局的提醒函怎么办
  • 物品数量怎么填写
  • 建筑企业结转收入方法
  • 进项税和销项税月末怎么结转
  • 主营业务成本如何设置明细
  • 出售转让固定资产的账务处理
  • 个人转租房屋需要交房产税吗
  • 公司接待考察团的费用入哪个科目
  • 处置长投其他权益变动要转损益吗
  • 汇率的差额如何处理
  • 增值税税控系统专用设备费及技术维护费抵扣
  • 会计行政法规包括哪些条例?具体说明?
  • mysql中key 、primary key 、unique key 与index区别
  • windows xp/2000/2003系统自动登陆设置方法无需输入密码
  • xp系统怎么关闭更新系统
  • 写出linux安装的详细步骤
  • 如何设置windows密码
  • win8打开ie
  • windows预体验版本遇到问题
  • bzip2 bunzip2 bzcat参数使用
  • win7开机无信号之后黑屏
  • linux就该这么些
  • 开发环境配置是干嘛
  • 深入理解bootstrap
  • node.js jquery
  • eclipse4.9.0安装windowbuilder
  • python ssh 远程执行命令
  • jquery的使用方法
  • 用javascript
  • shell脚本 -ne 0
  • python放歌
  • [置顶]马粥街残酷史
  • js对象创建方法
  • 医保可以异地交嘛
  • 向境外付款需要代扣代缴所得税吗
  • 政府补贴 收入
  • 同业借款的主要用途
  • 济南市中区税务局办税大厅
  • 免责声明:网站部分图片文字素材来源于网络,如有侵权,请及时告知,我们会第一时间删除,谢谢! 邮箱:opceo@qq.com

    鄂ICP备2023003026号

    网站地图: 企业信息 工商信息 财税知识 网络常识 编程技术

    友情链接: 武汉网站建设