《Java 泛型的作用与常见用法详解》

news2025/4/19 16:01:04

大家好呀!👋 今天我们要聊的是Java中一个超级重要但又让很多初学者头疼的概念——泛型(Generics)。带你彻底搞懂它!💪 准备好你的小本本,我们开始啦~📝

一、为什么需要泛型?🤔

想象一下,你有一个神奇的盒子📦,这个盒子可以放任何东西:苹果🍎、书本📚、甚至小猫🐱。听起来很方便对吧?但是在Java中,这样的"万能盒子"会带来大麻烦!

// 没有泛型的"万能"List
List myList = new ArrayList();
myList.add("字符串");  // 放字符串
myList.add(123);      // 放数字
myList.add(new Date());// 放日期

// 取出来时...
String str = (String) myList.get(1); // 运行时出错!其实是数字

看到问题了吗?😱 我们不知道盒子里到底装了什么,取出来时要强制转换,一不小心就会出错!

泛型就是来解决这个问题的!它给盒子贴上了标签🏷️:

List stringList = new ArrayList<>(); // 这个盒子只能放字符串
stringList.add("hello");
// stringList.add(123); // 编译时就报错!安全!

二、泛型基础语法速成班🎓

1. 泛型类(Generic Class)

让我们自己造一个带标签的盒子吧!

// T是类型参数,就像盒子的标签
public class MagicBox {
    private T content;
    
    public void put(T item) {
        this.content = item;
    }
    
    public T get() {
        return content;
    }
}

// 使用示例
MagicBox stringBox = new MagicBox<>();
stringBox.put("秘密纸条");
// stringBox.put(100); // 编译错误!
String secret = stringBox.get(); // 不需要强制转换

2. 泛型方法(Generic Method)

单个方法也可以有自己的类型标签哦!

public class Tool {
    // 泛型方法 - 在返回类型前声明类型参数
    public static  T getMiddle(T... items) {
        return items[items.length / 2];
    }
}

// 使用示例
String middleStr = Tool.getMiddle("苹果", "香蕉", "橙子");
Integer middleNum = Tool.getMiddle(1, 2, 3); // 可以省略类型参数

3. 泛型接口(Generic Interface)

接口也可以带标签!

public interface Storage {
    void store(T item);
    T retrieve();
}

// 实现类需要指定具体类型
public class FileStorage implements Storage {
    @Override
    public void store(String item) { /*...*/ }
    
    @Override
    public String retrieve() { /*...*/ }
}

三、泛型高级用法🚀

1. 类型通配符(Wildcards)

有时候我们不知道盒子里具体是什么,但知道大概范围:

// 未知类型的盒子
public void peekBox(MagicBox box) {
    System.out.println("盒子里有东西,但我不知道是啥");
}

// 必须是Number或其子类的盒子
public void sumNumbers(MagicBox box) {
    Number num = box.get();
    System.out.println(num.doubleValue() + 10);
}

// 必须是Integer或其父类的盒子
public void setInteger(MagicBox box) {
    box.put(100);
}

记忆口诀📝:

  • ``:随便啥都行
  • ``:T或T的子类(上界)
  • ``:T或T的父类(下界)

2. 泛型擦除(Type Erasure)

Java泛型是编译期的魔法🔮,运行时类型信息会被擦除:

List stringList = new ArrayList<>();
List intList = new ArrayList<>();

// 运行时都是ArrayList,类型参数被擦除了
System.out.println(stringList.getClass() == intList.getClass()); // true

这也是为什么我们不能这样写:

// 编译错误!
public class MyClass {
    private T instance = new T(); // 不知道T有没有无参构造
    private T[] array = new T[10]; // 数组必须知道具体类型
}

3. 泛型与数组的恩怨情仇💔

泛型数组是个特殊的存在:

// 这样不行!
List[] arrayOfLists = new List[10]; // 编译错误

// 但这样可以(会有警告)
List[] arrayOfLists = (List[]) new List[10];

为什么这么设计?因为数组在运行时需要知道具体类型来保证类型安全,而泛型会被擦除,两者机制冲突了。

四、实际开发中的泛型应用场景🏗️

1. 集合框架(Collections)

这是泛型最常用的地方:

// 传统方式(不建议)
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0); // 需要强制转换

