Java泛型是什么?

简介: 本文回顾了作者五年的工作经历,强调了自我学习的重要性,并介绍了Java泛型的基础知识,包括泛型的概念、泛型集合、泛型方法、泛型接口、泛型类及类型擦除等内容,旨在帮助读者理解泛型机制及其在编程中的应用。

1、 文章背景

工作已有五年之久,回望过去,没有在一线城市快节奏下学习成长,只能自己不断在工作中学习进步,最近一直想写写属于自己的文章,记录学习的内容和知识点,当做一次成长。

2、 泛型的概述

摘要:Java泛型是JDK5中引入的一个新特性,其本质是参数化类型‌。

什么是泛型

泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参列表,普通方法的形参列表中,每个形参的数据类型是确定的,而变量是一个参数。在调用普通方法时需要传入对应形参数据类型的变量(实参),若传入的实参与形参定义的数据类型不匹配,则会报错。

Java 1.5 之前没有泛型,通常需要使用强制类型转换的方式将一种数据类型转换为另一种数据类型,这种转换要求开发者对实际参数的类型具有可预知性。对于强制类型转换错误的情况,编译器可能不会提示错误,但是在运行时会出现异常,这是一个安全隐患。

为了解决这一隐患,从 Java 1.5 开始提供了泛型。泛型可以在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高了代码的重用率。

3、泛型集合

泛型本质上是提供类型的“类型参数”,也就是参数化类型。我们可以为类、接口或方法指定一个参数类型,通过这个操作限制数据操作的类型,从而保证类型转换的绝对安全。

代码Map<Integer,Book> books=new HashMap<Integer,Book>();创建了一个键类型为 Integer、值类型为 Book 的泛型集合,即指明了该 Map 集合中存放的键必须是 Integer 类型、值必须为 Book 类型,否则编译出错。在获取 Map 集合中的元素时,不需要将books.get(id);获取的值强制转换为 Book 类型,程序会隐式转换。在创建 List 集合时,同样使用了泛型,因此在获取集合中的元素时也不需要将bookList.get(i)代码强制转换为 Book 类型,程序会隐式转换。

4、泛型方法

泛型方法使得该方法能够独立于类而产生变化。如果使用泛型方法可以取代类泛型化,那么就应该只使用泛型方法。另外,对一个 static 的方法而言,无法访问泛型类的类型参数。因此,如果 static 方法需要使用泛型能力,就必须使其成为泛型方法。

  • 4.1 方法中形参类型不确定时
  • 使用类后面定义的泛型。
  • 在方法上定义自己的泛型。

  • 4.2 格式

ini

代码解读

复制代码

[访问权限修饰符][static][final]<类型参数列表> 返回值类型 方法名([形式参数列表]){};

  • 4.3 多个类型参数
  • 泛型方法可以定义多个类型参数。

java

代码解读

复制代码

public static <T, U> void printPair(T first, U second) {
    System.out.println(first + ", " + second);
}
  • 4.4 参数类型约束
  • 可以通过extends关键字对类型参数进行约束,要求类型参数必须是某个类(或接口)的子类(或实现类)。

java

代码解读

复制代码

public static <T extends Number> void printNumber(T num) {
    System.out.println(num);
}

5、泛型接口

泛型接口是Java泛型机制的一部分,它允许在接口定义中使用类型参数,使得实现该接口的类或方法在遵循接口规范的同时,可以处理不同的数据类型。

  • 5.1 格式
  • 泛型接口的定义与泛型类类似,在接口名后面的尖括号<>中指定类型参数。这些类型参数可以在接口的方法签名、返回类型或参数列表中使用。

java

代码解读

复制代码

public interface GenericInterface<T> {

    void someMethod(T t);
    
    T anotherMethod();
}

  • 5.2 实现泛型接口
  • 在实现泛型接口时,可以选择指定类型参数的具体类型,或者将实现类也定义为泛型类,以便推迟类型参数的指定。

java

代码解读

复制代码

// 指定具体类型
public class ConcreteClass implements GenericInterface<String> {
    @Override
    public void someMethod(String s) {
        // 实现方法
    }

    @Override
    public String anotherMethod() {
        // 返回String类型的值
        return "Hello";
    }
}
// 推迟类型参数的指定
public class GenericClass<T> implements GenericInterface<T> {
    @Override
    public void someMethod(T t) {
        // 实现方法
    }

