JavaSe-泛型机制详解

news2024/11/16 13:51:46

1 理解泛型的本质

JDK 1.5开始引入Java泛型(generics)这个特性,该特性提供了编译时类型安全检测机制,允许程序员在编译时检测到非法的类型。

泛型的本质是参数化类型,即给类型指定一个参数,然后在使用时再指定此参数具体的值,那样这个类型就可以在使用时决定了。这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。

为了兼容之前的版本,Java泛型的实现采取了“伪泛型”的策略,即Java在语法上支持泛型,但是在编译阶段会进行所谓的“类型擦除”(Type Erasure),将所有的泛型表示(尖括号中的内容)都替换为具体的类型(其对应的原生态类型)。

在这里插入图片描述

2 泛型的作用

泛型有四个作用:类型安全、自动转换、性能提升、可复用性。即在编译的时候检查类型安全,将所有的强制转换都自动和隐式进行,同时提高代码的可复用性。

在这里插入图片描述

2.1 泛型如何保证类型安全

在没有泛型之前,从集合中读取到的每一个对象都必须进行类型转换,如果不小心插入了错误的类型对象,在运行时的转换处理就会出错。

比如:没有泛型的情况下使用集合:

	    public static void noGenericTest() {
	        // 编译正常通过,但是使用的时候可能转换处理出现问题
	        ArrayList arr = new ArrayList();
	        arr.add("加入一个字符串");
	        arr.add(1);
	        arr.add('a');
	      }

有泛型的情况下使用集合:

		public static void genericTest() {
		        // 编译不通过,直接提示异常,Required type:String
		        ArrayList<String> arr = new ArrayList<>();
		        arr.add("加入一个字符串");
		        arr.add(1);
		        arr.add('a');
		}

有了泛型后,会对类型进行验证,所以集合arr在编译的时候add(1)、add(‘a’) 都会编译不通过。

这个过程相当于告诉编译器每个集合接收的对象类型是什么,编译器在编译期就会做类型检查,告知是否插入了错误类型的对象,使得程序更加安全,增强了程序的健壮性。

2.2 类型自动转换,消除强转

泛型的另一个好处是消除源代码中的强制类型转换,这样代码可读性更强,且减少了转换类型出错的可能性。

以下面的代码为例子,以下代码段需要强制转换,否则编译会通不过:

ArrayList list = new ArrayList();
list.add(1);
int i = (int) list.get(0); // 需强转

当重写为使用泛型时,代码不需要强制转换:

ArrayList list = new ArrayList<>();
list.add(1);
int i = list.get(0); // 无需转换

2.3 避免装箱、拆箱,提高性能

在非泛型编程中,将简单类型作为Object传递时会引起Boxing(装箱)和Unboxing(拆箱)操作,这两个过程都是具有很大开销的。引入泛型后,就不必进行Boxing和Unboxing操作了,所以运行效率相对较高,特别在对集合操作非常频繁的系统中,这个特点带来的性能提升更加明显。

泛型变量固定了类型,使用的时候就已经知道是值类型还是引用类型,避免了不必要的装箱、拆箱操作。

object a=1;//由于是object类型,会自动进行装箱操作。

int b=(int)a;//强制转换,拆箱操作。这样一去一来,当次数多了以后会影响程序的运行效率。

使用泛型后

public static T GetValue(T a) {
  return a;
}

public static void Main(){
  int b=GetValue(1);//使用这个方法的时候已经指定了类型是Interger,所以不会有装箱和拆箱的操作。
}

2.4 提升程序可复用性

引入泛型的另一个意义在于:适用于多种数据类型执行相同的代码(代码复用)

我们通过下面的例子来说明,代码如下:

private static int add(int a, int b) {
System.out.println(a + “+” + b + “=” + (a + b));
return a + b;
}

private static float add(float a, float b) {
System.out.println(a + “+” + b + “=” + (a + b));
return a + b;
}

private static double add(double a, double b) {
System.out.println(a + “+” + b + “=” + (a + b));
return a + b;
}

如果没有泛型,要实现不同类型的加法,每种类型都需要重载一个add方法;通过泛型,我们可以复用为一个方法:

private static double add(T a, T b) {
System.out.println(a + “+” + b + “=” + (a.doubleValue() + b.doubleValue()));
return a.doubleValue() + b.doubleValue();
}

3 泛型的使用