// 泛型方式(推荐)
List list = new ArrayList<>();
list.add("hello");
String s = list.get(0); // 自动转换

2. 函数式接口(Functional Interfaces)

配合Lambda表达式使用:

// 定义泛型函数式接口
@FunctionalInterface
interface Converter {
    R convert(T from);
}

// 使用示例
Converter converter = Integer::valueOf;
Integer num = converter.convert("123");

3. 构建工具类

比如一个通用的Pair类:

public class Pair {
    private final T first;
    private final U second;
    
    public Pair(T first, U second) {
        this.first = first;
        this.second = second;
    }
    
    // getters...
}

// 使用示例
Pair nameAndAge = new Pair<>("张三", 25);

4. 反射中的泛型

获取泛型类型信息:

public class GenericClass {
    public List getStringList() {
        return new ArrayList<>();
    }
}

// 获取方法的泛型返回类型
Method method = GenericClass.class.getMethod("getStringList");
Type returnType = method.getGenericReturnType();

if (returnType instanceof ParameterizedType) {
    ParameterizedType type = (ParameterizedType) returnType;
    Type[] typeArguments = type.getActualTypeArguments();
    for (Type typeArg : typeArguments) {
        System.out.println(typeArg); // 输出: class java.lang.String
    }
}

五、常见问题解答❓

Q1: 为什么不能直接创建泛型数组?

A: 因为数组需要在运行时知道确切类型来保证类型安全,而泛型在运行时会被擦除,导致可能的类型不安全。

Q2: ListList 有什么区别?

A:

  • List 是明确存储Object类型元素的列表
  • List 是存储未知类型元素的列表,更灵活但限制更多
List objectList = new ArrayList<>();
objectList.add("字符串"); // 可以
objectList.add(123);    // 可以

List wildcardList = new ArrayList();
// wildcardList.add("hello"); // 编译错误!不知道具体类型

Q3: 泛型方法中的``和返回类型前的T有什么关系?

A: 方法声明中的``是类型参数声明,返回类型前的T是使用这个类型参数。它们必须匹配:

// 正确:声明T并使用T
public static  T method1(T param) { ... }

// 错误:声明T却使用U
public static  U method2(T param) { ... } // 编译错误!

六、最佳实践与陷阱规避🚧

1. 命名约定

类型参数通常用单个大写字母:

  • E - Element (集合中使用)
  • K - Key (键)
  • V - Value (值)
  • T - Type (类型)
  • S,U,V - 第二、第三、第四类型

2. 避免原生类型

// 不好!
List list = new ArrayList(); // 原生类型

// 好!
List list = new ArrayList<>(); // 参数化类型

3. 谨慎使用通配符

// 过度使用通配符会让代码难以理解
public void process(List> list) { ... }

// 适当拆分更清晰
public > void process(List list) { ... }

4. 类型安全的异构容器

有时候我们需要一个容器能存储多种不同类型:

public class TypeSafeContainer {
    private Map, Object> map = new HashMap<>();
    
    public  void put(Class type, T instance) {
        map.put(Objects.requireNonNull(type), type.cast(instance));
    }
    
    public  T get(Class type) {
        return type.cast(map.get(type));
    }
}

// 使用示例
TypeSafeContainer container = new TypeSafeContainer();
container.put(String.class, "字符串");
container.put(Integer.class, 123);

String s = container.get(String.class);
Integer i = container.get(Integer.class);

七、Java 8/9/10/11中的泛型改进🌈

1. 钻石操作符改进 (Java 7)

// Java 7之前
List list = new ArrayList();

// Java 7+ 可以省略右边的类型参数
List list = new ArrayList<>();

2. 局部变量类型推断 (Java 10)

// Java 10+
var list = new ArrayList(); // 自动推断为ArrayList

3. 匿名类的钻石操作符 (Java 9)

// Java 9+ 匿名类也可以使用钻石操作符
List list = new ArrayList<>() {
    // 匿名类实现
};

八、终极挑战:你能回答这些问题吗?🧠

  1. 下面代码有什么问题?

    public class Box {
        private T[] items = new T[10]; // 哪里错了?
    }
    
  2. 下面两个方法签名有什么区别?

    void printList(List list)
    void printList(List list)
    
  3. 如何编写一个方法,接受任何List,但只能添加Number及其子类?

(答案在文末👇)

九、总结与思维导图🎯

