Java 入门指南:Java 泛型(generics)

2024-08-25 18:36

本文主要是介绍Java 入门指南:Java 泛型(generics),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Java 泛型概要

Java 泛型(generics) 是 JDK 5 中引入的一个新特性。泛型的本质是参数化类型,也就是所操作的数据类型被指定为一个参数(可以称之为类型形参,然后在使用/调用时传入具体的类型。)

使用 Java 泛型的概念,我们可以写一个泛型方法来对一个对象数组排序。然后,调用该泛型方法来对整型数组、浮点数数组、字符串数组等进行排序。

Java中的泛型,只在编译阶段有效

在编译之后程序会采取去泛型化的措施。在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦出,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。也就是说,泛型信息不会进入到运行时阶段

因此,泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型

泛型有三种使用方式:泛型类泛型接口泛型方法

泛型的优势

  1. 类型安全:泛型可以在编译时检查类型错误,减少运行时异常。

  2. 消除强制类型转换:使用泛型后,可以自动获得正确的类型,无需进行显式的类型转换,提高了代码的可读性和安全性。

  3. 提高代码重用性:泛型使得可以编写更加通用的代码,如泛型集合可以存储任何类型的对象,而无需为每种类型编写特定的集合类。

  4. 性能优化:通过消除运行时的类型检查和转换,泛型可以提高程序的性能。

泛型类

泛型类型用于类的定义中,被称为泛型类。通过泛型可以完成对一组类的操作对外开放相同的接口。最典型的就是各种容器类,如:ListSetMap

泛型类的声明