3.1 泛型类

泛型类是指把泛型定义在类上,具体的定义格式如下:

public class 类名 <泛型类型1,…> {
// todo
}

注意事项:泛型类型必须是引用类型,非基本数据类型

定义泛型类,在类名后添加一对尖括号,并在尖括号中填写类型参数,参数可以有多个,多个参数使用逗号分隔:

public class GenericClass<a,b,c> {
// todo
}

当然,这个后面的参数类型也是有规范的,不能像上面一样随意,通常类型参数我们都使用大写的单个字母表,可以任意指定,但是还是建议使用有字面含义的,让人通俗易懂,下面的字母可以参考使用:

T:任意类型 type
E:集合中元素的类型 element
K:key-value形式 key
V: key-value形式 value
N: Number(数值类型)
?: 表示不确定的java类型

这边举个例子,假设我们写一个通用的返回对象,对象中的某个字段的类型不定:

		@Data
		public class Response<T> {
		    /**
		     * 状态
		     */
		    private boolean status;
		    /**
		     * 编码
		     */
		    private Integer code;
		    /**
		     * 消息
		     */
		    private String msg;
		    /**
		     * 接口返回内容,不同的接口返回的内容不一致,使用泛型数据
		     */
		    private T data;
		
		    /**
		     * 构造
		     * @param status
		     * @param code
		     * @param msg
		     * @param data
		     */
		    public Response(boolean status,int code,String msg,T data) {
		        this.status = status;
		        this.code = code;
		        this.msg = msg;
		        this.data = data;
		    }
		}

做成泛型类,他的通用性就很强了,这时候他返回的情况可能如下:

先定义一个用户信息对象

@Data
public class UserInfo {
    /**
     * 用户编号
     */
    private String userCode;
    /**
     * 用户名称
     */
    private String userName;
}

尝试返回不同的数据类型:

        /**
         * 返回字符串
         */
        Response<String> responseStr = new Response<>(true,200,"success","Hello Word");

        /**
         * 返回用户对象
         */
        UserInfo userInfo = new UserInfo();
        userInfo.setUserCode("123456");
        userInfo.setUserName("Brand");
        Response<UserInfo> responseObj = new Response<>(true,200,"success",userInfo);

输出结果如下:

	{
		"status": true,
		"code": 200,
		"msg": "success",
		"data": "Hello Word"
	}
	// 和
	{
		"status": true,
		"code": 200,
		"msg": "success",
		"data": {
			"user_code": "123456",
			"user_name": "Brand"
		}
	}

3.2 泛型接口

泛型方法概述:把泛型定义在接口上,他的格式如下

public interface 接口名 {
// todo
}

注意点1:方法声明中定义的形参只能在该方法里使用,而接口、类声明中定义的类型形参则可以在整个接口、类中使用。当调用fun()方法时,根据传入的实际对象,编译器就会判断出类型形参T所代表的实际类型。

		public interface GenericInterface<T> {
				void show(T value);}
		}
		public class StringShowImpl implements GenericInterface<String> {
				@Override
				public void show(String value) {
				System.out.println(value);
				}
		}
		public class NumberShowImpl implements GenericInterface<Integer> {
				@Override
				public void show(Integer value) {
				System.out.println(value);
				}
		}

注意点2:使用泛型的时候,前后定义的泛型类型必须保持一致,否则会出现编译异常:

// 编译的时候会报错,因为前后类型不一致
GenericInterface genericInterface = new NumberShowImpl();
// 编译正常,前面泛型接口不指定类型,由new后面的实例化来推导。
GenericInterface g1 = new NumberShowImpl();
GenericInterface g2 = new StringShowImpl();

3.3 泛型方法

泛型方法,是在调用方法的时候指明泛型的具体类型 。定义格式如下:

public <泛型类型> 返回类型 方法名(泛型类型 变量名) {
// todo
}

