8.0 泛型

通过之前的学习,读者可以了解到,把一个对象存入集合后,再次取出该对象时,该对象的编译类型就变成了Object类型(尽管其在运行时类型没有改变)。集合设计成这样,提高了它的通用性,但是也带来了一些类型不安全和繁琐的问题,例如,集合可以同时存储任何类型的对象,通常对取出之后的对象都需要强制类型转换,而且如果不知道实际参数类型的情况,也无法进行强制类型转换。为了解决这些问题,从JDK 5版本开始引入了泛型,本章将围绕泛型的相关内容进行讲解。

1. 泛型基础

1.1. 泛型的概念

泛型是在JDK 5中引入的一个新特性,其本质是参数化类型,也就是将具体的类型形参化,参数化的类型(可以称之为类型形参)在使用或者调用时传入具体的类型(类型实参),类似于调用方法时传入实参才确定方法形参的具体值。泛型的声明由一对尖括号和类型形参组成,类型形参定义在尖括号中间,定义类、接口和方法时使用泛型声明,定义出的类、接口和方法分别称为泛型类、泛型接口和泛型方法。

1.2. 泛型的定义

使用泛型编程,会在使用或者调用时传入具体的类型时才确定最终的数据类型,所以集合需要存储什么类型的数据,在创建集合时传入对应的类型即可。

定义泛型时类型形参由一对尖括号(<>)包含在中间,使用或者调用泛型时,需要将类型实参写在尖括号(<>)之间。

JDK 5之后的类库中很多重要的类和接口都引入了泛型,例如集合体系中的类和接口。下面分别演示未引入泛型和使用泛型编程的区别,体验泛型具体有什么好处。

(1)未引入泛型前

public class TestDemo {@Testpublic void test(){// 创建一个只保存Integer类型的List集合List intList = new ArrayList();intList.add(1);intList.add(2);//因为失误存放了Integer类型之外的字符串数据intList.add("3");for (int i = 0; i < intList.size(); i++) {/*因为List里面默认取出的全部Object对象,所以使用之前需要进行强* 制类型转换。集合内最后一个元素进行转换时候将出现类型转换异常* */Integer num=(Integer)intList.get(i);}}
}

(2)引入泛型后

public class TestDemo {@Testpublic void test(){// 创建一个只保存Integer类型的List集合List<Integer> intList = new ArrayList<Integer>();intList.add(1);intList.add(2);//下面代码将出现编译时异常intList.add("3");for (int i = 0; i < intList.size(); i++) {//下面的代码无需强制类型转换Integer num=intList.get(i);}}
}

1.3. 泛型的好处

使用泛型的好处如下:

  • 提高类型的安全性

使用泛型后,将类型的检查从运行期提前到编译期,编译期的类型检查,可以更早、更容易的找出因为类型限制而导致的类型转换异常,从而提高程序的可靠性。

  • 消除强制类型转换

使用泛型后,程序会记住当前的类型形参,从而无需对传入的实参值进行强制类型转换。使得代码更加清晰和筒洁,可读性更高。

  • 提高代码复用性

使用泛型后,可以更好的将程序中通用的代码提取出来,在使用时传入不同类型的参数,避免了多次编写相同功能的代码,以提高代码的复用性。

  • 拥有更高的运行效率

使用泛型之前,传入的实际参数值作为Object类型传递时,需要进行封箱和拆箱操作,会消耗程序的一定的开销。使用泛型后,类型形参中都需要使用引用数据类型,即传入的实际参数的类型都是对应引用数据类型,避免了封箱和拆箱操作,降低了程序运行的开销,提高了程序运行的效率。

2. 泛型类

2.1. 泛型类的语法格式

定义类时,在类名后加上尖括号包含类型形参,定义的这个类就是泛型类。创建泛型类的实例对象时传入不同的类型实参,从而可以动态生成无数个该泛型类的子类。在JDK类包中泛型类的最典型应用就是各种容器类,如ArrayList、HashMap等。定义泛型类的格式具体如下。

public class 类名<类型形参变量>{}

上述语法格式中,类名<类型形参变量>是一个整体的数据类型,通常称为泛型类型;类型形参变量,没有特定的意义,可以是任意一个字母,但是为了提高可读性,建议使用有意义的字母。一般情况下使用较多的字母及意义如下所示。

