JavaSE基础——第九章 枚举类注解
本专题主要为观看韩顺平老师《零基础30天学会Java》课程笔记,同时也会阅读其他书籍、学习其他视频课程进行学习笔记总结。如有雷同,不是巧合!
一、枚举类(Enum)
1.枚举类出现的原因
(1)替代传统常量的缺陷
在枚举出现之前,Java 使用 public static final
定义常量(如 int
或 String
类型),但这种方式存在明显问题:
-
类型不安全:常量本质上是普通变量,编译器无法检查值的合法性。
public static final int SPRING = 1; public static final int SUMMER = 2;void setSeason(int season) { ... }// 问题:可以传入任意 int 值(如 99),编译器不会报错 setSeason(99);
但是可以将构造器私有化,提供
get
方法,但不提供set
方法。类似于饿汉式单例设计模式。例如public static final Season SPRINT = new Season(”春天”, “温暖”);
-
可读性差:常量是分散的,缺乏逻辑关联。
-
功能单一:常量只是值,无法附加行为或属性。
枚举的解决方案:
将常量定义为枚举类型,编译器会强制类型检查,非法值无法通过编译:
enum Season { SPRING, SUMMER; }
void setSeason(Season season) { ... }setSeason(Season.SPRING); // 正确
setSeason(99); // 编译错误!
(2)需要更强大的常量表达方式
传统常量无法表达复杂场景,例如:
- 常量需要关联额外属性(如枚举值对应描述、编号等)。
- 常量需要定义特定行为(如不同枚举值执行不同逻辑)。
枚举的解决方案:
枚举可以像类一样定义字段、方法和构造函数:
enum Planet {EARTH(5.97e24, 6371),MARS(6.39e23, 3389);private final double mass; // 质量private final double radius; // 半径Planet(double mass, double radius) {this.mass = mass;this.radius = radius;}public double surfaceGravity() {return 6.67e-11 * mass / (radius * radius);}
}
(3)替代枚举模式的实现
在枚举出现前,开发者通过以下方式模拟枚举:
-
常量类(类型不安全,如
public static final
)。 -
类型安全枚举模式(Joshua Bloch 在《Effective Java》中提出),需要大量模板代码:
public class Season {private final String name;private Season(String name) { this.name = name; }public static final Season SPRING = new Season("Spring");// 其他季节... }
(4)支持面向对象特性
枚举本质上是类(继承自 java.lang.Enum
),因此可以:
-
实现接口。
-
覆盖方法。
-
定义抽象方法并由不同枚举值实现多态:
enum Operation {ADD { double apply(double x, double y) { return x + y; } },SUBTRACT { double apply(double x, double y) { return x - y; } };abstract double apply(double x, double y); }
(5)线程安全与单例优化
枚举天然适合实现单例模式:
- 实例由 JVM 在加载枚举类时创建,保证线程安全。
- 防止反射攻击(传统单例可能被反射破坏)。
enum Singleton {INSTANCE;public void doSomething() { ... }
}
2.枚举介绍
枚举(Enum)是 Java 5 引入的一种特殊数据类型,用于定义一组固定的常量。枚举类比传统的常量定义(如 public static final
)更安全、更强大。
基本语法
访问修饰符 enum 枚举类名 {常量名1(实参列表), 常量名1(实参列表), ...; // 一定要写在最前面成员变量;构造函数;成员方法;
}使用 枚举类名.常量名 访问
public enum Season {SPRING, SUMMER, AUTUMN, WINTER;
}
- 枚举常量本身隐式是
public static final
,无需也不能显式添加修饰符。 - 构造器必须是
private
(可省略,默认就是private
),因为枚举常量必须在枚举类内部实例化,外部无法通过new
创建。- 枚举的本质是固定数量的实例(如
Color.RED
、Color.GREEN
),不允许运行时动态创建。 - 安全性:防止外部通过反射或其他方式破坏枚举的单例特性。
- 如果尝试声明非
private
构造器,编译器会报错。
- 枚举的本质是固定数量的实例(如
- 安全性:防止外部通过反射或其他方式破坏枚举的单例特性
- 使用无参构造器创建枚举对象时,实参列表和小括号都可以省略
- 有多个枚举对象时,使用
,
间隔,使用;
结尾 - 枚举对象必须放在枚举类的行首
枚举的特点
- 类型安全:编译器会检查类型,避免传入无效值
- 自带方法:枚举类自动继承
java.lang.Enum
,提供了一些有用方法【不能再继承其他类】 - 可添加自定义属性和方法:枚举可以像普通类一样定义字段和方法
- 可实现接口:枚举可以实现一个或多个接口
- 可用于switch语句:枚举类型可以直接用于switch-case
枚举的常用方法
所有枚举类型都隐式继承 Enum
类,因此有以下方法:
name()
:返回枚举常量的名称(字符串)ordinal()
:返回枚举常量的序数(声明时的位置,从0开始)values()
:返回包含所有枚举常量的数组(编译器生成)valueOf(String name)
:根据名称返回对应的枚举常量
高级用法
(1)带参数的枚举
public enum Planet {MERCURY(3.303e+23, 2.4397e6),VENUS(4.869e+24, 6.0518e6),EARTH(5.976e+24, 6.37814e6);private final double mass; // 质量(kg)private final double radius; // 半径(m)Planet(double mass, double radius) {this.mass = mass;this.radius = radius;}public double surfaceGravity() {return 6.67300E-11 * mass / (radius * radius);}
}
(2)枚举实现接口
public interface Command {void execute();
}public enum LogLevel implements Command {ERROR {public void execute() {System.err.println("Error logging");}},WARN {public void execute() {System.out.println("Warning logging");}};
}
(3)枚举单例模式
public enum Singleton {INSTANCE;public void doSomething() {System.out.println("Singleton operation");}
}
使用示例
public class EnumExample {public static void main(String[] args) {// 遍历枚举for (Season s : Season.values()) {System.out.println(s);}// switch使用Season current = Season.SUMMER;switch (current) {case SPRING:System.out.println("春暖花开");break;case SUMMER:System.out.println("夏日炎炎");break;// 其他case...}// 调用枚举方法System.out.println("Earth gravity = " + Planet.EARTH.surfaceGravity());}
}
枚举与设计模式
枚举是实现以下设计模式的优秀选择:
- 单例模式(Singleton)
- 策略模式(Strategy)
- 状态模式(State)
- 命令模式(Command)
Enum
类的常用方法
1. 核心方法
name()
- 作用:返回枚举常量的名称(声明时的名字)
- 特点:是
final
方法,不能重写 - 示例:
enum Color { RED, GREEN, BLUE }public class Main {public static void main(String[] args) {System.out.println(Color.RED.name()); // 输出 "RED"}
}
ordinal()
- 作用:返回枚举常量的序数(声明时的位置,从0开始)
- 特点:通常不建议依赖此值,因为改变枚举顺序会影响结果
- 示例:
System.out.println(Color.RED.ordinal()); // 输出 0
System.out.println(Color.GREEN.ordinal()); // 输出 1
values()
- 作用:返回包含所有枚举常量的数组(按声明顺序)
- 特点:编译器自动生成的方法,不在 Enum 类中
- 示例:
for (Color c : Color.values()) {System.out.println(c);
}
// 输出:
// RED
// GREEN
// BLUE
valueOf(String name)
- 作用:将字符串转换成枚举对象,根据名称返回对应的枚举常量,要求字符串必须是已有的常量名。
- 特点:区分大小写,如果名称不存在会抛出 IllegalArgumentException
- 示例:
Color red = Color.valueOf("RED"); // 返回 Color.RED
2. 实用方法
toString()
- 作用:默认返回与 name() 相同的结果【
return name;
】,但可以重写 - 示例:
enum Color {RED("红色"), GREEN("绿色"), BLUE("蓝色");private String chineseName;Color(String name) {this.chineseName = name;}@Overridepublic String toString() {return this.chineseName;}
}System.out.println(Color.RED); // 输出 "红色"
compareTo(E o)
- 作用:比较两个枚举常量的顺序(基于 ordinal 值)
- 返回值:负数(小于)、0(等于)、正数(大于)
- 示例:
Color.RED.compareTo(Color.BLUE); // 返回负数(RED在BLUE前面)
equals(Object other)
- 作用:比较两个枚举常量是否相同
- 特点:可以直接使用 == 比较,因为枚举常量是单例
- 示例:
Color.RED.equals(Color.RED); // true
Color.RED == Color.RED; // true
3. 高级用法
getDeclaringClass()
- 作用:返回枚举常量的枚举类型 Class 对象
- 示例:
Class<Color> colorClass = Color.RED.getDeclaringClass();
4.实现接口方法
枚举可以实现接口,并为每个常量提供不同的实现:
interface Operation {int apply(int a, int b);
}enum BasicOperation implements Operation {PLUS {public int apply(int a, int b) { return a + b; }},MINUS {public int apply(int a, int b) { return a - b; }};
}System.out.println(BasicOperation.PLUS.apply(2, 3)); // 输出 5
5. 使用建议
- 优先使用 name() 而非 toString():除非需要自定义输出
- 避免依赖 ordinal():因为枚举顺序可能会变化
- 使用 == 比较枚举:比 equals() 更高效
- 考虑实现接口:使枚举更具灵活性
- 利用 values() 进行遍历:比反射更安全高效
5. 完整示例
enum Planet {MERCURY(3.303e+23, 2.4397e6),VENUS(4.869e+24, 6.0518e6),EARTH(5.976e+24, 6.37814e6);private final double mass; // 质量(kg)private final double radius; // 半径(m)Planet(double mass, double radius) {this.mass = mass;this.radius = radius;}public double surfaceGravity() {return 6.67300E-11 * mass / (radius * radius);}public double surfaceWeight(double otherMass) {return otherMass * surfaceGravity();}
}public class Main {public static void main(String[] args) {// 遍历所有行星for (Planet p : Planet.values()) {System.out.printf("%s: %.2f m/s²%n", p, p.surfaceGravity());}// 根据名称获取枚举Planet earth = Planet.valueOf("EARTH");System.out.println("Earth's gravity: " + earth.surfaceGravity());}
}
二、注解(Annotation)
注解(Annotation)是Java 5引入的一种**元数据(Metadata)**形式,用于修饰包、类、方法、属性、构造器、局部变量等,它提供了一种向代码添加结构化信息的方式,这些信息可以被编译器、开发工具或运行时环境读取和使用。
- 和注释一样。不影响程序逻辑;但注解可以被编译或运行,相当于嵌入在代码中的补充信息
(一)注解的基本概念
注解是一种特殊的接口,以@
符号为前缀,可以附加在包、类、方法、字段、参数等代码元素上,用于提供额外的信息。
基本语法
@AnnotationName
public class MyClass {@AnnotationNamepublic void myMethod() {// ...}
}
(二)Java内置的常用注解
实际上还是一个类,可以定义属性和方法。
1. 编译时注解
-
@Override
- 表示方法覆盖了父类方法,只能用于注解方法 -
@Deprecated
- 表示方法、类、字段、包、参数等已过时,不再推荐使用,但仍可以用 -
@SuppressWarnings({""})
-写入希望抑制编译器的警告类型
2. 元注解(用于定义注解的注解)
-
@Target
- 指定注解可以应用的目标,比如@Target(ElementType.METHOD)
表示只能修饰方法 -
@Retention
- 指定注解的保留策略,即注解的作用范围【3种:SOURCE(源码), CLASS(类),RUNTIME(运行时)】 -
@Documented
- 表示注解应包含在Javadoc中 -
@Inherited
- 表示注解可以被子类继承 -
@Repeatable
(Java 8+) - 表示注解可以重复应用于同一元素
(三)自定义注解
可以创建自己的注解类型:
1. 定义注解
import java.lang.annotation.*;@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {String value() default "default value";int count() default 1;
}
@interface
表示一个注解类
2. 使用注解
public class AnnotationTest {@MyAnnotation(value = "test", count = 5)public void testMethod() {// ...}
}
(四)注解的保留策略
通过@Retention
指定注解的保留时间:
RetentionPolicy.SOURCE
- 仅保留在源码中,编译时丢弃RetentionPolicy.CLASS
- 保留在class文件中,但运行时不可见(默认)RetentionPolicy.RUNTIME
- 保留到运行时,可以通过反射读取
(五)注解的目标类型
通过@Target
指定注解可以应用的位置:
@Target({ElementType.TYPE, // 类、接口、枚举ElementType.FIELD, // 字段ElementType.METHOD, // 方法ElementType.PARAMETER, // 参数ElementType.CONSTRUCTOR, // 构造器ElementType.LOCAL_VARIABLE, // 局部变量ElementType.ANNOTATION_TYPE,// 注解ElementType.PACKAGE, // 包ElementType.TYPE_PARAMETER, // 类型参数(Java 8+)ElementType.TYPE_USE // 类型使用(Java 8+)
})
(六)注解的处理
1. 编译时处理
通过注解处理器(Annotation Processor)在编译时处理注解。
2. 运行时处理
通过反射API在运行时处理注解:
Method method = AnnotationTest.class.getMethod("testMethod");
if (method.isAnnotationPresent(MyAnnotation.class)) {MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);System.out.println("Value: " + annotation.value());System.out.println("Count: " + annotation.count());
}
(七)Java 8对注解的增强
1. 可重复注解
@Repeatable(Authorities.class)
public @interface Authority {String role();
}public @interface Authorities {Authority[] value();
}@Authority(role="admin")
@Authority(role="manager")
public class User {// ...
}
2. 类型注解
可以在任何使用类型的地方添加注解:
List<@NonNull String> list = new ArrayList<>();
(八)注解的常见应用场景
- 代码生成:如Lombok库
- 配置替代:如Spring的
@Component
、@Autowired
- 测试框架:如JUnit的
@Test
- 文档生成:如Swagger的API文档
- 验证框架:如Bean Validation的
@NotNull