举例说明,下面是一个典型的泛型方法,根据传入的对象,打印它的值和类型:

		/**
		     * 泛型方法    
		     * @param <T> 泛型的类型
			  * @param c 传入泛型的参数对象
		     * @return T 返回值为T类型
		     * 说明:
		     *   1)public 与 返回值中间<T>非常重要,可以理解为声明此方法为泛型方法。
		     *   2)只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
		     *   3)<T>表明该方法将使用泛型类型T
		     *   4)与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E等形式的参数常用于表示泛型。
		     */
		    public <T> T genercMethod(T c) {
		        System.out.println(c.getClass());
		        System.out.println(c);
		        return c;
		   } 
		 
		public static void main(String[] args) {
		   //这里的泛型跟下面调用的泛型方法可以不一样。
		    GenericsClassDemo<String> genericString  = new GenericsClassDemo("Hello World"); 
		    String str = genericString.genercMethod("brand");//传入的是String类型,返回的也是String类型
		    Integer i = genericString.genercMethod(100);//传入的是Integer类型,返回的也是Integer类型
		}

输出结果如下:

class java.lang.String
brand 
 
class java.lang.Integer
100

从上面可以看出,泛型方法随着我们的传入参数类型不同,执行的效果不同,拿到的结果也不一样。**泛型方法能使方法独立于类而产生变化。**这里是泛型类的方法和泛型方法的主要区别,注意理解。方法声明中定义的形参只能在该方法里使用,而接口、类声明中定义的类型形参则可以在整个接口、类中使用。当调用泛型方法时,根据传入的实际对象,编译器就会判断出类型形参所代表的实际类型。

3.4 泛型通配符(上下界)

Java泛型的通配符是用于解决泛型之间引用传递问题的特殊语法, 主要有以下三类:

  • 无边界的通配符,使用精确的参数类型
  • 关键字声明了类型的上界,表示参数化的类型可能是所指定的类型,或者是此类型的子类
  • 关键字声明了类型的下界,表示参数化的类型可能是指定的类型,或者是此类型的父类

结构如下:

// 表示类型参数可以是任何类型
public class B<?> {
}

// 上界:表示类型参数必须是A或者是A的子类
public class B {
}

// 下界:表示类型参数必须是A或者是A的超类型
public class B {
}

上界示例:

		class Info<T extends Number>{    // 此处泛型只能是数字类型
		    private T var ;        // 定义泛型变量
		    public void setVar(T var){
		        this.var = var ;
		    }
		    public T getVar(){
		        return this.var ;
		    }
		    public String toString(){    // 直接打印
		        return this.var.toString() ;
		    }
		}
		public class demo1{
		    public static void main(String args[]){
		        Info<Integer> i1 = new Info<Integer>() ;        // 声明Integer的泛型对象
		    }
		}

下界示例:

	class Info<T>{
	    private T var ;        // 定义泛型变量
	    public void setVar(T var){
	        this.var = var ;
	    }
	    public T getVar(){
	        return this.var ;
	    }
	    public String toString(){    // 直接打印
	        return this.var.toString() ;
	    }
	}
	public class GenericsDemo21{
	    public static void main(String args[]){
	        Info<String> i1 = new Info<String>() ;        // 声明String的泛型对象
	        Info<Object> i2 = new Info<Object>() ;        // 声明Object的泛型对象
	        i1.setVar("hello") ;
	        i2.setVar(new Object()) ;
	        fun(i1) ;
	        fun(i2) ;
	    }
	    //只能接收String或Object类型的泛型,String类的父类只有Object类
	    public static void fun(Info<? super String> temp) { 
	        System.out.print(temp + ", ") ;
	    }
	}

4 泛型实现原理

Java泛型这个特性是从JDK 1.5才开始加入的,因此为了兼容之前的版本,Java泛型的实现采取了“伪泛型”的策略,即Java在语法上支持泛型,但是在编译阶段会进行所谓的“类型擦除”(Type Erasure),

将所有的泛型表示(尖括号中的内容)都替换为具体的类型(其对应的原生态类型),就像完全没有泛型一样。

泛型本质是将数据类型参数化,它通过擦除的方式来实现,即编译器会在编译期间「擦除」泛型语法并相应的做出一些类型转换动作。

4.1 泛型的类型擦除原则

  • 消除类型参数声明,即删除<>及其包围的部分。
  • 根据类型参数的上下界推断并替换所有的类型参数为原生态类型:如果类型参数是无限制通配符或没有上下界限定则替换为Object,如果存在上下界限定则根据子类替换原则取类型参数的最左边限定类型(即父类)。
  • 为了保证类型安全,必要时插入强制类型转换代码。
  • 自动产生“桥接方法”以保证擦除类型后的代码仍然具有泛型的“多态性”。

4.2 擦除的方式