class ClassName<T>{// 类成员的和方法定义private T item;public void setItem(T item){this.item = item;}public T getItem(){return item;}// 此方法有错误,因为类的声明中未声明泛型E,在使用E做形参和返回值类型时,编译器会无法识别。public E setKey(E key){this.key = key;}
}
  • T 是类型参数,它代表着一个占位符,表示在实例化泛型类时将传入的具体类型

  • 在泛型类的声明中,T可以被替换为任何合法的Java标识符,通常使用如下常见的命名约定:

    • T - 代表任意类型
    • E - 代表元素(通常在集合类中使用)
    • K - 代表键(通常在关联数据结构中使用)
    • V - 代表值(通常在关联数据结构中使用)
  • 类型参数声明部分可以包含一个或多个类型参数,参数间用逗号隔开。

  • 泛型的类型参数只能是类类型StringDoubleInteger),不能是简单类型(intfloatchardouble

  • 不能对确切的泛型类型使用 instanceof 操作。如下面的操作是非法的,编译时会出错:

if(ex_num instanceof Generic<Number>){...}

实例化是否需要传入实参

在使用泛型的时候如果传入泛型实参,则会根据传入的泛型实参做相应的限制,此时泛型才会起到本应起到的限制作用。

如果不传入泛型类型实参的话,在泛型类中使用泛型的方法或成员变量定义的类型可以为任何的类型

泛型接口

泛型接口与泛型类的定义及使用基本相同。泛型接口常被用在各种类的生产器中,它允许在接口的方法、成员变量和常量等位置使用一种或多种类型参数来增加灵活性和重用性。

public interface InterFaceName<T>{public T next();
}

未传入泛型实参

未传入泛型实参时,与泛型类的定义相同。在声明类的时候,需将接口泛型的声明也一起加到类中

如果不声明泛型,如:

class FruitGenerator implements Generator<T>

编译器会报错:“Unknown class

public interface Pair<K,V>{K getKey();V getValue();
}public class OrderPair <K,V> implements Pair <K,V>{private K key;private V value;public OrderedPair(K key, V value) {this.key = key;this.value = value;}@Overridepublic K getKey() {return key;}@Overridepublic V getValue() {return value;}
}

传入泛型实参时

定义一个类(生产器)实现这个接口,虽然我们只创建了一个泛型接口
但是我们可以为 T 传入无数个实参,形成无数种类型的 Implement 接口。

在实现类实现泛型接口时,如已将泛型类型传入实参类型,则所有使用泛型的地方都要替换成传入的实参类型,所有的 T 都将换成传入的实参

public class OrdersPair implements Pair <Integer,String>{private Integer key;private String value;public OrderedPair(Integer key, String value) {this.key = key;this.value = value;}@Overridepublic Integer getKey() {return key;}@Overridepublic String getValue() {return value;}
}

类型擦除

虚拟机是没有泛型的,把泛型类的字节码进行反编译,用反编译工具(如 jad)将 class 文件反编译后,类型变量 <E> 消失了,取而代之的是 Object。

如果泛型类使用了限定符 extends,例如 <E extends TestClass>
类型变量 <E extends TestClass> 不见了,E 被替换成了 TestClass

Java 虚拟机会将泛型的类型变量擦除,并替换为限定类型(没有限定的话,为 Object

类型擦除会遇到的问题

![[Pasted image 20231103230114.png]]

在浅层的意识上,我们会想当然地认为 Arraylist<String> listArraylist<Date> list 是两种不同的类型,因为 StringDate 是不同的类。

但由于类型擦除的原因,以上代码是不会通过编译的——编译器会提示一个错误(这正是类型擦除引发的那些“问题”)
![[Pasted image 20231103230142.png]]

这两个方法的参数类型在擦除后是相同的。也就是说,method(Arraylist<String> list)method(Arraylist<Date> list) 是同一种参数类型的方法,不能同时存在。类型变量 StringDate 在擦除后会自动消失,method 方法的实际参数是 Arraylist list

泛型通配符

若传入的实参类型具有父子类关系,如 NumberInteger 类,能否在泛型中视为具有父子关系的泛型关系?

即在 class<Number>"作为形参的方法中,能否传入 class<Interger>的实例? 由于类型擦除的原因,编译器会报错

同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的

需要一个在逻辑上可以表示同时class<Integer>class<Number>父类的引用类型。由此类型通配符应运而生

在Java中,泛型通配符(Wildcard) 是一种特殊的类型参数,用于表示不确定的类型。通配符可以被用作泛型类、泛型接口和泛型方法中的类型参数,以增加代码的灵活性和重用性。

例如 List<?> 表示一个可以存储任何类型对象的 List,但是不能对其中的元素进行添加操作。通配符可以用来解决类型不确定的情况,例如在方法参数或返回值中使用。

泛型通配符有三种形式

通配符

问号 ? :表示未知类型,可以匹配任何类型。此处的“ ? ”是泛型实参,而不是泛型形参!!!即 NumberIntegerString… 都是同一种实际的类型

上限通配符

上限通配符 ? extends T表示类型参数是 T 或 T 的子类型。类型实参只准传入某种类型及其子类的对象。

public void processList(List<? extends Number> list) {for (Number element : list) {// 处理元素}
}

List<? extends Number> 表示接受的元素类型是 Number 或 Number 的子类型的List

下限通配符

下限通配符(Lower Bounded Wildcards) 用 super 关键字来声明,其语法形式为 <? super T> ,其中 T 表示类型参数。它表示的是该类型参数必须是某个指定类的超类(包括该类本身)。

当我们需要往一个泛型集合中添加元素时,如果使用的是上限通配符,集合中的元素类型可能会被限制,从而无法添加某些类型的元素。但是,如果我们使用下限通配符,可以将指定类型的子类型添加到集合中,保证了元素的完整性。

假设有一个类 Animal,以及两个子类 Dog 和 Cat。现在我们有一个 List<? super Dog> 集合,它的类型参数必须是 Dog 或其父类类型。可以向该集合中添加 Dog 类型的元素,也可以添加它的子类。但是,不能向其中添加 Cat 类型的元素,因为 Cat 不是 Dog 的子类。

虽然使用下限通配符可以添加某些子类型元素,但是在读取元素时,我们只能确保其是 Object 类型的,无法确保其是指定类型或其父类型。因此,在读取元素时需要进行类型转换

泛型方法

泛型方法是一种具有泛型类型参数的方法。通过在方法的声明中使用类型参数,可以在方法内部使用不特定的类型

![[Pasted image 20231103224842.png]]

方法返回类型和方法参数类型至少需要一个

public <T> returnType methodName(T parameter){// method code blockfor(T element : parameter){}
}
  • public 与 返回值中间 <T> 非常重要,表示类型参数的声明,可以是一个或多个类型参数。用于声明此方法为泛型方法。

  • 只有声明了 <T> 的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。

  • <T> 表明该方法将使用泛型类型 T,此时才可以在方法内部使用泛型类型T

  • 与泛型类的定义一样,此处 T 可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。

    • T - 代表任意类型
    • E - 代表元素(通常在集合类中使用)
    • K - 代表键(通常在关联数据结构中使用)
    • V - 代表值(通常在关联数据结构中使用)

泛型类,是在实例化类的时候指明泛型的具体类型;而泛型方法,是在调用方法的时候指明泛型的具体类型

如果使用泛型方法将整个类泛型化,那么就应该使用泛型方法。

泛型方法中添加上下边界限制的时候,必须在权限声明与返回值之间的<T>上添加上下边界:

public <T extends Number> T showKeyName (Generic<T> container)

类中的泛型方法

public class ClassName<T>{public void test1(T t){System.out.println(t.toString);}public <E> void test2(E e){}public <T> void test3(T t){}
}
  • test2 中的 泛型 E 可以为任意类型。可以类型与 T 相同,也可以不同。由于泛型方法在声明的时候会声明泛型 <E>,即使在泛型类中并未声明泛型,编译器也能够正确识别泛型方法中识别的泛型。

  • test3 中的 泛型 T 是一个全新的类型,可以与类中使用的泛型 T 不同

泛型方法与可变参数

可变参数是一种特殊的参数形式,它允许方法接受可变数量的参数

使用可变参数和泛型方法的组合,可以更方便地处理具有不确定数量和类型的参数列表,并在方法内部使用泛型类型参数,从而实现更灵活和通用的代码。

class ClassName{public <T> void test(T...args){for(T arg : args){System.out.println(arg);}}
}ClassName cn = new ClassName();
cn.test("111",222,"aaaa","2323.4",55.55)
cn.test(true,false);

静态方法与泛型

在类中的静态方法使用泛型:静态方法无法访问类上定义的泛型

如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。即如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法

泛型数组

java中是 不能创建一个确切的泛型类型的数组 的。因为Java中的数组是具体类型的集合,而泛型是在编译时进行类型擦除的。由于类型擦除的存在,无法在运行时创建具体类型的泛型数组。

可以创建一个泛型类型的数组引用,然后将其转换为指定类型的数组。这个转换被称为类型强制转换或类型安全的转换。

// 创建泛型类型的数组引用
Object[] genericArray = new Object[5];// 转换为指定类型的数组
String[] stringArray = (String[]) genericArray;

也可以使用通配符创建泛型数组:

List<?>[] lists = new ArrayList<?>[10];
// 转换为指定类型的数组
List<String>[] stringArray = (List<String>[]) genericArray;

最后取出数据是要做显式的类型转换的

或者直接指定类型:

List<String> lists = new ArrayList<?>[10];

在类型强制转换时,应确保转换是安全的,即转换后的类型与实际存储在数组中的对象类型相符。如果转换不是安全的,会在运行时抛出ClassCastException 异常。

尽管可以使用通配符创建泛型数组引用并进行类型转换,但在实际编程中,最好使用集合类型(如ArrayList)来代替泛型数组,以获得更好的类型安全和灵活性。

总结

泛型是Java中一种强大的特性,它允许我们编写类型安全且可重用的代码。通过使用泛型,我们可以创建灵活的组件,这些组件可以处理不同的数据类型,同时保持代码的简洁性和可读性。了解泛型的基本概念和高级用法对于编写高质量的Java程序至关重要。

这篇关于Java 入门指南:Java 泛型(generics)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1106331

相关文章

Java实现将byte[]转换为File对象

《Java实现将byte[]转换为File对象》这篇文章将通过一个简单的例子为大家演示Java如何实现byte[]转换为File对象,并将其上传到外部服务器,感兴趣的小伙伴可以跟随小编一起学习一下... 目录前言1. 问题背景2. 环境准备3. 实现步骤3.1 从 URL 获取图片字节数据3.2 将字节数组

Java捕获ThreadPoolExecutor内部线程异常的四种方法

《Java捕获ThreadPoolExecutor内部线程异常的四种方法》这篇文章主要为大家详细介绍了Java捕获ThreadPoolExecutor内部线程异常的四种方法,文中的示例代码讲解详细,感... 目录方案 1方案 2方案 3方案 4结论方案 1使用 execute + try-catch 记录

SpringBoot接收JSON类型的参数方式

《SpringBoot接收JSON类型的参数方式》:本文主要介绍SpringBoot接收JSON类型的参数方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、jsON二、代码准备三、Apifox操作总结一、JSON在学习前端技术时,我们有讲到过JSON,而在

Python FastAPI入门安装使用

《PythonFastAPI入门安装使用》FastAPI是一个现代、快速的PythonWeb框架,用于构建API,它基于Python3.6+的类型提示特性,使得代码更加简洁且易于绶护,这篇文章主要介... 目录第一节:FastAPI入门一、FastAPI框架介绍什么是ASGI服务(WSGI)二、FastAP

Spring-AOP-ProceedingJoinPoint的使用详解

《Spring-AOP-ProceedingJoinPoint的使用详解》:本文主要介绍Spring-AOP-ProceedingJoinPoint的使用方式,具有很好的参考价值,希望对大家有所帮... 目录ProceedingJoinPoijsnt简介获取环绕通知方法的相关信息1.proceed()2.g

Spring Security注解方式权限控制过程

《SpringSecurity注解方式权限控制过程》:本文主要介绍SpringSecurity注解方式权限控制过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、摘要二、实现步骤2.1 在配置类中添加权限注解的支持2.2 创建Controller类2.3 Us

Spring MVC跨域问题及解决

《SpringMVC跨域问题及解决》:本文主要介绍SpringMVC跨域问题及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录跨域问题不同的域同源策略解决方法1.CORS2.jsONP3.局部解决方案4.全局解决方法总结跨域问题不同的域协议、域名、端口

SpringBoot项目启动错误:找不到或无法加载主类的几种解决方法

《SpringBoot项目启动错误:找不到或无法加载主类的几种解决方法》本文主要介绍了SpringBoot项目启动错误:找不到或无法加载主类的几种解决方法,具有一定的参考价值,感兴趣的可以了解一下... 目录方法1:更改IDE配置方法2:在Eclipse中清理项目方法3:使用Maven命令行在开发Sprin

JAVA SE包装类和泛型详细介绍及说明方法

《JAVASE包装类和泛型详细介绍及说明方法》:本文主要介绍JAVASE包装类和泛型的相关资料,包括基本数据类型与包装类的对应关系,以及装箱和拆箱的概念,并重点讲解了自动装箱和自动拆箱的机制,文... 目录1. 包装类1.1 基本数据类型和对应的包装类1.2 装箱和拆箱1.3 自动装箱和自动拆箱2. 泛型2

SpringBoot操作MaxComputer方式(保姆级教程)

《SpringBoot操作MaxComputer方式(保姆级教程)》:本文主要介绍SpringBoot操作MaxComputer方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的... 目录引言uqNqjoe一、引入依赖二、配置文件 application.properties(信息用自己