让我们用一张图总结泛型的核心要点:

Java泛型
├── 为什么需要?
│   ├── 类型安全
│   └── 消除强制转换
├── 基础语法
│   ├── 泛型类 class Box
│   ├── 泛型方法  T method(T t)
│   └── 泛型接口 interface Store
├── 高级特性
│   ├── 通配符 ?
│   │   ├── 上界 ? extends T
│   │   └── 下界 ? super T
│   └── 类型擦除
└── 应用场景
    ├── 集合框架
    ├── 工具类
    └── 函数式编程

十、实战练习💻

练习1:实现通用缓存类

// 实现一个通用缓存类,可以存储任意类型,但每种类型只能存储一个实例
public class TypeCache {
    // 你的代码...
}

// 使用示例
TypeCache cache = new TypeCache();
cache.put(String.class, "hello");
String value = cache.get(String.class);

练习2:编写泛型工具方法

// 编写一个方法,将任意类型数组转换为ArrayList
public static  ArrayList arrayToList(T[] array) {
    // 你的代码...
}

终极挑战答案:

  1. 不能直接创建泛型数组,应该使用Object数组然后强制转换:private T[] items = (T[]) new Object[10];
  2. 第一个只能接受List,第二个可以接受任何List
void addNumbers(List list) {
    list.add(Integer.valueOf(1));
    list.add(Double.valueOf(2.0));
}

好啦!这篇超详细的Java泛型指南就到这里啦!👏 希望你现在对泛型有了全面的理解。如果有任何问题,欢迎在评论区留言讨论哦~💬 记得点赞收藏,下次见!😘

推荐阅读文章

  • 由 Spring 静态注入引发的一个线上T0级别事故(真的以后得避坑)

  • 如何理解 HTTP 是无状态的,以及它与 Cookie 和 Session 之间的联系

  • HTTP、HTTPS、Cookie 和 Session 之间的关系

  • 什么是 Cookie?简单介绍与使用方法

  • 什么是 Session?如何应用?

  • 使用 Spring 框架构建 MVC 应用程序:初学者教程

  • 有缺陷的 Java 代码:Java 开发人员最常犯的 10 大错误

  • 如何理解应用 Java 多线程与并发编程?

  • 把握Java泛型的艺术:协变、逆变与不可变性一网打尽

  • Java Spring 中常用的 @PostConstruct 注解使用总结

  • 如何理解线程安全这个概念?

  • 理解 Java 桥接方法

  • Spring 整合嵌入式 Tomcat 容器

  • Tomcat 如何加载 SpringMVC 组件

  • “在什么情况下类需要实现 Serializable,什么情况下又不需要(一)?”

  • “避免序列化灾难:掌握实现 Serializable 的真相!(二)”

  • 如何自定义一个自己的 Spring Boot Starter 组件(从入门到实践)

  • 解密 Redis:如何通过 IO 多路复用征服高并发挑战!

  • 线程 vs 虚拟线程:深入理解及区别

  • 深度解读 JDK 8、JDK 11、JDK 17 和 JDK 21 的区别

  • 10大程序员提升代码优雅度的必杀技,瞬间让你成为团队宠儿!

  • “打破重复代码的魔咒:使用 Function 接口在 Java 8 中实现优雅重构!”

  • Java 中消除 If-else 技巧总结

  • 线程池的核心参数配置(仅供参考)

  • 【人工智能】聊聊Transformer,深度学习的一股清流(13)

  • Java 枚举的几个常用技巧,你可以试着用用

  • 由 Spring 静态注入引发的一个线上T0级别事故(真的以后得避坑)

  • 如何理解 HTTP 是无状态的,以及它与 Cookie 和 Session 之间的联系

  • HTTP、HTTPS、Cookie 和 Session 之间的关系

  • 使用 Spring 框架构建 MVC 应用程序:初学者教程

  • 有缺陷的 Java 代码:Java 开发人员最常犯的 10 大错误

  • Java Spring 中常用的 @PostConstruct 注解使用总结

  • 线程 vs 虚拟线程:深入理解及区别

  • 深度解读 JDK 8、JDK 11、JDK 17 和 JDK 21 的区别

  • 10大程序员提升代码优雅度的必杀技,瞬间让你成为团队宠儿!

  • 探索 Lombok 的 @Builder 和 @SuperBuilder:避坑指南(一)

  • 为什么用了 @Builder 反而报错?深入理解 Lombok 的“暗坑”与解决方案(二)

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

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