    @Override
    public T anotherMethod() {
        // 返回T类型的值
        return null; // 或者其他T类型的实例
    }
}

  • 5.3 多个类型参数接口
  • 泛型接口可以定义多个类型参数。

java

代码解读

复制代码

public interface MultipleGenericInterface<T, U> {
    T getFirst();
    U getSecond();
}

  • 5.4 类型参数的约束
  • 在接口中定义泛型,并在实现时指定具体类型。

java

代码解读

复制代码

public interface MultipleGenericInterface<T extends Number> {

    // 省略其他方法
}

6、泛型类

泛型类是Java泛型机制的一个重要组成部分,它允许在类定义时使用类型参数,以便类可以处理不同的数据类型。

  • 6.1 格式
  • 泛型类的定义与普通类相似,但在类名后面的尖括号<>中指定了一个或多个类型参数。这些类型参数可以在类的字段、方法参数、返回类型以及方法体内部使用。

java

代码解读

复制代码

public class GenericClass<T> {
    private T value;

    public GenericClass(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }
}

  • 6.2 实例化泛型类
  • 在创建泛型类的实例时,需要指定类型参数的具体类型。

java

代码解读

复制代码

GenericClass<String> stringInstance = new GenericClass<>("Hello");
GenericClass<Integer> integerInstance = new GenericClass<>(123);
  • 6.3 多个类型参数
  • 泛型类可以定义多个类型参数。

java

代码解读

复制代码

public class MultipleGenericClass<T, U> {
    private T first;
    private U second;

    public MultipleGenericClass(T first, U second) {
        this.first = first;
        this.second = second;
    }

    // 省略getter和setter方法
}

  • 6.4 类型参数的约束
  • 与泛型接口类似,泛型类中的类型参数也可以通过extends(对于类)或implements(对于接口)关键字进行约束。例如,要求类型参数继承自某个类或实现某个接口:

java

代码解读

复制代码

public class ConstrainedGenericClass<T extends Number> {
    private T value;

    public ConstrainedGenericClass(T value) {
        this.value = value;
    }

    // 省略其他方法
}

7、类型擦除

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

换而言之,泛型信息只存在于代码编译阶段,在代码编译结束后,与泛型相关的信息会被擦除掉,专业术语叫做类型擦除。也就是说,成功编译过后的 class 文件中不包含任何泛型信息,泛型信息不会进入到运行时阶段。

8、泛型标识

  • 尖括号 <> 中的 泛型标识被称作是类型参数,用于指代任何数据类型。
  • 泛型标识是任意设置的(如果你想可以设置为 Hello都行),Java 常见的泛型标识以及其代表含义如下:

java

代码解读

复制代码

  T :代表一般的任何类。
  E :代表 Element 元素的意思,或者 Exception 异常的意思。
  K :代表 Key 的意思。
  V :代表 Value 的意思,通常与 K 一起配合使用。
  S :代表 Subtype 的意思,文章后面部分会讲解示意。

9、泛型通配符

在Java的泛型机制中,通配符(wildcard)是一种特殊的类型参数,它用于表示未知的类型。泛型通配符主要用于泛型方法的定义中,以及作为泛型类型和泛型方法之间的桥梁,增加代码的灵活性。以下是关于泛型通配符的关键点:

  • 问号(?)
    问号(?)是泛型通配符的基本符号,它表示未知的类型。在泛型方法中,可以使用问号来代替具体的类型参数。
  • 上限通配符(? extends T)
    上限通配符表示未知的类型是T类型或T类型的子类。它用于限制泛型类型的上界,确保泛型方法或泛型集合中的元素是T类型或其子类类型。
    例如,List<? extends Number>表示一个包含Number或其子类(如IntegerDouble等)的列表。
  • 下限通配符(? super T)
    下限通配符表示未知的类型是T类型或T类型的父类。它用于限制泛型类型的下界,允许泛型方法或泛型集合添加T类型或其子类类型的元素,但读取时只能保证是T类型的父类。
    例如,List<? super Integer>表示一个可以包含Integer或其父类(如NumberObject等)的列表,但可以安全地向其中添加Integer类型的元素。


转载来源:https://juejin.cn/post/7428997298196447270