擦除类定义中的类型参数 - 无限制类型擦除

当类定义中的类型参数没有任何限制时,在类型擦除中直接被替换为Object,即形如

和<?>的类型参数都被替换为Object。

擦除类定义中的类型参数 - 有限制类型擦除

当类定义中的类型参数存在限制(上下界)时,在类型擦除中替换为类型参数的上界或者下界,比如形如

和<? extends Number>的类型参数被替换为Number,<? super Number>被替换为Object。
在这里插入图片描述

擦除方法定义中的类型参数

擦除方法定义中的类型参数原则和擦除类定义中的类型参数是一样的,这里仅以擦除方法定义中的有限制类型参数为例。

在这里插入图片描述
参考文章;https://blog.csdn.net/zjjcchina/article/details/121417950

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

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

相关文章

2022最后一个月如何快速发表一篇SCI

距2022年结束仅剩不到1个月&#xff0c;年终考核迫在眉睫&#xff0c;您的年初计划是否都已完成&#xff1f;2023年的科研计划是否也已提上日程&#xff1f;想要在2023年论文发表快人一步&#xff0c;早安排才是关键&#xff01; 进入12月&#xff0c;我处EA-ISET协会重点SCI/…

基于jsp+mysql+ssm手机综合类门户网站-计算机毕业设计

项目介绍 手机综合类门户网站采用ssm框架和eclipse编辑器、MySQL数据库设计并实现的,主要包括系统手机评测管理模块、文章管理模块、手机新闻管理、所有评论管理、登录模块、和退出模块等多个模块。 管理员的登录模块&#xff1a;管理员登录系统对本系统其他管理模块进行管理。…

vue3 速成教程(上)

学 vue3 通过官方文档更详细&#xff0c;不过阅读本博客&#xff0c;可以更容易理解&#xff0c;且帮你速成&#xff01; 官方文档&#xff08;记得将API风格偏好切换为 组合式 否则你学的是vue2&#xff09; https://cn.vuejs.org/guide/introduction.html 学习前的准备 创建…

HBase的读写流程

HBase的读流程 客户端从zk获取.META.表所在的regionserver&#xff1b;去对应的regionserver读取.META.表&#xff0c;获取region所在信息&#xff08;region在哪个regionserver上保存的信息&#xff09;&#xff1b;客户端到了regionserver时&#xff0c;先找到region&#xf…

MongoDB聚合小tips

MongoDB对于嵌套&#xff08;Embedded&#xff09;数组的过滤 首先定义下结构 {"play_id": "639045efae627e2aacf35dce","region_id": 1106,"point_list": [{"id": "1faf5aa9-e262-45fe-96dd-64395c96cf5c",&qu…

Allegro如何检查过孔是否重叠的四种方法操作指导

Allegro如何检查过孔是否重叠的四种方法操作指导 Allegro可以检查过孔是否重叠,避免重孔的情况的出现,具体检查方法如下 一.非同名网络过孔重叠 以下图为例 打开DRC开关,EnableDRC 打开Constraints-Mode 打开Spacing规则via的规则 可以看到非同名网络过孔,孔重叠在一…

C#多线程之Thread,ThreadPool,Task,Parallel

总目录 文章目录总目录前言一、多线程以及与之相关概念1.基本概念1&#xff09;进程2&#xff09;线程3&#xff09;多线程2.同步、异步1&#xff09;同步方法2&#xff09;异步方法二、Thread1.线程的使用1&#xff09;创建并开启线程2&#xff09;线程的属性设置&方法调用…

