当前位置: 首页 > ai >正文

Java 核心--泛型枚举

作者:IvanCodes

发布时间:2025年4月30日🤓

专栏:Java教程


各位 CSDN伙伴们,大家好!👋 写了那么多代码,有没有遇到过这样的“惊喜”:满心欢喜地从 ArrayList 里取出数据,想当然地以为它是个 String,结果一运行,“啪”!一个 ClassCastException 甩在脸上?😵‍💫 或者,为了表示几个固定的状态(比如订单处理中、已发货),用了 1, 2, 3 这样的“魔法数字”🧙‍♂️,过段时间自己都忘了哪个数字对应哪个状态?🤦‍♀️

别担心,这些都是新手(甚至老手!)路上常见的“坑”。好在 Java 提供了两大利器来帮我们填坑:泛型 (Generics) 和 枚举 (Enums)!它们就像给我们的代码上了双重保险,让代码更安全、更易懂。今天,我们就来把这两个“保险”搞明白!🛠️✨

一、 泛型 (<T>):类型的“占位符”,安全的“万能容器

还记得那个什么都能装Object 类型的 ArrayList 吗?它就像一个不透明大麻袋 🛍️,苹果 🍎、书 📚、玩具 🧸 都能往里扔。方便是方便,可往外取的时候就麻烦了——你得里面是啥,然后强制转换类型。万一猜错了… 运行时就了!💥

泛型 就是来解决这个问题的救星!它的核心思想,说白了就是:把类型检查这活儿,从不靠谱的运行时,提前到靠谱的编译时搞定! 并且顺便让代码更好看、更好用。

它是怎么做到的呢?通过引入 类型参数 (Type Parameters)——你可以把它想象成一个类型的“占位符” (常写成 <T>, <E> 这类大写字母)。在定义类、接口或方法的时候,先用这个占位符代表“未来的某种类型”。等到实际使用的时候,再明确告诉编译器:“嘿,这次这个 <T> 代表的是 String!”或者“这次 <E> 代表 Integer!”

1.1 泛型带来的实实在在的好处

  • 类型安全 <✅>:编译器成了你的“类型警察”👮‍♀️。你往 ArrayList<String> 里塞个 Integer编译时就给你拦下来!再也不会有运行时的 ClassCastException 意外了。
  • 告别强制类型转换 <✨>:既然编译器已经帮你把好关了,从 ArrayList<String> 里取出来的元素,它百分百就是 String!再也不用写 (String) 这种难看又可能出错的代码了。代码瞬间清爽不少!
  • 代码更通用、复用性更高 <🔄>:想想 ArrayList<T>,一份代码就能搞定 ArrayList<String>, ArrayList<Integer>, ArrayList<YourCustomClass>… 这就是泛型带来的代码复用魔力。

1.2 怎么玩转泛型

1.2.1 泛型类 <📦>

最常见的用法,定义一个可以持有特定类型对象的类。

// 一个“类型安全”的盒子
public class Box<T> { // <T> 是类型占位符private T item; // 里面的东西是 T 类型public void setItem(T item) {this.item = item;}public T getItem() {return item;}public static void main(String[] args) {// 创建一个只能装 String 的盒子Box<String> stringBox = new Box<>(); // 明确指定 T 为 StringstringBox.setItem("Hello Generics! <✨>");String message = stringBox.getItem(); // 直接就是 String,无需强转System.out.println(message);// stringBox.setItem(123); // 编译器报错❌!类型警察出动!// 创建一个只能装 Integer 的盒子Box<Integer> integerBox = new Box<>();integerBox.setItem(123);int number = integerBox.getItem(); // 直接就是 int (自动拆箱)System.out.println("Number in box: " + number);}
}
1.2.2 泛型方法 <🔧>

有时候,只是某个方法需要处理泛型,而不是整个类。

public class GenericMethodDemo {// 一个可以打印任何类型数组的泛型方法// 类型参数 <E> 声明在 static 和 返回值 void 之间public static <E> void printArray(E[] inputArray) {System.out.print("Array elements: [ ");for (E element : inputArray) { // element 的类型就是 ESystem.out.print(element + " ");}System.out.println("]");}public static void main(String[] args) {Integer[] intArray = { 1, 2, 3 };String[] stringArray = { "A", "B", "C" };// 调用时通常无需显式指定,编译器会自动推断System.out.println("Integer Array:");printArray(intArray); // 编译器推断 E 是 IntegerSystem.out.println("\nString Array:");printArray(stringArray); // 编译器推断 E 是 String}
}
1.2.3 有界类型参数 <T extends Number>

