泛型通配符 T、E、K、V、?

在Java后端开发中,你一定在写集合类或工具类时,见过 T、E、K、V、? 这样的泛型通配符。但你是否有过以下疑惑:

  • T、E、K、V 到底有什么区别?为什么大家都用这些字母?
  • List<?> 和 List 有什么不同?什么时候该用通配符,什么时候该用类型参数?
  • 如果不用泛型,代码也能跑,为什么一定要用泛型?

1. 为什么要用泛型

类型不安全与强制转换

假设我们要写一个简单的盒子类,用来存放物品:

// 没有泛型的盒子类
public class Box {private Object item;  // 只能用Object存储任何类型public void setItem(Object item) {this.item = item;}public Object getItem() {return item;}
}

使用方式:​

public static void main(String[] args) {Box box = new Box();box.setItem("Hello");  // 存入StringString s = (String) box.getItem();  // 必须强制转换回Stringbox.setItem(123);      // 也可以存入IntegerString i = (String) box.getItem();  // 但这里会抛出ClassCastException!
}

问题:​​

  • ​​类型不安全​​:可以存入任何类型(String、Integer等),但取出时容易忘记转换或转换错误
  • ​​繁琐的强制转换​​:每次取出都要手动cast
  • ​​运行时错误​​:如果类型转换错了,只能在运行时才发现(抛出ClassCastException)

使用泛型后**

