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 为 String
        stringBox.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 的类型就是 E
            System.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 是 Integer

        System.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.RED
                System.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),   // 调用构造方法传入 true
    TUESDAY(true),
    WEDNESDAY(true),
    THURSDAY(true),
    FRIDAY(true),
    SATURDAY(false),  // 调用构造方法传入 false
    SUNDAY(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()); // false
        today.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> {
    @Override
    public int compare(String s1, String s2) {
        // 返回负数表示 s1 < s2, 0 表示相等, 正数表示 s1 > s2
        return 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); // Charlie

        List<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;
    }

    // 可以添加一个方法来判断某个宽度属于哪个 Size
    public 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()); // 51
        System.out.println("Medium max width: " + s.getMaxWidth()); // 100

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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值