相关文章
|
12月前
|
C语言
C语言形参和实参的区别
在C语言中,形参(形式参数)与实参(实际参数)有着明确的角色区分。形参是在函数定义中声明的参数,用于接收调用函数时传入的数据;实参则是调用函数时传递的具体值或变量。简言之,实参提供数据,形参接收并处理这些数据。
|
3月前
|
人工智能 JavaScript 前端开发
js删除对象属性
本文介绍了JavaScript中删除对象属性及数组元素的多种方法,包括设置属性为undefined、使用delete操作符、对象解构、Reflect.deleteProperty方法以及数组的delete和splice操作。每种方法均有示例代码及关键特性说明,适用于不同场景下的属性或元素删除需求,帮助开发者更高效地处理对象和数组的操作。
117 0
js删除对象属性
|
2月前
|
人工智能 JavaScript 前端开发
理解 JavaScript 中的节流和防抖:实现 `throttle` 和 `debounce` 函数
节流(throttle)是指在一定时间间隔内只执行一次函数,常用于控制高频事件触发频率,如滚动、窗口调整等。本文介绍其实现原理与代码示例。
|
缓存 安全 Java
Spring Get请求 与post请求
本文详细介绍了Spring框架中GET请求和POST请求的区别及应用场景。GET请求用于从服务器获取资源,参数附在URL末尾,适合查看非敏感信息;POST请求用于向服务器提交数据,参数在请求体中传输,适合处理敏感信息。Spring通过`@GetMapping`和`@PostMapping`注解分别处理这两种请求。此外,文章还提供了示例代码,展示了如何在Spring中实现这两种请求的处理。最后,文章总结了推荐使用POST请求的原因,包括更高的安全性、更大的数据传输量、更好的幂等性及灵活性。
440 1
Spring Get请求 与post请求
|
3月前
|
存储 人工智能 Java
java之通过Http下载文件
本文介绍了使用Java实现通过文件链接下载文件到本地的方法,主要涉及URL、HttpURLConnection及输入输出流的操作。
178 0
|
6月前
|
存储 缓存 关系型数据库
MySQL为什么需要主键
本文介绍了MySQL中主键的重要性及最佳实践。主键用于唯一标识表中的每一行,其值必须唯一且不允许为空。主键有助于简化更新和删除操作,避免影响无关行。推荐使用与业务无关的自增ID作为Innodb表的主键,以优化存储结构、减少碎片并提高性能。此外,文章还提到不更新、不重用主键值以及避免使用可能变更的字段(如邮箱)作为主键的良好习惯。最后强调了紧凑索引结构对查询效率的关键作用。
185 0
MySQL为什么需要主键
|
11月前
|
XML JSON Java
Jackson反序列化不可变类
Jackson 默认的反序列化策略需要无参构造器和字段 setter 函数。对于不可变类(如 `ImmutableUser`),可以通过以下三种方式解决: 1. **使用 Jackson 注解**:在全参构造器上使用 `@JsonCreator` 和 `@JsonProperty` 注解。 2. **使用 jackson-module-parameter-names**:引入依赖并注册 `ParameterNamesModule` 模块。 3. **使用 Mixins 机制**:创建一个 Mixin 类,使用 `@JsonCreator` 和 `@JsonProperty` 注解
125 3
Jackson反序列化不可变类
|
11月前
|
前端开发 安全 Java
springboot解决跨域问题
跨域问题指前端调用与后端接口不在同一域名或端口时产生的安全限制。本文介绍两种在Spring Boot中解决跨域问题的方法:一是通过配置CorsFilter,二是实现WebMvcConfigurer接口。配置完成后重启项目即可生效。作者:博笙困了。来源:稀土掘金。
288 6
|
11月前
|
算法 大数据 Go
Go文件操作:掌握Go的文件读写与操作技巧
本文介绍了Go语言的文件操作功能,包括文件的打开、读写和关闭。Go语言通过`os`和`io`包提供了丰富的文件操作接口,使开发者能够轻松实现文件的读写和管理。文章详细讲解了核心概念、具体操作步骤和代码示例,并探讨了实际应用场景和未来发展趋势。
164 4
|
算法 安全 测试技术
golang 栈数据结构的实现和应用
本文详细介绍了“栈”这一数据结构的特点,并用Golang实现栈。栈是一种FILO(First In Last Out,即先进后出或后进先出)的数据结构。文章展示了如何用slice和链表来实现栈,并通过golang benchmark测试了二者的性能差异。此外,还提供了几个使用栈结构解决的实际算法问题示例,如有效的括号匹配等。
237 1
golang 栈数据结构的实现和应用