  • E:表示Element(元素),常用在java Collection里使用,如 List<E>,Iterator<E>,Set<E>。

  • K,V:表示Key,Value(Map的键值对)。

  • N:表示Number(数字)。

  • T:表示Type(类型),如String,Integer等。

2.2. 泛型类的定义与创建

定义:定义泛型类时,类的构造方法名称还是类的名称,类型形参变量可以用于属性的类型、方法的返回值类型和方法的参数类型。

创建:创建泛型类的对象时,不强制要求传入类型实参,如果传入类型实参,类型形参会根据传入的类型实参做相应的限制,此时泛型才会起到本应起到的限制作用。如果不传入类型实参的话,在泛型类中使用类型形参的方法或成员变量定义的类型可以为任何的类型。

(1)定义泛型类Goods,声明私有变量info,定义构造方法,与getter/setter方法。

/*** 定义泛型类Goods* @param <T>*/
public class Goods<T> {// 类型形参变量作用于属性的类型private T info ;//无参构造方法public Goods(){}// 类型形参变量作用于构造方法的参数类型public Goods(T info) {this.info = info;}// 类型形参变量作用于方法的参数类型public void setInfo(T info){this.info = info ;}// 类型形参变量作用于方法的返回值类型public T getInfo(){return this.info ;}
}

(2)定义测试类,创建Goods对象,分别调用setInfo()方法和getInfo()方法。

public class TestDemo{@Testpublic void test(){Goods goods = new Goods();goods.setInfo("电脑");System.out.println(goods.getInfo() + ":" + goods.getInfo().getClass());goods.setInfo(200);System.out.println(goods.getInfo() + ":" + goods.getInfo().getClass());}@Testpublic void test2(){Goods<String> goods = new Goods<>();goods.setInfo("电脑");System.out.println(goods.getInfo() + ":" + goods.getInfo().getClass());}@Testpublic void test3(){Goods<Integer> goods= new Goods<>();goods.setInfo(200);System.out.println(goods.getInfo() + ":" + goods.getInfo().getClass());}
}

2.3. 泛型类的练习

定义一个泛型类Point<T>,其中包含x和y两个类型为T的成员,定义类的带参构造方法,为x和y定义setter和getter,定义show方法输出坐标。

编写测试方法,创建Point<Integer>对象和Point<Double>对象。

  • Point<T>类

public class Point<T> {//泛型成员private T x;private T y;//构造方法public Point(T x,T y){this.x = x;this.y = y;}public T getX() {return x;}public void setX(T x) {this.x = x;}public T getY() {return y;}public void setY(T y) {this.y = y;}//输出坐标public void show(){System.out.println("x坐标是:" + x + ",y坐标是:" + y);}
}
  • 测试类

public class Test {public static void main(String[] args) {//Integer型Point<Integer> p1 = new Point(1,2);p1.show();//Double型Point<Double> p2 = new Point(1.1,2.2);p2.show();}
}

3. 泛型接口

3.1. 泛型接口的语法格式

定义泛型接口和定义泛型类的语法格式类似,在接口名称后面加上尖括号包含类型形参即可。集合相关的接口中很多接口也都是泛型接口,如Collection、List等。定义泛型接口的基本语法格式如下所示:

public interface 接口名称<类型形参变量>{}

3.2. 泛型接口的应用

泛型接口可以有两种类方式实现,第一种是使用非泛型类实现泛型接口,第二种是使用泛型类实现泛型接口。

(1)使用非泛型类实现泛型接口

当使用非泛型类实现接口时,需要明确接口的泛型类型,也就是需要将类型实参传入到接口中。此时实现类重写接口中使用泛型的地方,都需要将类型形参替换成传入的类型实参,这样可以直接使用泛型接口的类型实参,具体代码如下所示。

  • 定义一个泛型接口

public interface Student<T> {public abstract void show(T t);
}
  • 定义泛型接口的实现类,在泛型接口后指定类型实参以明确接口的泛型类型。

public class StudentImpl implements Student<String>{@Overridepublic void show(String s) {System.out.println(s);}
}
  • 定义测试类,创建Student对象时,传入的类型实参必须是String类型,否则编译异常。

public class TestDemo {@Testpublic void test(){Student<String> stu = new StudentImpl();stu.show("你好,我是张三");}}

(2)使用泛型类实现泛型接口

当使用泛型类实现泛型接口时,需要将泛型的声明加在实现类中,并且泛型类和泛型接口使用的都是同一个类型形参变量,否则会出现编译异常。具体代码如下所示。