【微电网】具有柔性结构的孤岛直流微电网的分级控制(Malab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️❤️&#x1f4a5;&#x1f4a5;&#x1f4a5; &#x1f4dd;目前更新&#xff1a;&#x1f31f;&#x1f31f;&#x1f31f;电力系统相关知识&#xff0c;期刊论文&…

carsim/trucksim获取轮胎侧偏刚度、纵向刚度

本文参考&#xff1a;https://blog.csdn.net/weixin_44902384/article/details/107926814 这个方法适应计算侧偏刚度、纵向刚度&#xff0c;因为魔术公式里y 可以代表侧向力、纵向力 针对上面的内容&#xff0c;有两个问题需要解释。1是魔术公式轮胎中 有的是tan-1 有的是ar…

[Linux]------线程池的模拟实现和读者写者锁问题

文章目录前言一、线程池二、线程安全的单例模式什么是单例模式什么是设计模式单例模式的特点三、STL&#xff0c;智能指针和线程安全STL中的容器是否是线程安全的&#xff1f;智能指针是否是线程安全的&#xff1f;四、其他常见的各种锁五、读者写者问题读写锁读写锁接口初始化…

云开发智能家居客户案例详解(内附拓扑图)

万物互联&#xff0c;大至全世界&#xff0c;小至一间房&#xff0c;物联网和云计算技术的高速发展使得住宅变得愈发智能化。 在“互联网”时代&#xff0c;智能家居开始走入千家万户&#xff0c;不断提升着家居生活的安全性、舒适型、便利性和环保性&#xff0c;逐渐变成人们…

Linux 用户权限

用户权限1、访问权限2、chmod 命令3、chown 命令4、chgrp命令5、权限掩码6、lsattr 命令7、chattr命令8、文件的特别权限suid权限set位权限粘滞位权限&#xff08;Sticky&#xff09;9、ACL访问控制列表setfacl命令getfacl命令示例10、sudo11、SELinux1、访问权限 shell在创建…

SpringBoot2学习笔记--入门及HelloWorld

SpringBoot2学习笔记--入门及HelloWorld1 系统要求1.1、maven设置2、HelloWorld2.1、创建maven工程2.2、引入依赖2.3、创建主程序2.4、编写业务2.5、测试2.6、简化配置2.7、简化部署1 系统要求 ● Java 8 & 兼容java14 . ● Maven 3.3 ● idea 2019.1.2 1.1、maven设置 …

Java版 剑指offer笔记(一)

1.数组中重复的数字 思路1&#xff1a; 使用哈希表&#xff0c;哈希表是一种根据关键码&#xff08;key&#xff09;直接访问值&#xff08;value&#xff09;的一种数据结构。而这种直接访问意味着只要知道key就能在O(1)时间内得到value&#xff0c;因此哈希表常用来统计频率…

软件测试有哪些常用的测试方法?

软件测试是软件开发过程中重要组成部分&#xff0c;是用来确认一个程序的质量或者性能是否符合开发之前提出的一些要求。软件测试的目的有两方面&#xff0c;一方面是确认软件的质量&#xff0c;另一方面是提供信息&#xff0c;例如&#xff0c;给开发人员或者程序经理反馈意见…

4.MyBatis映射

需求分析 1.订单商品数据模型 (1).表 用户表user:记录了购买商品的用户信息 订单表orders:记录了用户所创建的订单信息 订单明细表orderdetail:记录了订单的详细信息 商品表item:记录了商品详细信息 (2).表与表之间的业务关系 在分析表与表之间的业务关系时&#xff0c;需要建…

Nginx的反向代理和负载均衡

Nginx&#xff1a; Nginx作为面试中的大…小头目&#xff0c;自然是不能忽视的&#xff0c;而以下两点就是它能成为面试中头目的招牌。 反向代理和负载均衡 在此之前&#xff0c;我们先对Nginx做一个简单的了解 Nginx概述&#xff1a; Nginx (engine x) 是一个高性能的HTTP…

Ansible——inventory 主机清单

Ansible——inventory 主机清单Ansible——inventory 主机清单inventory简介ansible配置文件的优先级ansible命令常用参数主机清单文件hosts&#xff08;/etc/ansible/hosts&#xff09;通过列表的方式标识主机范围指定主机端口使用主机名表示主机范围inventory 中的变量主机变…

JS 数组方法 every 和 some 的区别

1. 前言 2. every 和 some 相同点 3. every 和 some 的区别 4. every 和 some 总结 1. 前言 JS 数组方法 every 和 some 的区别 &#xff1f; 这是某位前端玩家遇到的面试题 特定场景合理的使用 JS 方法&#xff0c;不仅可以减少我们的代码量&#xff0c;还能更轻松的阅读…

宇航服,真正的“科技”与“狠活”!

千百年的探索仰望和摘星的遐想&#xff0c;已照进现实&#xff0c;浩瀚的天宫&#xff0c;我们亦可置身其中。 北京时间2022年12月4日20时09分&#xff0c;神舟十四号载人飞船返回舱在东风着陆场成功着陆&#xff0c;标志着太空出差183天的宇航员正式回家&#xff01;据悉&…