// 泛型盒子类
public class Box<T> {private T item;  // T是类型参数public void setItem(T item) {this.item = item;}public T getItem() {return item;  // 不需要强制转换}
}public static void main(String[] args) {Box<String> stringBox = new Box<>();stringBox.setItem("Hello");String s = stringBox.getItem();  // 自动就是String类型,无需转换Box<Integer> intBox = new Box<>();intBox.setItem(123);Integer i = intBox.getItem();  // 自动就是Integer类型stringBox.setItem(123);  // 编译错误!不能放入Integer}

2. T、E、K、V、? 的含义

首先,我们要明确一个概念,TEKV类型参数(Type Parameter),而?通配符(Wildcard)。他们虽然都用在泛型中,但扮演的角色完全不同。Java 官方并没有强制规定这些字母的含义,只是社区形成了约定俗成的写法。常见规则如下:

符号常见含义使用场景
TType(类型)通用类型,最常见
EElement(元素)集合中的元素
KKey(键)映射的键(Map)
VValue(值)映射的值(Map)
?通配符表示未知类型,常用于 API 的参数或返回值

2.1 使用 T (Type,任意类型)

示例:API响应包装器

// 使用 T 定义一个通用的API响应类
public class ApiResponse<T> {private int code;private String message;private T data; // T 代表响应的业务数据类型// 构造方法public ApiResponse(int code, String message, T data) {this.code = code;this.message = message;this.data = data;}// 成功响应的静态工厂方法public static <T> ApiResponse<T> success(T data) {return new ApiResponse<>(200, "成功", data);}public static ApiResponse<?> error(int code, String message) {return new ApiResponse<>(code, message, null);}// Getter 和 Setterpublic T getData() {return data;}public void setData(T data) {this.data = data;}// ... 其他getter/setter
}// 业务实体
public class User {private Long id;private String name;private String email;// ... 构造方法、getter、setter
}public class Product {private Long id;private String name;private BigDecimal price;// ... 构造方法、getter、setter
}// 在Service层使用
public class UserService {public ApiResponse<User> getUserById(Long id) {User user = userRepository.findById(id);if (user != null) {return ApiResponse.success(user); // T 被推断为 User} else {return ApiResponse.error(404, "用户不存在");}}
}public class ProductService {public ApiResponse<List<Product>> getFeaturedProducts() {List<Product> products = productRepository.findFeatured();return ApiResponse.success(products); // T 被推断为 List<Product>}
}// Controller层调用
@GetMapping("/users/{id}")
public ApiResponse<User> getUser(@PathVariable Long id) {return userService.getUserById(id);// 返回: {"code":200,"message":"成功","data":{"id":1,"name":"张三","email":"zhang@example.com"}}
}@GetMapping("/products/featured")
public ApiResponse<List<Product>> getFeaturedProducts() {return productService.getFeaturedProducts();// 返回: {"code":200,"message":"成功","data":[{"id":101,"name":"手机","price":2999.00}]}
}

2.2 E(Element,集合中的元素)

示例:树形结构节点

// 通用树节点(可用于组织架构、分类目录等)
public class TreeNode<E> {private E data;private List<TreeNode<E>> children;public void addChild(TreeNode<E> child) {if (children == null) children = new ArrayList<>();children.add(child);}
}// 使用示例
TreeNode<String> root = new TreeNode<>();
root.setData("总公司");TreeNode<String> branch1 = new TreeNode<>();
branch1.setData("北京分公司");
root.addChild(branch1);TreeNode<String> branch2 = new TreeNode<>();
branch2.setData("上海分公司");
root.addChild(branch2);

2.3 类型参数 K(Key)和 V(Value)——键值对

示例:本地缓存类

// 本地缓存实现
public class LocalCache<K, V> {private Map<K, V> cache = new ConcurrentHashMap<>();private long expireTime;public void put(K key, V value) {cache.put(key, value);}public V get(K key) {return cache.get(key);}
}// 使用示例
LocalCache<Long, User> userCache = new LocalCache<>();
userCache.put(1001L, new User(1001L, "Alice"));LocalCache<String, List<Product>> categoryCache = new LocalCache<>();
categoryCache.put("electronics", Arrays.asList(new Product(...), ...));

2.4 通配符 ? ——处理未知类型

Java 泛型通配符主要有三种形态

1)无界通配符 ?

无界通配符表示可以匹配任何类型,适用于不确定或无关具体类型的情况。
示例:打印任意集合元素

import java.util.*;public class Demo1 {public static void printList(List<?> list) {for (Object element : list) {System.out.println(element);}}public static void main(String[] args) {List<String> names = Arrays.asList("Tom", "Jerry");List<Integer> scores = Arrays.asList(88, 99);printList(names);  // 输出 Tom, JerryprintList(scores); // 输出 88, 9}
}

特点:

  • 可以接收任何类型的 List。
  • 只能读取元素,不能随意 add。
2)上界通配符 ? extends T

表示“某种类型是 T 或 T 的子类”,适合生产者 / 只读场景(PECS 原则中的 Producer)。
示例:打印数字列表

import java.util.*;public class Demo1 {public static void printNumbers(List<? extends Number> list) {for (Number n : list) {System.out.println(n);}}public static void main(String[] args) {List<Integer> ints = Arrays.asList(1, 2, 3);List<Double> doubles = Arrays.asList(1.1, 2.2, 3.3);printNumbers(ints);    // Integer extends NumberprintNumbers(doubles); // Double extends Number}
}

特点:

  • 可以读取元素为 Number 类型。
  • 不能写入 list.add(…),因为不知道具体是 Integer 还是 Double。
3)下界通配符 ? super T

表示“某种类型是 T 或 T 的父类”,适合消费者 / 写入场景(PECS 原则中的 Consumer)。
示例:向集合中添加数字

import java.util.*;public class Demo3 {public static void addNumbers(List<? super Integer> list) {list.add(10);list.add(20);}public static void main(String[] args) {List<Number> numbers = new ArrayList<>();List<Object> objects = new ArrayList<>();addNumbers(numbers); // Number 是 Integer 的父类addNumbers(objects); // Object 是 Integer 的父类System.out.println(numbers); // 输出 [10, 20]System.out.println(objects); // 输出 [10, 20]}
}

特点:

  • 可以安全向集合写入 Integer 类型。
  • 读取出来的元素只能当作 Object,因为类型不确定
总结
通配符含义适用场景示例特点
?无界泛用工具、只读 API可以读取,不能写入,适合日志/导出/打印等
? extends T上界生产者 / 只读可以读取 T 或其子类,不能写入
? super T下界消费者 / 写入可以写入 T 类型,读取只能当 Object

3. 通配符中的PECS原则

PECS 是 Java大师Joshua Bloch 在《Effective Java》里提出的一个泛型使用经验法则,用来指导我们在选择通配符时,应该用 extends 还是 super。

  • Producer Extends:如果参数是生产者(提供数据给你),就用 ? extends T。
  • Consumer Super:如果参数是消费者(你要把数据放进去),就用 ? super T。

简单一句话:

  • 读(生产者)用 extends,写(消费者)用 super。

示例 1:Producer(读数据)
假设我们有个方法,需要从集合里读取元素:

public static void printNumbers(List<? extends Number> list) {for (Number n : list) {System.out.println(n);}
}
  • list 是一个 生产者(提供数字给我们打印),所以用 ? extends Number,允许 List、List 传进来。

示例 2:Consumer(写数据)
假设我们有个方法,需要往集合里写入数据:

public static void addIntegers(List<? super Integer> list) {list.add(1);list.add(2);
}

list 是一个 消费者(我们往里面放 Integer)所以用 ? super Integer,允许 List、List、List 传进来。

4. 注意事项

  • 能用泛型参数就别用 Object,除非你明确就是要“任意类型”,否则优先用泛型。
  • 合理选择通配符 ? , 只读数据 → ? extends T,只写数据 → ? super T
  • 不要滥用泛型,有些场景写成泛型反而增加理解成本,比如方法内部只操作 String,就直接用 String

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

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

相关文章

基于脚手架微服务的视频点播系统-数据管理与网络通信部分的预备工作

基于脚手架微服务的视频点播系统-数据管理与网络通信部分的预备工作一.数据管理二.网络通信2.1客户端通信模块及测试用例的实现2.2MockServer搭建的相关接口介绍2.3MockServer的搭建示例一.数据管理 在前⾯的实现中&#xff0c;程序中的数据、以及界⾯操作等全部搅合在⼀起&am…

html表单登录模式代码

使用的是Content-Typeapplication/x-www-form-urlencoded形式如代码如下的html&#xff0c;后端没写下去&#xff1a;<!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><title>管理员登录</title><…

NLP:Transformer之self-attention(特别分享3)

本文目录&#xff1a;一、核心思想&#xff1a;一句话概括二、计算流程的直观比喻三、分步计算详解&#xff08;附数字例子&#xff09;第 1 步&#xff1a;创建 Query, Key, Value 向量第 2 步&#xff1a;计算注意力分数第 3 步&#xff1a;缩放并应用 Softmax第 4 步&#x…

25、优化算法与正则化技术:深度学习的调优艺术

学习目标:深入理解各种优化算法的原理和适用场景,掌握学习率调度的方法和策略,学会使用Dropout、批归一化等正则化技术,理解早停和验证策略,建立深度学习调优的系统性知识。 深度学习的成功不仅依赖于精巧的模型架构,更在于巧妙的训练策略。如果说网络架构是房屋的设计图…

Netty-01-NIO前置知识

目录 NIO三大组件 一. ByteBuffer 基本用法 DirectByteBuffer与HeapByteBuffer对比 字符串转ByteBuffer ByteBuffer.wrap(byte[] ) 粘包与拆包 文件编程 零拷贝transferTo 二. 阻塞与非阻塞Channel 三. Selector SelectionKey&#xff08;重点&#xff09; Select…

知识点17:多Agent系统架构设计模式

知识点17&#xff1a;多Agent系统架构设计模式 核心概念 掌握系统架构思维&#xff0c;理解多Agent系统的设计原则和模式 架构设计核心概念 在构建多Agent系统时&#xff0c;良好的架构设计是系统成功的关键。本节将介绍多Agent系统架构设计中的核心概念&#xff0c;包括单点瓶…

数据库造神计划第五天---增删改查(CRUD)(1)

&#x1f525;个人主页&#xff1a;寻星探路 &#x1f3ac;作者简介&#xff1a;Java研发方向学习者 &#x1f4d6;个人专栏&#xff1a;《从青铜到王者&#xff0c;就差这讲数据结构&#xff01;&#xff01;&#xff01;》、 《JAVA&#xff08;SE&#xff09;----如此简单&a…

基于Vue3的人工智能生成内容标识服务平台前端页面设计

效果图&#xff1a;素材库&#xff1a;App.vue<template><div id"app"><!-- 头部导航 --><Header /><!-- 主要内容区域 --><main class"main-content"><div class"container"><!-- 强制性国家标准…

使用 MyCat 实现 MySQL 主从读写分离

文章目录使用 MyCat 实现 MySQL 主从读写分离完整指南一、MySQL 读写分离基础概述1.1 读写分离工作原理1.2 为什么需要读写分离1.3 读写分离的两种实现方式主流读写分离中间件对比二、MyCat 中间件简介2.1 MyCat 核心功能2.2 MyCat 适用场景三、环境准备与 MyCat 安装3.1 前提&…

物联网传感器检测实验

/*------------------------------------------------------------------------------ * @文件名 : handle * @描述 : 用户处理函数 * @作者 : 物联网项目组 * @日期 : 2023/04/01 * @版本 : V0.0.2 *****************************…

什么是dirsearch、xray、durpsuite、sqlmap?

你提到的 dirsearch、xray、durpsuite&#xff08;可能为笔误&#xff0c;推测是 ​​Burp Suite​​&#xff09;和 sqlmap 均为网络安全领域中常用的工具&#xff0c;主要用于 Web 应用的安全测试、漏洞检测或渗透测试。以下分别详细说明&#xff1a;​​1. dirsearch​​​​…

lamp脚本部署

#!/bin/bash #关闭防火墙和selinux systemctl stop firewalld systemctl disable firewalld setenforce 0 #配置yum网络源 echo “正在配置yum仓库” rm -rf /etc/yum.repos.d/* wget -O /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo &am…

Redis Hash数据类型深度解析:从命令、原理到实战场景

前言 在Redis的众多数据结构中&#xff0c;Hash&#xff08;哈希&#xff09;类型占据着至关重要的地位。Redis本身就是一个高性能的键值&#xff08;Key-Value&#xff09;数据库&#xff0c;其底层的键值对便是通过哈希方式组织的。而Hash数据类型则更进一步&#xff0c;它允…

【C++实战⑬】解锁C++文件操作:从基础到实战的进阶之路

目录一、文件操作的基本概念1.1 文件的分类与打开方式1.2 文件流的概念与相关类&#xff08;ifstream、ofstream、fstream&#xff09;1.3 文件操作的基本流程二、文本文件的读写实战2.1 文本文件的打开与关闭2.2 文本文件的写入操作&#xff08;<< 运算符、write 函数&a…

从C++开始的编程生活(9)——模板初阶

前言 本系列文章承接C语言的学习&#xff0c;需要有C语言的基础才能学会哦~ 第8篇主要讲的是有关于C的模板初阶。 C才起步&#xff0c;都很简单&#xff01;&#xff01; 目录 前言 模板初阶 基本语法 函数模板的实例化 显式实例化的作用 类模板 基本语法 模板初阶 模板…

计算机网络——传输层(25王道最新版)

传输层传输层提供的服务进程 端口号 传输层协议之间的关系socket套接字有链接 VS 无连接 | 可靠 VS 不可靠UDP数据报及检验数据报格式检验方法TCPTCP协议的三大阶段TCP报文段格式&#xff08;很重要&#xff09;建立连接&#xff08;三次握手&#xff09;&#xff08;超级超级重…

羽毛球地板:从专业运动场景到全民健身市场的技术跃迁与产业重构

在全球体育产业向“专业化大众化”双轨并行的趋势下&#xff0c;羽毛球地板作为运动场景的核心基础设施&#xff0c;正经历从单一功能型产品向“性能优化场景适配智能管理”一体化解决方案的转型。据QYResearch统计&#xff0c;2031年全球羽毛球地板市场规模将达15.95亿元&…

R 语言查看类库源码的方法

你想查看 getGEO&#xff08;来自 R 语言 GEOquery 包&#xff09;的源码&#xff0c;这能帮你更好理解其工作原理和数据处理细节。由于 getGEO 是 R 函数&#xff0c;查看方法与 Python 有所不同。下面为你提供几种主要方法。 方法 适用场景 关键命令/操作 在 R 控制台直接查看…

SQL,posexplode 用法示例

示例1 -- 创建测试数据 WITH test_data AS (SELECT array(apple, banana, cherry) as fruits ) SELECT pos, col FROM test_data LATERAL VIEW posexplode(fruits) t AS pos, col;结果 pos | col ----|------- 0 | apple 1 | banana 2 | cherry示例2 -- 假设有一个用户表…

数据库造神计划第十天---数据库约束(1)

&#x1f525;个人主页&#xff1a;寻星探路 &#x1f3ac;作者简介&#xff1a;Java研发方向学习者 &#x1f4d6;个人专栏&#xff1a;《从青铜到王者&#xff0c;就差这讲数据结构&#xff01;&#xff01;&#xff01;》、 《JAVA&#xff08;SE&#xff09;----如此简单&a…