  • 定义一个泛型接口

public interface Student<T> {public abstract void show(T t);
}
  • 定义泛型接口的实现类,使用泛型类实现泛型接口

public class StudentImpl<T> implements Student<T> {@Overridepublic void show(T t) {System.out.println(t);}
}
  • 定义测试类,创建Student对象时,传入不同的类型实参,并分别调用show()方法进行输出验证。

public class TestDemo {@Testpublic void test(){Student<String> stu1 = new StudentImpl<>();stu1.show("你好,我是张三");Student<Integer> stu2 = new StudentImpl<>();stu2.show(20);}}

4. 泛型方法

4.1. 泛型方法的语法格式

泛型方法是将类型形参的声明放在修饰符和返回类型之间的方法。在Java程序中,定义泛型方法常用的格式如下所示:

public [static] [final] <类型形参> 返回值类型 方法名 (形式参数列表){}

定义泛型方法注意事项如下所示:

  • 访问权限修饰符(包括private、public、protected)、static和 final都必须写在类型形参列表的前面。

  • 返回值类型必须写在类型形参列表的后面。

  • 泛型方法可以在泛型类中,也可以在普通类中。

  • 泛型类中的任何方法本质上都是泛型方法,所以在实际使用中很少会在泛型类中再用上面的形式来定义泛型方法。

  • 类型形参可以用在方法体中修饰局部变量,也可以修饰方法的返回值。

  • 泛型方法可以是实例方法(没有用static修饰,也叫非静态方法)也可以是静态方法。

4.2. 泛型方法的应用

如果泛型方法是实例方法,则需要使用对象名进行调用;如果泛型方法是静态方法,可以使用类名进行调用,泛型方法的两种使用方式。

  • 方式一

对象名|类名.<类型实参> 方法名(类型实参列表);
  • 方式二

对象名|类名.方法名(类型实参列表);

两种调用泛型方法的差别在于,方法名之前是否显式地指定了类型实参。调用时是否需要显式地指定了类型实参,要根据泛型方法的声明形式,以及调用时编译器能否从实际参数表中获得足够的类型信息决定,如果编译器能够根据实际参数推断出参数类型,就可以不指定类型实参,反之则需要指定类型实参。

4.3. 泛型方法的练习

下面通过一个案例,演示泛型方法的定义与使用,具体代码如下所示。

(1)定义Student类,在类中定义一个静态泛型方法和一个普通泛型方法。

public class Student {// 静态泛型方法public static <T> void show(T t) {System.out.println(t + ":" + t.getClass());}// 普通泛型方法public <T> void study(T t) {System.out.println(t + ":" + t.getClass());}
}

(2)定义测试方法,调用方法测试结果。

public class TestDemo {@Testpublic void test(){//静态方法Student.show("你好,我是张三");            // 使用方式一调用静态的泛型方法Student.<String>show("你好,我是张三");    // 使用方式二调用静态的泛型方法//普通方法Student stu = new Student();stu.study("好好学习");          // 使用方式一调用普通的泛型方法stu.<String>study("好好学习");  // 使用方式二调用普通的泛型方法}
}

从运行结果可以得出,泛型方法可以在非泛型类中定义,并且在调用泛型方法的时候确定泛型的具体类型 。上述结果中虽然使用方式一和方式二的输出结果一致,但是方式一隐式的传入类型实参,不能直观的查看到调用的方法是泛型方法,不利于代码的阅读和维护,通常建议使用第二种方式调用泛型方法。

5. 类型通配符

类型通配符使用一个问号(?)表示,类型通配符可以匹配任何类型的类型实参。

下面使用一个案例演示类型通配符的使用。

(1)定义泛型类Student,声明私有变量info,定义有参构造方法和getter方法。

/*** 定义泛型类Student* @param <T>*/
public class Student<T> {private T info;public Student(T info) {this.info = info;}public T getInfo() {return info;}
}

(2)定义测试方法,创建Student对象,分别传入String类型和Integer类型的类型实参,进行测试。

public class TestDemo {@Testpublic void test(){// 创建student对象,传入String类型的类型实参Student<?> student = new Student<String>("张三");System.out.println( student.getInfo()+":"+student.getInfo().getClass());// 创建student对象,传入Integer类型的类型实参student =new Student<Integer>(20);System.out.println( student.getInfo()+":"+student.getInfo().getClass());}
}
  • 不使用通配符的情况一

如果创建Student对象时,不使用类型通配符,而是使用指定的类型实参,会出现编译异常,具体如下图所示。

