1、前置知识
1、枚举类是一个特殊的类,,它一样有自己的成员变量、方法,可以实现一个或多个接口,也可以定义自己的构造器。
2、一个 java 源文件最多只能定义一个 public 访问权限的 枚举类。且该 java 源文件也必须和该枚举的类名相同
3、枚举类默认继承了 java.lang.Enum 类,而不是 Object 类,所以枚举类不能显示继承其他父类。其中
java.lang.Enum 类实现了 java.lang.Serializable 和 java.lang.Comparable 两个接口。
4、使用 enum 定义、非抽象的枚举类会默认使用 final 修饰
5、枚举类的构造器只能用 private 访问控制符,因为子类构造器总要调用父类构造器一次,所以枚举类不能派生子类
6、在枚举类中列出枚举值时,实际上就是调用构造器创建枚举类对象。只是这里无需使用 new 关键字。也无需显示调用
构造器。前面列出枚举值时无需传入参数(调用无参的构造方法),也可以传入参数(有参的构造方法)
7、枚举类的所有实例必须在枚举类的第一行显示列出,否则这个枚举类永远不能产生实例。列出这些实例时系统会自动
添加 public static final 修饰,无需自动添加
package main.enumkey;
/**
* --------------------------------------------
* ClassName: SeasonEnum
* CreateBy: IntelliJ IDEA
* Author: 醉瑾
* Date: 2022-04-01
* Description :
* --------------------------------------------
*/
/*
* 1、枚举类是一个特殊的类,,它一样有自己的成员变量、方法,可以实现一个或多个接口,也可以定义自己的构造器。
* 2、一个 java 源文件最多只能定义一个 public 访问权限的 枚举类。且该 java 源文件也必须和该枚举的类名相同
* 3、枚举类默认继承了 java.lang.Enum 类,而不是 Object 类,所以枚举类不能显示继承其他父类。其中
* java.lang.Enum 类实现了 java.lang.Serializable 和 java.lang.Comparable 两个接口。
* 4、使用 enum 定义、非抽象的枚举类会默认使用 final 修饰
* 5、枚举类的构造器只能用 private 访问控制符,因为子类构造器总要调用父类构造器一次,所以枚举类不能派生子类
* 6、在枚举类中列出枚举值时,实际上就是调用构造器创建枚举类对象。只是这里无需使用 new 关键字。也无需显示调用
* 构造器。前面列出枚举值时无需传入参数(调用无参的构造方法),也可以传入参数(有参的构造方法)
* 7、枚举类的所有实例必须在枚举类的第一行显示列出,否则这个枚举类永远不能产生实例。列出这些实例时系统会自动
* 添加 public static final 修饰,无需自动添加
* */
public enum SeasonEnum {
// 列出这些枚举值 之前不能有任何语句 见 7
// 在枚举类中列出枚举值时,实际上就是调用构造器创建枚举类对象 见 6
FALL,
SPRING,
SUMMER,
WINTER("冬天"); // 本句后面如果没有其他代码语句可以不加 分号
public String season;
SeasonEnum() {
// 无参构造方法
}
SeasonEnum(String season) {
// 有参的构造方法
this.season = season;
}
public String getSeason() {
return season;
}
public void setSeason() {
switch (this) { // 此处 this 为 某个实例对象
case SPRING:
this.season = "春天";
break;
case SUMMER:
this.season = "夏天";
break;
case FALL:
this.season = "秋天";
break;
}
}
}
2、测试
package main.enumkey;
/**
* --------------------------------------------
* ClassName: EnumTest
* CreateBy: IntelliJ IDEA
* Author: 醉瑾
* Date: 2022-04-01
* Description :
* --------------------------------------------
*/
public class EnumTest {
public static void main(String[] args) {
test1();
test2();
test3();
}
static void test1() {
System.out.println(SeasonEnum.WINTER.getSeason()); // 有参构造,冬天
System.out.println(SeasonEnum.SPRING.getSeason()); // 无参构造,所以 null
System.out.println("-------------------test1-END------------------");
}
static void test2() {
/*
* 因为所有的枚举类都继承了 java.lang.Enum 类,所以枚举类可以直接使用 Enum 类中所包含的的方法
* Enum 类包含方法:
* 1、int compareTo():
* 该方法用于与指定枚举对象比较顺序, 同一个枚举实例只能
* 与相同类型的枚举实例进行比较。如果该 枚举对象位于指定枚举对象之后,
* 则返回正整数;如果该枚举 对象位于指定枚举对象之前,则返回负整数,否则返回零
* 2、String name():
* 返回此枚举实例的名称,这个名称就是定义 枚举类时列出的所有枚举值之一。
* 与此方法相比,大多数程序 员应该优先考虑使用toString()方法,
* 因为toString()方法返 回更加用户友好的名称。
* 3、int ordinal()
* int ordinal():返回枚举值在枚举类中的索引值(就是枚举 值在枚举声明中的位置,
* 第一个枚举值的索引值为零)。
* 4、String toString():
* 返回枚举常量的名称,与name方法相 似,但toString()方法更常用。
* 5、public static<T extends Enum<T>> T valueOf(Class<T> enumType,String name):
* 这是一个静态方法,用于返回指定 枚举类中指定名称的枚举值。
* 名称必须与在该枚举类中声明枚 举值时所用的标识符完全匹配,
* 不允许使用额外的空白字符
* */
// 枚举类会有一个默认的 values() 方法,返回该枚举类的所有实例
for (SeasonEnum seasonEnum : SeasonEnum.values()) {
// 每一个seasonEnum 都是一个实例对象,都能访问枚举类中的成员变量和方法
System.out.println(seasonEnum);
System.out.println(seasonEnum.valueOf(SeasonEnum.class, seasonEnum.name()));
System.out.println(seasonEnum.compareTo(SeasonEnum.SPRING));
System.out.println(seasonEnum.ordinal());
System.out.println(seasonEnum.toString());
System.out.println("===================");
}
System.out.println("-------------------test2-END------------------");
}
static void test3() {
SeasonEnum seasonEnum = Enum.valueOf(SeasonEnum.class, "SPRING");
SeasonEnum seasonEnum1 = Enum.valueOf(SeasonEnum.class, "SPRING");
System.out.println("修改前: " + seasonEnum.season);
seasonEnum.season = "春秋季";
seasonEnum1.season = "冬夏季";
System.out.println("seasonEnum-被修改后: " + seasonEnum.season);
System.out.println("seasonEnum1-被修改后: " + seasonEnum1.season); // 被连带修改了,出现连带修改,非常混乱
System.out.println("-------------------test3-END------------------");
/*
* 在上边的枚举类中 season 字段被 public 修饰
* 当枚举实例直接调用 season 字段并修改,下个枚举实例获取到的可能就是被修改到的值
* (此处的枚举实例指的是由同一个 枚举值 创建的,如都是由 SPRING 创建的)
*
* 解决办法:
* 将 season 字段用 private 修饰,禁止实例直接调用,利用 setter 方法选择赋值,同时设定的值不可自行修改。
* 其实一般直接在列出枚举值时直接进行 构造函数的初始化,不需要 设置 setter 方法,同时设定变量被 private final 修饰
* */
}
}
test1
test2
test3
3、改进枚举类
package main.enumkey;
/**
* --------------------------------------------
* ClassName: SeasonEnum1
* CreateBy: IntelliJ IDEA
* Author: 醉瑾
* Date: 2022-04-01
* Description :
* --------------------------------------------
*/
public enum SeasonEnum1 {
// 列出这些枚举值 之前不能有任何语句 见 7
// 在枚举类中列出枚举值时,实际上就是调用构造器创建枚举类对象 见 6
FALL("秋天"),
SPRING("春天"),
SUMMER("夏天"),
WINTER("冬天"); // 本句后面如果没有其他代码语句可以不加 分号
public final String season;
SeasonEnum1(String season) {
// 有参的构造方法
this.season = season;
}
public String getSeason() {
return season;
}
}
// 一个Java源文件中最多只能定义一 个public访问权限的枚举类,
// 且该Java源文件也必须和该枚举类的类 名相同。
// 此处不能用 public 修饰,道理和 普通java文件类里面不能有多个 public 修饰的类一样
enum test {
}
4、实现接口的枚举类
GanderDec接口
public interface GanderDec {
void ganderInfo();
}
Gander枚举类实现GanderDec接口
package main.enumkey;
/**
* --------------------------------------------
* ClassName: Gander
* CreateBy: IntelliJ IDEA
* Author: 醉瑾
* Date: 2022-04-01
* Description : 实现接口的枚举类
* --------------------------------------------
*/
public enum Gander implements GanderDec {
MALE("男"), FEMALE("女");
private final String gander;
Gander(String gander) {
this.gander = gander;
}
@Override
public void ganderInfo() {
System.out.println("我的性别是:" + gander);
}
}
测试
static void test4() {
System.out.println(Gander.MALE);
System.out.println(Gander.FEMALE);
Gander.MALE.ganderInfo();
Gander.FEMALE.ganderInfo();
System.out.println("-------------------test4-END------------------");
}
如果由枚举类来实现接口里的方法,则每个枚举值在调用该方法
时都有相同的行为方式(因为方法体完全一样)。如果需要每个枚举 值在调用该方法时呈现出不同的行为方式,则可以让每个枚举值分别 来实现该方法,每个枚举值提供不同的实现方式,从而让不同的枚举 值调用该方法时具有不同的行为方式
第二种重写方法(分别重写)
package main.enumkey;
/**
* --------------------------------------------
* ClassName: Gander
* CreateBy: IntelliJ IDEA
* Author: 醉瑾
* Date: 2022-04-01
* Description : 实现接口的枚举类
* --------------------------------------------
*/
public enum Gander implements GanderDec {
/*
*如果由枚举类来实现接口里的方法,则每个枚举值在调用该方法
* 时都有相同的行为方式(因为方法体完全一样)。如果需要每个枚举
* 值在调用该方法时呈现出不同的行为方式,则可以让每个枚举值分别
* 来实现该方法,每个枚举值提供不同的实现方式,从而让不同的枚举
* 值调用该方法时具有不同的行为方式。
* */
MALE("男") {
@Override
public void ganderInfo() {
System.out.println("黑马王子");
}
},
FEMALE("女") {
@Override
public void ganderInfo() {
System.out.println("白雪公主");
}
};
private final String gander;
Gander(String gander) {
this.gander = gander;
}
/*@Override
public void ganderInfo() {
System.out.println("我的性别是:" + gander);
}*/
}
当创建MALE和FEMALE两个枚举值时,后面又紧跟了一对花括号,这对花括号里包含了一个 ganderInfo()方法定义。如果读者还记得匿名内部类语法的话,则可能对这 样的语法有点印象了,花括号部分实际上就是一个类体部分,在这种 情况下,当创建MALE、FEMALE枚举值时,并不是直接创建Gender枚举 类的实例,而是相当于创建Gender的匿名子类的实例。因为括 号部分实际上是匿名内部类的类体部分,所以这个部分的代码语法与匿名内部类语法大致相似,只是它依然是枚举类的匿名内 部子类。
枚举类不是用final修饰了吗?怎么还能派生子类呢?
并不是所有的枚举类都使用了final修饰!非抽象的枚举类才默 认使用final修饰。对于一个抽象的枚举类而言——只要它包含了抽
象方法,它就是抽象枚举类,系统会默认使用abstract修饰,而不是 使用final修饰。
拓展:
抽象方法:
java中的抽象方法就是以abstract修饰的方法,这种方法只声明返回的数据类型、方法名称和所需的参数,没有方法体,也就是说抽象方法只需要声明而不需要实现。
抽象方法与抽象类:
当一个方法为抽象方法时,意味着这个方法应该被子类的方法所重写,否则其子类的该方法仍然是abstract的,这个子类由于继承父类,拥有抽象方法,因此它也是抽象类,即声明为abstract。abstract抽象类不能用new实例化对象,abstract方法只允许声明不能实现。如果一个类中含有abstract方法,那么这个类必须用abstract来修饰,当然abstract类也可以没有abstract方法。 一个抽象类里面没有一个抽象方法可用来禁止产生这种类的对象。(摘自百度文库:java抽象类和方法,作者:余书慧先生)
抽象方法与接口:
在interface中所有的方法都是public abstract的,即使你没有申明它是public abstract的。在interface中所有的数据成员都是public static final的,即使你没有申明.但不能是blank final 在编译时候确定的。在Java,interface的地位和class是一样的。实现interface的类,其interface中所有的方法必须被“实现”,否则这个类成为一个抽象类。Interface可以从多个interface得到继承,但是不能继承类。一个类可以实现多个interface
编 译 上 面 的 程 序 , 可 以 看 到 生 成 了 Gender.class 、 Gender$1.class和Gender$2.class三个文件,这样的三个class文件正 好证明了上面的结论:MALE和FEMALE实际上是Gender匿名子类的实 例,而不是Gender类的实例。当调用MALE和FEMALE两个枚举值的方法 时,就会看到两个枚举值的方法表现不同的行为方式
上边是从实现某个接口,从而包含抽象方法,接下来定义一个包含抽象方法的枚举类
package main.enumkey;
/**
* --------------------------------------------
* ClassName: Operation
* CreateBy: IntelliJ IDEA
* Author: 醉瑾
* Date: 2022-04-01
* Description : 包含抽象方法的枚举类
* --------------------------------------------
*/
public enum Operation {
PLUS {
// 加
public double eval(double x, double y) {
return x + y;
}
},
MINUS {
// 减
public double eval(double x, double y) {
return x - y;
}
},
TIMES {
// 乘
public double eval(double x, double y) {
return x * y;
}
},
DIVIDE {
// 除
public double eval(double x, double y) {
return x / y;
}
};
// 为枚举类定义一个抽象方法,不定义此方法不会报错,但是调用Operation.PLUS.eval时会报找不到 eval 方法错误
public abstract double eval(double x, double y);
public static void main(String[] args) {
System.out.println(Operation.PLUS.eval(2, 3));
System.out.println(Operation.MINUS.eval(2, 3));
System.out.println(Operation.TIMES.eval(2, 3));
System.out.println(Operation.DIVIDE.eval(2, 3));
}
}
编译上面程序会生成5个class文件,其实Operation对应一个 class文件,它的4个匿名内部子类分别各对应一个class文件。
枚举类里定义抽象方法时不能使用abstract关键字将枚举类定义成抽象类(因为系统自动会为它添加abstract关键字),但因为枚举 类需要显式创建枚举值,而不是作为父类,所以定义每个枚举值时必 须为抽象方法提供实现,否则将出现编译错误。