想让你的泛型更“挑剔”一点?比如,我的盒子只装数字相关的类型!

  • <T extends UpperBound>:告诉编译器,这里的 T 必须是 UpperBound 这个类,或者是它的子类。就像给盒子贴了个标签:“仅限数字!”🔢
  • 这让你可以在泛型代码内部安全地调用 UpperBound 类定义的方法。
// 一个只能装 Number 及其子类的盒子
class NumericBox<T extends Number> { // 关键字限定上界private T number;// ... setter/getter ...public double getDoubleValue() {// 因为 T 保证是 Number 或其子类,所以可以安全调用 Number 的方法return number.doubleValue();}
}public class BoundedTypeDemo {public static void main(String[] args) {NumericBox<Integer> intBox = new NumericBox<>();   // OK <✅>NumericBox<Float> floatBox = new NumericBox<>(); // OK <✅>// NumericBox<String> strBox = new NumericBox<>(); // 编译错误❌!String 不是 Number}
}
1.2.4 通配符 (Wildcards) <?> <❓>

通配符这东西,初看可能有点绕 <😵‍💫>,但它主要用在方法参数变量声明时,让你写出更灵活的代码,可以接收或引用“某种未知类型”的泛型

  • ? (无界通配符): “我啥都能接,但我不知道具体是啥”。List<?> list 可以指向 List<String>, List<Integer> 等等。但为了类型安全,你不能list添加任何元素(除了 null),通常只用于读取或调用Object的方法。
  • ? extends UpperBound (上界通配符): “我能接 UpperBound 及其所有子类型”。List<? extends Number> list 可以指向 List<Integer>, List<Double> 等。同样,不能往里添加元素(除了 null),主要用于安全地读取元素作为 UpperBound 类型(生产者场景)。
  • ? super LowerBound (下界通配符): “我能接 LowerBound 及其所有父类型”。List<? super Integer> list 可以指向 List<Integer>, List<Number>, List<Object>。你可以安全地list添加 Integer 或其子类的对象(消费者场景)。

何时深入? 当你开始大量使用泛型集合作为方法参数,并且希望方法能更通用地处理不同类型的集合时,就是研究通配符的好时机。

二、 枚举 (enum):定义常量集合的“专属俱乐部” 👑🚦

现在换个场景。如果你的程序需要表示一组固定的、有限的值,比如一周七天 📅、红绿灯状态 🚦、订单状态 (待付款、已付款、已发货…) 等等。

老办法可能是用 int 常量 (public static final int MONDAY = 1;) 或者 String 常量 (public static final String PENDING = "PENDING";)。但这种方式问题多多

  • 类型不安全🚫:一个期望星期几 int 的方法,你传个 100 进去,编译器根本不管
  • 可读性差 <😵‍💫>:代码里看到个数字 3,谁知道它代表星期三还是订单已发货?得翻文档去…
  • 没有命名空间 <🏷️>:常量名容易冲突。
  • 难以管理 <🛠️>:增加或修改常量可能涉及多处代码。

枚举 (enum) 就是来终结这种混乱的!它让你创建一个类型安全、含义清晰、管理方便常量集合

枚举的核心思想用一个专属的类型来代表一组有限的、命名的常量,并提供编译时安全检查

2.1 枚举闪光点

  1. 类型安全 <✅>:编译器强制你只能使用枚举中定义的常量。想给 DayOfWeek 类型的变量赋个 TrafficLight.RED?没门!编译错误
  2. 代码清晰、可读性爆表 <📖>if (order.getStatus() == OrderStatus.SHIPPED)if (order.getStatus() == 3) 不知道清晰多少倍!
  3. 代码更健壮、易维护 <🛠️>:常量集中管理。想加个“退款中”的状态?改 enum 就行。
  4. 不仅仅是常量 <💪>枚举本质上是特殊的class!它可以有构造方法、成员变量、普通方法,甚至可以实现接口!功能远超你的想象!

2.2 玩转枚举

2.2.1 基础款枚举 <🚦>

最简单的用法,就是定义一组常量。

// 定义交通信号灯枚举
public enum TrafficLight { // 使用 enum 关键字RED, YELLOW, GREEN // 常量列表,规范用大写
}public class BasicEnumSwitchDemo {public static void main(String[] args) {TrafficLight currentLight = TrafficLight.GREEN;// 在 switch 中使用枚举是绝配!<🎯>switch (currentLight) {case RED: // case 后面直接用常量名,不用写 TrafficLight.REDSystem.out.println("Stop! <✋>");break;case YELLOW:System.out.println("Caution! <⚠️>");break;case GREEN:System.out.println("Go! <✅>");break;// default 通常可以省略,因为枚举类型是有限的}// 遍历枚举所有常量System.out.println("\nAll light states:");for (TrafficLight light : TrafficLight.values()) { // values() 获取所有常量数组System.out.println("- " + light + " (ordinal: " + light.ordinal() + ")"); // ordinal() 是常量顺序}// 从字符串获取枚举常量TrafficLight lightFromString = TrafficLight.valueOf("RED"); // valueOf(),字符串必须精确匹配System.out.println("\nLight from string 'RED': " + lightFromString);}
}
2.2.2 进阶版枚举:带属性和方法 <👑><⚙️>

让你的常量“活”起来,拥有自己的数据和行为!

// 星期枚举,包含是否是工作日的属性和方法
public enum DayOfWeek {MONDAY(true),   // 调用构造方法传入 trueTUESDAY(true),WEDNESDAY(true),THURSDAY(true),FRIDAY(true),SATURDAY(false),  // 调用构造方法传入 falseSUNDAY(false);private final boolean isWeekday; // final 实例变量// 构造方法必须是 private (或包级私有)private DayOfWeek(boolean isWeekday) {this.isWeekday = isWeekday;}// 公共方法来获取属性public boolean isWeekday() {return isWeekday;}// 还可以定义其他方法public void printTypeOfDay() {if (isWeekday) {System.out.println(this.name() + " is a weekday. <💼>");} else {System.out.println(this.name() + " is part of the weekend! <🎉>");}}
}public class EnumWithMethodDemo {public static void main(String[] args) {DayOfWeek today = DayOfWeek.SATURDAY;System.out.println("Is today a weekday? " + today.isWeekday()); // falsetoday.printTypeOfDay(); // SATURDAY is part of the weekend! <🎉>System.out.println("\nChecking all days:");for (DayOfWeek day : DayOfWeek.values()) {day.printTypeOfDay();}}
}

三、 泛型 vs. 枚举 & 何时请哪位“大神”? 🤔⚖️

虽然都是好东西,但它们解决的问题完全不同:

  • 泛型出马:当你需要编写能处理各种不同(但类型未知)数据的通用代码时,比如 List<T>, Map<K,V>,或者一个通用的排序方法。目标是类型参数化编译时安全
  • 枚举出马:当你需要表示一个固定的、有限的、已知的常量集合时,比如星期、方向、状态、颜色等。目标是类型安全可读性清晰地表达意图

简单概括泛型处理不确定性 (类型);枚举处理确定性 (常量集合)。

四、总结 🏁✨

泛型枚举,就像给你的 Java 代码装上了安全带清晰的路标

  • 泛型 (<T>): 编译时就帮你挡住类型错误 <🛡️>,省去强制转换的麻烦 <✨>,让代码复用更容易 <🔄>。
  • 枚举 (enum): 把混乱的常量变成类型安全易读易维护的“专属俱乐部” <👑>,还能自带属性和方法 <💪>。

我的经验是:一旦你习惯了使用它们,就再也回不去那个充满ClassCastException魔法数字的“蛮荒时代”了!😄 它们是写出高质量现代 Java 代码的必备技能


五、练练手,检验成果!✏️🧠

光听不练等于零,动手试试吧!

⭐ 泛型应用 ⭐

  1. 创建一个泛型接口 Comparator<T>,包含一个方法 int compare(T o1, T o2),用于比较两个 T 类型的对象。然后创建一个实现类 StringLengthComparator 实现 Comparator<String>,用于比较字符串的长度。
  2. 编写一个泛型方法 <T> T findFirstMatch(List<T> list, Predicate<T> condition),该方法接收一个列表和一个条件(Predicate 是一个函数式接口,可以用 lambda 表达式 t -> boolean),返回列表中第一个满足条件的元素,如果找不到则返回 null。(提示:Predicate<T> 接口有一个 test(T t) 方法)

⭐ 枚举应用 ⭐

  1. 定义一个枚举 Size,包含常量 SMALL, MEDIUM, LARGE
  2. 为第 3 题的 Size 枚举添加一个int类型的 minWidth 字段和一个int类型的 maxWidth 字段,并提供构造方法和 getter。例如 SMALL(0, 50), MEDIUM(51, 100), LARGE(101, Integer.MAX_VALUE)

⭐ 概念理解 ⭐

  1. 什么是类型擦除?它是泛型实现的一部分吗?它对我们编写泛型代码有什么影响?(简单说明即可)
  2. 枚举类型可以extends(继承)另一个类吗?可以implements(实现)接口吗?

六、参考答案 ✅💡

⭐ 泛型应用答案 ⭐

1.Comparator<T> 接口与实现:

import java.util.Comparator; // Java 标准库已有 Comparator 接口,这里是模拟// 泛型接口定义
interface MyComparator<T> {int compare(T o1, T o2);
}// 实现类,比较字符串长度
class StringLengthComparator implements MyComparator<String> {@Overridepublic int compare(String s1, String s2) {// 返回负数表示 s1 < s2, 0 表示相等, 正数表示 s1 > s2return Integer.compare(s1.length(), s2.length());// 或者直接: return s1.length() - s2.length();}
}// 测试
public class ComparatorTest {public static void main(String[] args) {MyComparator<String> comparator = new StringLengthComparator();String str1 = "Java";String str2 = "Generics";int result = comparator.compare(str1, str2); // 比较 "Java" 和 "Generics" 的长度if (result < 0) {System.out.println("'" + str1 + "' is shorter than '" + str2 + "'");} else if (result > 0) {System.out.println("'" + str1 + "' is longer than '" + str2 + "'");} else {System.out.println("'" + str1 + "' and '" + str2 + "' have the same length.");}}
}

2.查找第一个匹配元素的泛型方法:

import java.util.List;
import java.util.Arrays;
import java.util.function.Predicate; // Java 8 的函数式接口public class FindFirstMatchDemo {public static <T> T findFirstMatch(List<T> list, Predicate<T> condition) {if (list == null || list.isEmpty() || condition == null) {return null;}for (T item : list) {if (condition.test(item)) { // 使用 Predicate 的 test 方法判断条件return item; // 找到第一个满足条件的,立即返回}}return null; // 遍历完都没找到}public static void main(String[] args) {List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");// 查找第一个长度大于 4 的名字String longName = findFirstMatch(names, name -> name.length() > 4);System.out.println("First name longer than 4 chars: " + longName); // CharlieList<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);// 查找第一个偶数Integer firstEven = findFirstMatch(numbers, num -> num % 2 == 0);System.out.println("First even number: " + firstEven); // 2// 查找第一个大于 10 的数 (找不到)Integer greaterThan10 = findFirstMatch(numbers, num -> num > 10);System.out.println("First number > 10: " + greaterThan10); // null}
}

⭐ 枚举应用答案 ⭐

3.Size 枚举定义:

public enum Size {SMALL,MEDIUM,LARGE
}

4.带宽度范围的 Size 枚举:

public enum Size {SMALL(0, 50),MEDIUM(51, 100),LARGE(101, Integer.MAX_VALUE); // 使用 Integer.MAX_VALUE 表示无上限private final int minWidth;private final int maxWidth;private Size(int minWidth, int maxWidth) {this.minWidth = minWidth;this.maxWidth = maxWidth;}public int getMinWidth() {return minWidth;}public int getMaxWidth() {return maxWidth;}// 可以添加一个方法来判断某个宽度属于哪个 Sizepublic static Size getFittingSize(int width) {for (Size size : values()) {if (width >= size.getMinWidth() && width <= size.getMaxWidth()) {return size;}}// 理论上,如果定义完整,总能找到一个,但可以加个默认或抛异常return SMALL; // 或者抛出 IllegalArgumentException}
}// 使用示例
public class SizeDemo {public static void main(String[] args) {Size s = Size.MEDIUM;System.out.println("Medium min width: " + s.getMinWidth()); // 51System.out.println("Medium max width: " + s.getMaxWidth()); // 100int currentWidth = 75;Size fitting = Size.getFittingSize(currentWidth);System.out.println("Width " + currentWidth + " fits in size: " + fitting); // MEDIUM}
}

⭐ 概念理解答案 ⭐

5.类型擦除 (Type Erasure):是的,它是 Java 泛型实现的一部分。为了兼容没有泛型的老代码,Java 编译器在编译后会“擦除”掉大部分泛型的类型信息(类型参数会被替换成它们的上界,通常是 Object)。这意味着在运行时,JVM 其实并不知道 ArrayList<String>ArrayList<Integer> 的区别(它们都是 ArrayList)。
影响

  • 我们不能在运行时获取泛型参数的实际类型(如 list instanceof ArrayList<String>非法的)。
  • 不能创建泛型数组(如 new T[]不允许的,通常用 new Object[] 再强转)。
  • 不能实例化类型参数(如 new T()不行的,除非有特殊约束如 Class<T>)。
  • 静态上下文中不能使用类的类型参数。

枚举继承实现

  • 不能 extends 另一个 🚫:所有的枚举隐式地继承java.lang.Enum 类,由于 Java 是单继承的,所以枚举不能再继承其他类。
  • 可以 implements 接口 ✅:这是一个非常强大的特性!枚举可以实现一个或多个接口,使得不同的枚举常量可以有不同的行为实现(通常结合接口方法在每个常量内部匿名实现,或者定义一个统一的实现)。

恭喜你又解锁了 Java 的两个重要技能!泛型枚举在实际项目中应用非常广泛,多加练习,它们会让你的代码水平更上一层楼!🚀 如果觉得这篇笔记有帮助,点赞👍、收藏⭐、关注就是对我最好的肯定!谢谢大家!💖

http://www.xdnf.cn/news/3012.html

相关文章:

  • 【KWDB 创作者计划】_深度解析KWDB存储引擎
  • vue elementui 去掉默认填充 密码input导致的默认填充
  • 大连理工大学选修课——机器学习笔记(8):Boosting及提升树
  • 2025年深圳杯-东三省联赛赛题浅析-助攻快速选题
  • 第四部分:赋予网页健壮的灵魂 —— TypeScript(中)
  • word模板填充导出-(支持word导出、pdf导出)
  • 抢先体验 | Qwen3 模型发布:基于 ZStack AIOS 平台极速体验
  • 第二章-科学计算库NumPy
  • 六.割草机技术总结--6.RTK定位精度分析
  • c++线程的创建
  • Qwen3 开源!深度对比 DeepSeek,一文选对模型
  • vue3数字秒转换为时分秒格式
  • 西游记2:天花乱坠,地涌金莲;说一会道,讲一会禅,三家(指儒、释、道)配合本如然;长生不老之术、七十二般变化之能以及筋斗云之法;你从何处而来,便回到何处去吧
  • Linux基础篇、第一章_01_3安装虚拟机手动安装部署Ubuntu22.04服务器
  • MySQL日志详解
  • 算法训练营第五天 | 454.四数相加II\ 383. 赎金信\15. 三数之和\ 18. 四数之和
  • 同一个路由器接口eth0和ppp0什么不同?
  • PCB入门指南:从电阻到常见电路的全解析
  • acwing背包问题求方案数
  • NOC科普一
  • 大模型——使用coze搭建基于DeepSeek大模型的智能体实现智能客服问答
  • 你的私域该大扫除了
  • 【记录】Python调用大模型(以Deepseek和Qwen为例)
  • 思维导图的快速生成
  • 某铝制品长棒材精轧线低压无源滤波装置改造案例
  • 智慧停车场升级难题:免布线视频桩如何破解三大核心痛点
  • 低版的spring boot 1.X接入knife4j
  • 批量修改文件名前后缀
  • 国内无法访问GitHub官网的问题解决
  • Cell Res | Stereo-seq揭示人类肝癌浸润区促进肝细胞-肿瘤细胞串扰、局部免疫抑制和肿瘤进展