  • 不使用通配符的情况二

使用Object代替类型通配符?接收所有的类型,也会出现编译异常,具体如下图所示。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/174948.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Git的原理与使用(一):Git的基本操作(包含:版本回退)

Git原理与使用一 一.Git的初识与安装1.什么是Git2.如何安装Git1.git命令与git help(Git下的"man手册")2.centos下安装Git3.ubantu下安装Git 二.Git的前置操作与前置知识1.创建Git本地仓库2.配置Git3.理解Git的分区1.工作区2.暂存区3.版本库4.分区关系总结 三.添加文件…

springboot启动Table ‘xxx‘ already exists

jpa.generate-ddl和jpa.hibernate.ddl-auto都可以控制是否执行datasource.schema脚本&#xff0c;来初始化数据库结构&#xff0c;只要有一个为可执行状态就会执行&#xff0c;比如jpa.generate-ddl:true或jpa.generate-ddl:update&#xff0c;并没有相互制约上下级的关系。 要…

Android修行手册 - 使用ViewPager2实现画廊效果

Unity3D特效百例案例项目实战源码Android-Unity实战问题汇总游戏脚本-辅助自动化Android控件全解手册再战Android系列Scratch编程案例软考全系列Unity3D学习专栏蓝桥系列ChatGPT和AIGC &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分…

小航助学题库蓝桥杯题库stem选拔赛(22年3月)(含题库教师学生账号)

需要在线模拟训练的题库账号请点击 小航助学编程在线模拟试卷系统&#xff08;含题库答题软件账号&#xff09;_程序猿下山的博客-CSDN博客 需要在线模拟训练的题库账号请点击 小航助学编程在线模拟试卷系统&#xff08;含题库答题软件账号&#xff09;_程序猿下山的博客-CSD…

解决ansible批量加入新IP涉及known_hosts报错的问题

我们把一批新的IP加入到ansible的hosts文件&#xff0c;比如/etc/ansible/hosts&#xff0c;往往会有这样的提示&#xff0c; 因为本机的~/.ssh/known_hosts文件中并有fingerprint key串&#xff0c;使用ssh连接目标主机时&#xff0c;一般会提示是否将key字符串加入到~/.ssh/…

如何使用内网穿透将Tomcat网页发布到公共互联网上【内网穿透】

文章目录 前言1.本地Tomcat网页搭建1.1 Tomcat安装1.2 配置环境变量1.3 环境配置1.4 Tomcat运行测试1.5 Cpolar安装和注册 2.本地网页发布2.1.Cpolar云端设置2.2 Cpolar本地设置 3.公网访问测试4.结语 前言 Tomcat作为一个轻量级的服务器&#xff0c;不仅名字很有趣&#xff0…

中国北斗:守护萨雷兹湖一方安澜

中国北斗&#xff1a;守护萨雷兹湖一方安澜 在第三届“一带一路”国际合作高峰论坛数字经济高级别论坛上&#xff0c;由中国经济信息社、国家发展改革委高技术司、国家数据局联合编制的《数字“慧”就发展之路》中英文图文集正式发布&#xff0c;展现了中国与共建“一带一路”国…

PHP中关于func_get_args()方法

首先呢这个函数出现的是比较早的,大致应该是PHP4出现的, func_get_args — 返回一个包含函数参数列表的数组 说明 func_get_args(): array 获取函数参数列表的数组。 该函数可以配合 func_get_arg() 和 func_num_args() 一起使用&#xff0c;从而使得用户自定义函数可以接…

如何将mobi、awz3、epub格式转化为pdf

偶然之间有个需求就是网上下载了一些书籍的格式没法打开看&#xff0c;或者是想把kindle的书籍转换成pdf 那么经过一番折腾找到了两个可以用的工具站分享给大家&#xff0c;有需要的可是尝试下&#xff0c;小编这边测试了可以用&#xff0c;就是下载的时候慢的一匹。。。 第一…

PHP 双门双向门禁控制板实时监控源码

本示例使用设备&#xff1a; 实时网络双门双向门禁控制板可二次编程控制网络继电器远程开关-淘宝网 (taobao.com) <?PHPheader("content-type:text/html;charsetGBK");$ThisIpget_local_ip(); //获取电脑IP地址 $server udp://.$ThisIp.:39192; $sock…

MATLAB中fft与fftshift的区别

两者的区别在于&#xff1a; fft函数将时域信号转换为频域信号&#xff0c;即将信号从时间域转换为频率域。fftshift函数用于对fft计算结果进行移位操作&#xff0c;将频域信号的零频率分量移到频谱的中心&#xff0c;方便观察和处理。fftshift函数将fft计 算结果沿着中心点进…

MySQL--InnoDB引擎

InnoDB引擎 逻辑存储引擎 表空间→段→区→页→行 Tablespace 表空间&#xff08;ibd文件&#xff09;&#xff1a;一个mysql实例可以对应多个表空间&#xff0c;用于存储记录、索引等数据Segment 段&#xff1a;段分为数据段、索引段、回滚段&#xff0c;InnoDB是索引组织表…

【Unity程序技巧】加入缓存池存储地图资源,节省资源,避免多次CG

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;Uni…

Python语言学习笔记之二(基础语法)

本课程对于有其它语言基础的开发人员可以参考和学习&#xff0c;同时也是记录下来&#xff0c;为个人学习使用&#xff0c;文档中有此不当之处&#xff0c;请谅解。 Python几种字符串的表示&#xff1a; 在Python中&#xff0c;字符串是一种基本的数据类型&#xff0c;可以使…

ChatGPT等模型:到2026年,将消耗尽高质量训练数据

《麻省理工技术评论》曾在官网发表文章表示&#xff0c;随着ChatGPT等大模型的持续火热&#xff0c;对训练数据的需求越来越大。大模型就像是一个“网络黑洞”不断地吸收&#xff0c;最终会导致没有足够的数据进行训练。 而知名AI研究机构Epochai直接针对数据训练问题发表了一…

Python Subprocess教程:创建和管理子进程的完整指南

更多Python学习内容&#xff1a;ipengtao.com 在Python中&#xff0c;Subprocess模块为我们提供了强大的工具&#xff0c;使得创建和管理子进程变得十分便捷。本文将深入探讨Subprocess的各种功能和用法&#xff0c;通过丰富的示例代码&#xff0c;带你领略其强大的子进程管理能…

s_v_web_id或fp协议过签名,dy滑块

某音s_web_id或fp协议过签名 ‘h5_sdk_version’, ‘2.36.0’ "search_impr":{"entity_id":"1135137973613200"},"link_item_list":null,"user_permissions":null,"offline_info_list":null,"is_cf":…

迁移redis数据库中的数据到另一台服务器

方案一 下面我使用的redis是用docker安装的&#xff0c;不是通过下载安装包安装的&#xff0c;所以和我安装方式不一样的小伙伴可以不看&#xff0c;因为很多操作是基于docker的 话不多说&#xff0c;直接开搞&#xff01; 1.首先一定要确保两台服务器上面的redis版本要一致…

C++:OJ练习(每日练习系列)

编程题&#xff1a; 题一&#xff1a;把字符串转换成整数 把字符串转换成整数_牛客题霸_牛客网 示例1 输入&#xff1a; "2147483647" 返回值&#xff1a; 2147483647思路一&#xff1a; 第一步&#xff1a;it从str的第一个字符开始遍历&#xff0c;定义一个最后输…

使用 SIEM 管理安全事件

每家公司都必须处理检测、管理和解决安全事件&#xff0c;未能制定事件响应计划可能会对任何组织产生重大的影响&#xff0c;无论是在财务损失还是声誉损害方面。本文探讨了事件响应的重要性、检测和管理事件的关键要素&#xff0c;以及帮助组织处理安全事件的最佳实践。 安全…