相关文章

【JavaWeb】详细讲解 HTTP 协议

文章目录 一、HTTP简介1.1 概念1.2 特点 二、协议2.1 HTTP-请求协议&#xff08;1&#xff09;GET方式&#xff08;2&#xff09;POST方式&#xff08;3&#xff09;GET和POST的区别&#xff1a; 2.2 HTTP-响应协议&#xff08;1&#xff09;格式&#xff08;2&#xff09;响应…

“星睿O6” AI PC开发套件评测 - Windows on Arm 安装指南和性能测评

引言 Radxa联合此芯科技和安谋科技推出全新的"星睿O6"迷你 ITX 主板。该系统搭载了 CIX P1&#xff08;CD8180&#xff09;12 核 Armv9 处理器&#xff0c;拥有高达30T算力的NPU和高性能的GPU&#xff0c;最高配备64GB LPDDR内存&#xff0c;并提供了如 5GbE、HDMI …

Python 调用 YOLOv11 ONNX

Python 调用 YOLO ONNX 1 下载ONNX文件2 Python代码 1 下载ONNX文件 ONNX下载地址 2 Python代码 import cv2 from ultralytics import YOLOdef check(yolo:str, path:str):# 加载 YOLOv11model YOLO(yolo)# 读取图片img cv2.imread(path)# 推理&#xff08;可以传文件路径…

数据通信学习笔记之OSPF路由汇总

区域间路由汇总 路由汇总又被称为路由聚合&#xff0c;即是将一组前缀相同的路由汇聚成一条路由&#xff0c;从而达到减小路由表规模以及优化设备资源利用率的目的&#xff0c;我们把汇聚之前的这组路由称为精细路由或明细路由&#xff0c;把汇聚之后的这条路由称为汇总路由或…

ASP.NET Core Web API 配置系统集成

文章目录 前言一、配置源与默认设置二、使用步骤1&#xff09;创建项目并添加配置2&#xff09;配置文件3&#xff09;强类型配置类4&#xff09;配置Program.cs5&#xff09;控制器中使用配置6&#xff09;配置优先级测试7&#xff09;动态重载配置测试8&#xff09;运行结果示…

如何判断单片机性能极限?

目录 1、CPU 负载 2、内存使用情况 3、实时性能 4、外设带宽 5、功耗与温度 在嵌入式系统设计中&#xff0c;当系统变得复杂、功能增加时&#xff0c;单片机可能会逐渐逼近其性能极限。及时识别这些极限点对于保证产品质量、稳定性和用户体验至关重要。 当你的嵌入式系统…

AI在多Agent协同领域的核心概念、技术方法、应用场景及挑战 的详细解析

以下是 AI在多Agent协同领域的核心概念、技术方法、应用场景及挑战 的详细解析&#xff1a; 1. 多Agent协同的定义与核心目标 多Agent系统&#xff08;MAS, Multi-Agent System&#xff09;&#xff1a; 由多个独立或协作的智能体&#xff08;Agent&#xff09;组成&#xff…

1.凸包、极点、极边基础概念

目录 1.凸包 2.调色问题 3.极性(Extrem) 4.凸组合(Convex Combination) 5.问题转化(Strategy)​编辑 6.In-Triangle test 7.To-Left-test 8.极边&#xff08;Extream Edges&#xff09; 1.凸包 凸包就是上面蓝色皮筋围出来的范围 这些钉子可以转换到坐标轴中&#xff0…

OSCP - Proving Grounds - DriftingBlues6

主要知识点 路径爆破dirtycow内核漏洞提权 具体步骤 总体来讲&#xff0c;这台靶机还是比较直接的&#xff0c;没有那么多的陷阱,非常适合用来学习 依旧是nmap开始,只开放了80端口 Nmap scan report for 192.168.192.219 Host is up (0.42s latency). Not shown: 65534 cl…

深度理解指针之例题

文章目录 前言题目分析与讲解涉及知识点 前言 对指针有一定了解后&#xff0c;讲一下一道初学者的易错题 题目分析与讲解 先定义一个数组跟一个指针变量 然后把数组名赋值给指针变量————也就是把首地址传到pulPtr中 重点是分析这一句&#xff1a; *&#xff08;pulPtr…

