十一、枚举 Enumeration(完结)
十一、枚举 Enumeration
11.1 枚举的引入
需求:用类表示季节
传统方法:声明 Season 类,有 name,temperature 两个属性,构造器,get,set 方法,创建四个对象表示四个季节
问题分析:
- 季节只能有春夏秋冬固定的值,传统方法体现不出,需要有方法,使得季节只能有春夏秋冬四种
- 并且里面的属性也不能修改
引入枚举类(Enumeration,简写Enum):
- 枚举是一组常量的集合。
- 可以这里理解:枚举属于一种特殊的类,里面只包含一组有限的特定的对象。
11.2 枚举类的两种实现方式
自定义枚举类
- 私有化构造器,防止外部再创建对象
- 不需要提供
setXxx
方法,因为枚举对象值通常为只读. - 对外暴露对象
public final static
共同修饰,实现底层优化. - 枚举对象名通常使用全部大写,常量的命名规范.
- 枚举对象根据需要,也可以有多个属性
package com.hspedu.enum_;
/**
* @author: Carl Zhang
* @create: 2021-11-23 14:20
*/
public class Enumeration01 {
public static void main(String[] args) {
//创建四个对象表示四个季节
//Season season1 = new Season("春天");
//Season season2 = new Season("夏天");
//Season season3 = new Season("秋天");
//Season season4 = new Season("冬天");
//Season season5 = new Season("今天");
//问题分析:
//季节只能有春夏秋冬固定的值,传统方法体现不出
//需要有方法,使得季节只能有春夏秋冬四种
//并且里面的属性也不能修改 -- 引入枚举类
System.out.println(Season.SPRING.toString());
System.out.println(Season.SUMMER.toString());
System.out.println(Season.AUTUMN.toString());
System.out.println(Season.WINTER.toString());
}
}
//要求创建季节 ( Season ) 对象,请设计并完成
class Season {
private String name;
//5. 枚举对象根据需要,也可以有多个属性
private String temperature;
public String getTemperature() {
return temperature;
}
//3. 对枚举对象 public + final + static 共同修饰,实现底层优化.
//4. 枚举对象名通常使用全部大写,常量的命名规范.
public static final Season SPRING = new Season("春天", "温暖");
public static final Season SUMMER = new Season("夏天", "炎热");
public static final Season AUTUMN = new Season("秋天", "萧瑟");
public static final Season WINTER = new Season("冬天", "寒冷");
//1. 私有化构造器,防止外部再创建对象
private Season(String name, String temperature) {
this.name = name;
this.temperature = temperature;
}
public String getName() {
return name;
}
//2. 不需要提供 setXxx 方法,因为枚举对象值通常为只读.
//public void setName(String name) {
// this.name = name;
//}
@Override
public String toString() {
return "季节:" + name + ", 温度:" + temperature;
}
}
使用 enum
关键字
enum
代替class
public static final Season2 SPRING = new Season2("春天", "温暖")
用SPRING("春天", "温暖")
,(即常量(参数列表)) 代替- 常量对象定义语句要写在第一行
- 多个常量对象用
,
隔开,结尾用;
- 常量对象调用的是构造器根据参数列表决定,调用无参构造可以参数列表和小括号都不写
public class Enumeration02 {
public static void main(String[] args) {
System.out.println(Season2.SPRING);
System.out.println(Season2.SUMMER);
System.out.println(Season2.AUTUMN);
System.out.println(Season2.WINTER);
System.out.println(Season2.TEST);
}
}
//要求创建季节 ( Season ) 对象,请设计并完成
//1. enum 代替class
enum Season2 {
//public static final Season2 SPRING = new Season2("春天", "温暖");
//public static final Season2 SUMMER = new Season2("夏天", "炎热");
//public static final Season2 AUTUMN = new Season2("秋天", "萧瑟");
//public static final Season2 WINTER = new Season2("冬天", "寒冷");
//用enum关键字表示枚举类:
//2. public static final Season2 SPRING = new Season2("春天", "温暖"); 用SPRING("春天", "温暖");代替
//3. 常量对象定义语句要写在第一行
//4. 多个常量对象用 , 隔开
//5. 常量对象调用的是构造器根据参数列表决定,调用无参构造可以参数列表和小括号都不写
SPRING("春天", "温暖"),
SUMMER("夏天", "炎热"),
AUTUMN("秋天", "萧瑟"),
WINTER("冬天", "寒冷"),
TEST;
private String name;
private String temperature;
private Season2(String name, String temperature) {
System.out.println("有参构造被调用");
this.name = name;
this.temperature = temperature;
}
private Season2() {
System.out.println("无参构造被调用");
}
public String getName() {
return name;
}
public String getTemperature() {
return temperature;
}
@Override
public String toString() {
return "季节:" + name + ", 温度:" + temperature;
}
}
11.3 enum 关键字的注意事项和细节
- 当我们使用 enum 关键字开发一个枚举类时,默认会继承
Enum
类, 而且是一个final
类
- 传统的
public static final Season2 SPRING = new Season2("春天", "温暖");
简化成SPRING("春天", "温暖")
, 这里必须知道,它调用的是哪个构造器. - 如果使用无参构造器创建枚举对象,则实参列表和小括号都可以省略
- 当有多个枚举对象时,使用
,
间隔,最后有一个分号结尾 - 枚举对象必须放在枚举类的行首
11.4 Enum 类常用方法
前面已经知道:使用 enum
关键字,会隐式继承 Enum
类,所以能直接调用 Enum
类的方法,看下图源码定义:
常用方法:
toString()
: Enum 类已经重写过了,返回的是当前对象 名,子类可以重写该方法,用于返回对象的属性信息name()
:返回当前对象名(常量名),子类中不能重写ordinal()
:返回当前对象的位置号,默认从 0 开始values()
:返回当前枚举类中所有的常量的对应对象数组valueOf()
:将字符串转换成枚举对象,要求字符串必须为已有的常量名,否则报异常!compareTo()
:比较两个枚举常量,比较的就是编号!
public class EnumMethod {
public static void main(String[] args) {
Season2 summer = Season2.SUMMER;
//1.toString:Enum 类已经重写过了,返回的是当前对象 名,子类可以重写该方法,用于返回对象的属性信息
System.out.println("summer.toString() = " + summer.toString()); //Season未重写时: SUMMER
//2.name:返回当前对象名(常量名),子类中不能重写
System.out.println("summer.name() = " + summer.name()); //SUMMER
//3.ordinal:返回当前对象的位置号,默认从 0 开始
System.out.println("summer.ordinal() = " + summer.ordinal()); //1
//4.values:返回当前枚举类中所有的常量的对应对象数组
Season2[] season2s = Season2.values();
for (Season2 season2 : season2s) { //增强型 for 循环
System.out.println(season2);
}
//5.valueOf:将字符串转换成枚举对象,要求字符串必须 为已有的常量名,否则报异常!
Season2 winter = Season2.valueOf("WINTER");
System.out.println("winter = " + winter);
//6.compareTo:比较两个枚举常量,比较的就是编号!
// 返回的是 winter.compare - Season.WINTER.compare
System.out.println("winter.compareTo(Season.WINTER) = " + winter.compareTo(Season2.WINTER)); //0
}
}
11.5 enum 实现接口
- 使用
enum
关键字后,就不能再继承其它类了,因为enum
会隐式继承Enum
,而 Java 是单继承机制。 - 枚举类和普通类一样,可以实现接口,语法:
enum 类名 implements 接口 1,接口 2 {}
public class EnumDetail {
public static void main(String[] args) {
Music.CLASSIC_MUSIC.playing();
}
}
interface IPlaying {
public void playing();
}
//1) 使用 enum 关键字后,就不能再继承其它类了,因为 enum 会隐式继承 Enum,而 Java 是单继承机制。
//2) 枚举类和普通类一样,可以实现接口,如下形式。 enum 类名 implements 接口 1,接口 2{}
enum Music implements IPlaying {
CLASSIC_MUSIC;
@Override
public void playing() {
System.out.println("播放经典歌曲");
}
}
11.6 注解的介绍
- 注解(
Annotation
)也被称为元数据(Metadata
),用于修饰解释 包、类、方法、属性、构造器、局部变量等数据信息。 - 和注释一样,注解不影响程序逻辑,但注解可以被编译或运行,相当于嵌入在代码中的补充信息。
- 注解的作用:
- 在
JavaSE
中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。 - 在
JavaEE
中注解占据了更重要的角色,例如进行框架的配置( 框架 = 代码 + 配置 ),代替JavaEE
旧版中所遗留的繁冗代码和XML
配置等
- 在
- 注解的使用位置:默认是
11.7 基本 Annotation 介绍
介绍:使用 Annotation
时要在其前面增加 @
符号, 并把该 Annotation
当成一个修饰符使用。用于修饰它支持的程序元素
**三个基本的 Annotation: **
@Override
: 表示某个方法是重写父类方法, 该注解只能用于方法@Deprecated
: 用于表示某个程序元素(类, 方法等)已过时@SuppressWarnings
: 抑制编译器警告
11.8 Annotation 应用案例
11.8.1 @Override
注解案例
class Father {
void speak() {
System.out.println("我是爸爸");
}
}
class Son extends Father {
//3. 看看 @Override 定义
/**
* @Target(ElementType.METHOD) //表示只能作用于方法,@Target 是修饰注解的注解,即元注解
* @Retention(RetentionPolicy.SOURCE)
* public @interface Override { //@interface 表示该类是一个注解类
* }
*/
@Override
void speak() {
System.out.println("我是儿子");
;
}
//1. 不加 @Override ,只要语法构成重写关系,也会认定为重写
//void speak() {
// System.out.println("我是儿子");
//}
//2. 加了 @Override , 编译器会对方法做重写语法的检查,如果方法语法不是重写,编译不通过
//@Override
//void speak(String name) {
// System.out.println("我是" + name);
//}
}
注意事项:
@Override
表示该方法重写父类方法(从编译层面验证),如果该方法语法不构成重写,则编译不通过- 如果不写
@Override
注解,只要语法构成重写关系,也会认定为重写 @Override
只能修饰方法,不能修饰其它类,包,属性等等- 查看
@Override
注解源码为@Target(ElementType.METHOD)
,说明只能修饰方法
- 查看
@Target
是修饰注解的注解,称为元注解,记住这个概念。
11.8.2 @Deprecated
注解案例
public class Deprecated_ {
public static void main(String[] args) {
A a = new A();
a.name = "张三";
a.speak();
}
}
//2. 查看 @Deprecated 注解类的源码
/**
* @Documented
* @Retention(RetentionPolicy.RUNTIME)
* @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE}) //3可以修饰方法,类,字段, 包, 参数 等等
* }
*/
//4. @Deprecated 可以做版本升级过渡使用
//1. @Deprecated 修饰某个元素, 表示该元素已经过时, 即不在推荐使用,但是仍然可以使用
@Deprecated
class A {
@Deprecated
String name;
@Deprecated
public void speak() {
System.out.println("我叫" + name);
}
}
注意事项:
@Deprecated
修饰某个元素, 表示该元素已经过时, 即不在推荐使用,但是仍然可以使用- 查看
@Deprecated
注解类的源码@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
,表示可以修饰方法,类,字段, 包, 参数 等等 @Deprecated
可以做版本升级过渡使用
11.8.3 @SuppressWarnings
注解的案例
@SuppressWarnings
: 抑制编译器警告
@SuppressWarnings({"rawtypes", "unchecked", "unused"}) //作用于这个类
public class SuppressWarnings_ {
//3. 关于 SuppressWarnings 作用范围是和你放置的位置相关
//比如 @SuppressWarnings 放置在 main 方法,那么抑制警告的范围就是 main
//通常我们可以放置具体的语句, 方法, 类.
//@SuppressWarnings({"rawtypes", "unchecked", "unused"})
public static void main(String[] args) {
//4. 看看 @SuppressWarnings 源码
/**
* @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE}) // 4.1作用位置
* @Retention(RetentionPolicy.SOURCE)
* public @interface SuppressWarnings {
* String[] value (); //4.2表示可以传入String类型数组的参数{"rawtypes", "unchecked", "unused"}
*}
*/
//1. 当我们不希望看到这些警告的时候,可以使用 SuppressWarnings 注解来抑制警告信息
//2. 在{""} 中,可以写入你希望抑制(不显示)警告信息
//@SuppressWarnings("rawtypes") //作用于该语句
List list = new ArrayList();
list.add("jack");
list.add("tom");
list.add("mary");
int i;
System.out.println(list.get(1));
}
public void method01() {
List list = new ArrayList();
list.add("jack");
list.add("tom");
list.add("mary");
int i;
System.out.println(list.get(1));
}
}
注意事项:
@SuppressWarnings
放在哪个元素位置,就对该元素的警告起作用@SuppressWarnings
可以修饰的程序元素为@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
- 可传入的参数,不用背,直接点击左侧的黄色提示,就可以选择(注意可以指定生成的位置)
| all | 抑制所有警告 |
| --- | --- |
| boxing | 抑制与封装/拆装作业相关的警告 |
| cast | 抑制与强制转型作业相关的警告 |
| dep-ann | 抑制与淘汰注释相关的警告 |
| deprecation | 抑制与淘汰的相关警告 |
| fallthrough | 抑制与 switch 陈述式中遗漏 break 相关的警告 |
| finally | 抑制与未传回 finally 区块相关的警告 |
| hiding | 抑制与隐藏变数的区域变数相关的警告 |
| incomplete-switch | 抑制与 switch 陈述式(enum case)中遗漏项目相关的警告 |
| javadoc | 抑制与 javadoc 相关的警告 |
| nls | 抑制与非 nls 字串文字相关的警告 |
| null | 抑制与空值分析相关的警告 |
| rawtypes | 抑制与使用 raw 类型相关的警告 |
| resource | 抑制与使用 Closeable 类型的资源相关的警告 |
| restriction | 抑制与使用不建议或禁止参照相关的警告 |
| serial | 抑制与可序列化的类别遗漏 serialVersionUID 栏位相关的警告 |
| static-access | 抑制与静态存取不正确相关的警告 |
| static-method | 抑制与可能宣告为 static 的方法相关的警告 |
| super | 抑制与置换方法相关但不含 super 呼叫的警告 |
| synthetic-access | 抑制与内部类别的存取未最佳化相关的警告 |
| sync-override | 抑制因为置换同步方法而遗漏同步化的警告 |
| unchecked | 抑制与未检查的作业相关的警告 |
| unqualified-field-access | 抑制与栏位存取不合格相关的警告 |
| unused | 抑制与未用的程式码及停用的程式码相关的警告 |
11.9 JDK 的元 Annotation(了解即可)
11.9.1 元注解基本介绍
元注解:修饰注解的注解,现在使用不多,主要是为了看懂源码
11.9.2 元注解的种类
@Retention
:指定注解的作用范围,三种SOURCE
,CLASS
,RUNTIME
@Target
:指定注解可以在哪些地方使用@Documented
:指定该注解是否会在javadoc
体现@Inherited
:子类会继承父类注解
11.9.3 @Retention
注解
介绍:
- 只能用于修饰一个
Annotation
, 用于指定该Annotation
可以保留多长时间 @Rentention
包含一个RetentionPolicy
(策略)类型的成员变量value
, 使用@Rentention
时必须为该value
成员变量赋值@Retention
的三种值(分别对应 java 程序的三个阶段):RetentionPolicy.SOURCE
:编译器使用后,直接丢弃这种策略的注解RetentionPolicy.CLASS
:编译器将把注解记录在class
文件中,当运行 Java 程序时JVM
不会保留注解,这是默认值RetentionPolicy.RUNTIME
:编译器将把注解记录在class
文件中. 当运行 Java 程序时,JVM
会保留注解,程序可以通过反射获取该注解
案例:
11.9.4 @Target
注解
介绍:用于修饰 Annotation
,用于指定被修饰的 Annotation
能用于修饰哪些程序元素 @Target
也包含一个 ElementType
数组类型的名为 value
的成员变量。
案例:见之前 Annotation 案例
11.9.5 @Documented
注解
介绍:
@Documented
:用于指定被该元Annotation
修饰的Annotation
类将被javadoc
工具提取成文档,即在生成文档时,可以看到该注解。- 注意:定义为
@Documented
的注解必须设置@Retention
值为RUNTIME
。
案例:
11.9.6 @Inherited
注解
介绍:被它修饰的 Annotation
将具有继承性.如果某个类使用了被 @Inherited
修饰的 Annotation
,则其子类将自动具有该注解(实际应用中,使用较少,了解即可)
11.10 自定义注解
11.10.1 自定义注解的格式
- **格式 :@interface **
修饰符 @interface 注解名{ //本质上是一个接口
// 常量
// 抽象方法 【注解中的抽象方法我们称为属性】,返回值类型必须是特定的,必须是无参的
// 特定的返回值类型 : String,Class类型,注解类型,枚举类型 , 基本数据类型,以及这些类型的一维数组。
类型 属性名(); // 使用的过程可以赋值
类型 属性名() default 值; // 也可以在定义的过程中给出默认值
}
- 案例:定义一个书(
Book
)的注解,包含属性书名(name
),价格(price
)(默认100
),作者(author
) , 作者要求可以有多名作者
public @interface Book {
String name();
double price() default 100;
String[] author();
}
11.10.2 注意事项和使用细节
- 自定义注解主要是可以给某些成分注入信息,注入一些数据
- 可以使用在类上,成员变量上,构造方法上,方法上 ...
- 有默认值的属性,可以不用进行赋值,也可以重新修改值
- 如果属性是数组,赋值时若有多个数据需要使用大括号括起来,逗号分隔数据 , 如果数组中只有一个元素, 那么大括号可以省略
- 如果注解中只有一个属性要赋值,而且名字是
value
。可以将value
给省略,可以直接给值
11.10.3 自定义注解的使用格式
- 格式 :
@注解名(属性=值 , 属性=值)
@Book(name="西游记" , author="吴承恩")
- 案例:创建一个
BookStore
的类,在类中定义成员变量,构造方法,成员方法 , 使用Book
注解给这些成分注入信息
package com.itheima.annotation_demo;
/*
给成员注入四大名著信息 :
西游记 --- 吴承恩
水浒传 --- 施耐庵
三国演义 --- 罗贯中
红楼梦 --- 曹雪芹 , 高鹗
*/
@Book(name = "西游记", author = {"吴承恩"})
public class BookStore {
@Book(name = "水浒传", price = 200, author = {"施耐庵"})
private String name;
@Book(name = "三国演义", author = {"罗贯中"})
public BookStore(String name) {
this.name = name;
}
@Book(name = "红楼梦", author = {"罗贯中", "高鹗"})
public void buy() {
}
}
public class Demo01 {
@A("Hello")
public void test1() {
}
@B("World")
public void test2() {
}
@B(value = "Hello", price = 10)
public void test3() {
}
}
@interface A {
String value();
}
@interface B {
String value();
int price() default 100;
}
11.10.4 解析注解,见 -- 二十、反射
11.11 练习
11.11.1 唐僧过河问题
- 有一个交通工具接口类 Vehicles ,有 work 接口
- 有 Horse 类和 Boat 类分别实现 Vehicles
- 创建交通工具工厂类,有两个方法分别获得交通工具Horse和Boat
- 有 Person 类,有 name 和 Vehicles 属性,在构造器中为两个属性赋值
- 实例化 Person 对象“唐僧”,要求一般情况下用 Horse 作为交通工具,遇到大河时用 Boat 作为交通工具
- 扩展 -- 遇到火焰山时坐飞机 Plane
//1.有一个交通工具接口类Vehicles,work方法
public interface Vehicles {
void work();
}
//2.有Horse类和Boat类分别实现Vehicles
public class Horse implements Vehicles {
@Override
public void work() {
System.out.println("在路上骑马");
}
}
public class Boat implements Vehicles {
@Override
public void work() {
System.out.println("在水上乘舟");
}
}
package com.hspedu.annotation_;
//3.创建交通工具工厂类,有两个方法分别获得交通工具Horse和Boat
public class Factory {
//问题:走路的时候,马可以一直是同一条不用换,原来的方法每次都要创建对象
//优化:使用饿汉式
private static Horse horse = new Horse();
private Factory() {}
public static Horse getHorse() {
return horse;
}
////获得Horse对象
//public static Horse getHorseInstance() {
// return new Horse();
//}
//获得Boat对象
public static Boat getBoat() {
return new Boat();
}
//使用匿名内部类创建飞机 -- 会出现多次使用飞机类情况,此处不适用匿名内部类
//public static Vehicles getPlane() {
// return new Vehicles() {
// @Override
// public void work() {
// System.out.println("飞机在天上飞");
// }
// };
//}
public static Plane getPlane() {
return new Plane();
}
}
package com.hspedu.annotation_;
//4.有Person类,有name和Vehicles属性,在构造器中为两个属性赋值
public class Person {
private String name;
private Vehicles vehicle;
public Person(String name, Vehicles vehicle) {
this.name = name;
this.vehicle = vehicle;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Vehicles getVehicle() {
return vehicle;
}
public void setVehicle(Vehicles vehicle) {
this.vehicle = vehicle;
}
//要求一般情况下用Horse作为交通工具,遇到大河时用Boat作为交通工具
//用 OOP 思想,创建方法解决问题
public void takeBoat() {
//因为创建对象时,分配了一个交通工具,所以这里要判断,该交通工具是否已经分配了
if (!(vehicle instanceof Boat))
vehicle = Factory.getBoat();
vehicle.work();
}
public void Ridding() {
//因为创建对象时,分配了一个交通工具,所以这里要判断,该交通工具是否已经分配了
if (!(vehicle instanceof Horse))
vehicle = Factory.getHorse();
vehicle.work();
}
//扩展:遇到火焰山时候 坐飞机
//使用匿名内部类的方式 -- 实际开发中,飞机类可能有多次使用情况,此处不适用匿名内部类
//创建新的飞机类,工厂创建产生飞机的方法,再通过工厂产生飞机
public void fly() {
//问题,如果一开始就是分配的飞机
// 解决:因为飞机使用的也是同一架,用 == 判断
//vehicle = Factory.getPlane();
//if (vehicle != Factory.getPlane())
// vehicle = Factory.getPlane();
if (!(vehicle instanceof Plane))
vehicle = Factory.getPlane();
vehicle.work();
}
}
public class Homework06 {
public static void main(String[] args) {
//要点:
// 1. 用工厂类实现生产对象,将马设计成单例
// 2. 用方法实现坐船,坐车,坐飞机的功能
// 3. 用instanceof 判断对象是否已存在,从而避免对象重复创建
//|5.实例化Person对象“唐僧”,要求一般情况下用Horse作为交通工具,遇到大河时用Boat作为交通工具
Person person = new Person("唐僧", Factory.getPlane());
//平时骑马
person.Ridding();
//遇到水坐船
person.takeBoat();
//遇到火焰山坐飞机
person.fly();
person.fly();
person.fly();
person.fly();
}
}
//扩展 -- 遇到火焰山坐飞机,创建新的飞机实体类
public class Plane implements Vehicles {
@Override
public void work() {
System.out.println("飞机在天上飞");
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南