LeetCode算法题(Go语言实现)_51

题目 给你两个下标从 0 开始的整数数组 nums1 和 nums2 &#xff0c;两者长度都是 n &#xff0c;再给你一个正整数 k 。你必须从 nums1 中选一个长度为 k 的 子序列 对应的下标。 对于选择的下标 i0 &#xff0c;i1 &#xff0c;…&#xff0c; ik - 1 &#xff0c;你的 分数 …

Solon AI MCP Server 入门:Helloworld (支持 java8 到 java24。国产解决方案)

目前网上能看到的 MCP Server 基本上都是基于 Python 或者 nodejs &#xff0c;虽然也有 Java 版本的 MCP SDK&#xff0c;但是鲜有基于 Java 开发的。 作为Java 开发中的国产顶级框架 Solon 已经基于 MCP SDK 在进行 Solon AI MCP 框架开发了&#xff0c;本文将使用 Solon AI …

公司内部自建知识共享的方式分类、详细步骤及表格总结,分为开源(对外公开)和闭源(仅限内部),以及公共(全员可访问)和内部(特定团队/项目组)四个维度

以下是公司内部自建知识共享的方式分类、详细步骤及表格总结&#xff0c;分为开源&#xff08;对外公开&#xff09;和闭源&#xff08;仅限内部&#xff09;&#xff0c;以及公共&#xff08;全员可访问&#xff09;和内部&#xff08;特定团队/项目组&#xff09;四个维度&am…

Oracle 19c部署之初始化实例(三)

上一篇文章中&#xff0c;我们已经完成了数据库软件安装&#xff0c;接下来我们需要进行实例初始化工作。 一、初始化实例的两种方式 1.1 图形化初始化实例 描述&#xff1a;图形化初始化实例是通过Oracle的Database Configuration Assistant (DBCA)工具完成的。用户通过一系…

医疗设备预测性维护合规架构:从法规遵循到技术实现的深度解析

在医疗行业数字化转型加速推进的当下&#xff0c;医疗设备预测性维护已成为提升设备可用性、保障医疗安全的核心技术。然而&#xff0c;该技术的有效落地必须建立在严格的合规框架之上。医疗设备直接关乎患者生命健康&#xff0c;其维护过程涉及医疗法规、数据安全、质量管控等…

Openfeign的最佳实践

文章目录 问题引入一、继承的方式1. 建立独立的Moudle服务2. 服务调用方继承jar包中的接口3. 直接注入继承后的接口进行使用 二、抽取的方式1. 建立独立的Moudle服务2.服务调用方依赖注入 问题引入 openfeign接口的实现和服务提供方的controller非常相似&#xff0c;例如&…

Buildroot编译过程中下载源码失败

RK3588编译一下recovery&#xff0c;需要把buildroot源码编译一遍。遇到好几个文件都下载失败&#xff0c;如下所示 pm-utils 1.4.1这个包下载失败&#xff0c;下载地址http://pm-utils.freedesktop.org/releases 解决办法&#xff0c;换个网络用windows浏览器下载后&#xff…

OpenCV 图形API(43)颜色空间转换-----将 BGR 图像转换为 LUV 色彩空间函数BGR2LUV()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 将图像从BGR色彩空间转换为LUV色彩空间。 该函数将输入图像从BGR色彩空间转换为LUV。B、G和R通道值的传统范围是0到255。 输出图像必须是8位无符…

自问自答模式(Operation是什么)

自问自答 问&#xff1a;Operation 注解来自哪里&#xff1f; 答&#xff1a;Operation 是 OpenAPI&#xff08;Swagger&#xff09;规范中&#xff0c;来自 io.swagger.v3.oas.annotations 包的一个注解&#xff0c;用于给 REST 接口增加文档元数据。 问&#xff1a;summary …

996引擎-实战笔记:Lua 的 NPC 面板获取 Input 内容

996引擎-实战笔记:Lua 的 NPC 面板获取 Input 内容 获取 Input 内容测试NPC参考资料获取 Input 内容 测试NPC -- NPC入口函数 function main(player)local msg = [[<Img|id=9527|x=0|y=0|width=300|height=150|img=public/bg_npc_01.png|bg=1|move=1|reset=1|show=0|layer…