作者 :孙玉昌,昵称【一一哥 】,另外【壹壹哥】也是我哦
千锋教育高级教研员、CSDN博客专家、万粉博主、阿里云专家博主、掘金优质作者
前言
我们在开发时,有时会遇到列举、请点或计数的情况,比如我们想匹配性别里的"男"、"女",或者列举今天是周一、周二、周三......这种表示"有限个数"的情况,我们该怎么实现呢?根据现在的知识储备,我们可以使用以下技术:
- 使用常量:使用final关键字定义public static final常量,这些常量值可以被其他类直接引用,但这种方式不能保证唯一性。
- 使用接口:使用接口来定义有限的个数,但需要在实现类中逐个实现,较为繁琐。
但是以上技术都存在一些缺陷,所以今天壹哥就通过一篇文章,给大家介绍一个新的小知识--枚举类!通过枚举,实现以上效果会更方便。
------------------------------前戏已做完,精彩即开始----------------------------
全文大约【4500】 字,不说废话,只讲可以让你学到技术、明白原理的纯干货!本文带有丰富的案例及配图视频,让你更好地理解和运用文中的技术概念,并可以给你带来具有足够启迪的思考......
配套开源项目资料
Github: github.com/SunLtd/Lear...
Gitee: gitee.com/sunyiyi/Lea...
一. 枚举简介
1. 概念
首先我们来了解一下"枚举 "这个词的含义。"枚举"是列举、清点或计数的意思,而在计算机里面,枚举通常是表示有限个数的常量集合,例如颜色、性别、星期几、月份等。
在Java中,枚举(enum)其实是一个特殊的类,它是一种表示有限个数的常量集合。枚举类是在Java 5中引入的新特性,它给我们提供了一种更加规范和安全的方式来定义常量。
2. 特点
枚举作为一个类,既符合一般类的特性,也具有自身的特点,如下:
- 枚举是有限个数的常量集合;
- 枚举类只能使用 public 或 default 访问修饰符,不能使用 private 或 protected修饰符;
- 枚举类的构造方法必须是 private 或 default 修饰,不能使用 public 或 protected;
- 枚举类默认是按照单例模式进行的实现,枚举常量只会被创建一次,可以保证该值的唯一性;
- 枚举类也可以实现接口,实现与普通类相同的功能。
3. 枚举类与普通类的区别
但枚举作为一种特殊的类,本身具有一定的特殊性,它与普通类之间具有如下区别:
- 枚举类只能使用 public 或 default 访问修饰符,不能使用 private 或 protected,因为枚举的常量要在其他类中访问;
- 枚举类的构造方法必须是 private 或 default 访问修饰符,不能使用 public 或 protected,因为枚举常量不能通过 new 关键字创建;
- 枚举类遵循单例模式,枚举常量只会被创建一次,因此可以保证唯一性。
4. 使用场景
既然枚举这么特殊,我们又该在哪些场景下使用呢?大家可以看看壹哥列举出的以下几种场景:
- 可以表示有限的、预定义的值集合,如一周的星期、一年的季节、性别等;
- 可以作为方法的参数或返回值类型,使代码更加清晰和类型安全;
- 可以作为单例模式的实现方式,以保证枚举常量的唯一性;
- 可以作为工厂模式的实现方式,以根据不同的枚举常量返回不同的实例对象;
- 可以作为状态机的实现方式,以简化状态转换过程的代码实现。
了解了枚举的基本情况之后,我们就来看看枚举该怎么使用吧。
二. 基本使用
1. 基本语法
我们在声明枚举时必须使用 enum关键字,然后可以定义枚举的名称、可访问性、基础类型和成员等。一个简单的枚举声明语法如下:
java
修饰符 enum 枚举名称:enum-base {
enum-body,...
}
修饰符主要包括 public、private 和 internal;enum-base表示基础类型;enum-body表示枚举中的成员,它是枚举类型中的常数。大家要注意,任意两个枚举成员的名称都不能相同,且它的常数值必须在该枚举的基础类型范围内,多个枚举成员之间要使用逗号分隔。
2. 基本案例
接下来壹哥就根据枚举的语法,给大家定义一个最简单的枚举类。定义枚举类时需要使用enum关键字,且枚举类中的常量应该全部大写,多个枚举值之间用逗号隔开,代码如下所示:
java
enum Sex {
MALE, FAMALE
}
这样我们就定义出了一个表示性别的枚举类,该类中有"MALE"和"FAMALE"两个值,分别表示"男"和"女",其实这两个值也各自对应一个索引序号,分别是"0"和"1"。
3. 基本使用
定义好上面的这个枚举类之后,我们该怎么使用这个类呢?情况下面的代码。
java
/**
* @author 一一哥Sun
* @company 千锋教育
*/
public class Demo01 {
public static void main(String[] args) {
// 使用枚举值
Sex sex = Sex.MALE;
System.out.println("sex=" + sex);
// 也可以在switch中使用
switch (sex) {
case MALE: {
System.out.println("性别是男的..." );
break;
}
case FAMALE: {
System.out.println("性别是女的..." );
break;
}
default:
throw new IllegalArgumentException("未知性别: " + sex);
}
}
}
这个案例中,壹哥给大家演示了如何调用定义好的枚举值,尤其是在switch语句中,枚举更是经常与其配合使用。
4. 定义方法
既然枚举是一个类,那么在枚举类也可以拥有方法,我们可以在枚举类中定义构造方法和普通的方法。如果是构造方法,则必须使用private私有的或default默认的修饰符,其他的普通方法则需要使用public或default来修饰。如下所示:
java
/**
* @author 一一哥Sun
* @company 千锋教育
*/
public enum Size {
//3个枚举值
SMALL("S"), MEDIUM("M"), LARGE("L");
private String abbreviation;
//构造方法必须私有化或默认的,否则会出现编译错误:
//llegal modifier for the enum constructor; only private is permitted.
private Size(String abbreviation) {
this.abbreviation = abbreviation;
}
public String getAbbreviation() {
return abbreviation;
}
}
在这个案例中,壹哥定义了一个Size枚举类,该类拥有SMALL("S")、MEDIUM("M")、LARGE("L")三个枚举值,还有一个abbreviation属性和一个getAbbreviation()方法,以及一个私有的构造方法。大家要注意,构造方法必须私有化或默认,否则会出错,"llegal modifier for the enum constructor; only private is permitted"。
5. 实现单例模式
因为枚举的本质也是一种类,并且枚举的常量是在类加载时被创建的,枚举常量只会被创建一次,因此枚举值具有唯一性。所以我们可以根据这种特性,利用枚举来实现单例模式。其实使用枚举来实现单例模式非常简单,我们只需要定义一个枚举类型,然后在该枚举类型中定义一个枚举常量即可。下面是使用枚举来实现单例模式的代码示例:
java
/**
* @author 一一哥Sun
* @company 千锋教育
*/
public enum Singleton {
//枚举实例值
INSTANCE;
//定义其他的属性和方法
private String name;
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
}
与普通类实现的单例模式相比,用枚举实现的单例模式代码非常简洁。另外我们也可以像普通类一样,在enum类中添加变量和方法。我们可以直接使用Singleton.INSTANCE
来获取单例模式的唯一对象,这样就避免了调用公开的getInstance()方法。并且这种枚举单例的写法,可以使得我们完全不用考虑序列化和反射的问题。
6. 实现工厂模式
枚举类还可以作为工厂模式的一种实现方式。工厂模式是一种创建型的设计模式,它给我们提供了一种将对象的创建过程封装起来的方式。接下来壹哥给大家定义一个枚举类来表示不同的图形:
java
/**
* @author 一一哥Sun
* @company 千锋教育
*/
public enum ShapeType {
//3个枚举值
CIRCLE,
RECTANGLE,
SQUARE
}
然后,我们可以定义一个接口Shape来表示图形:
java
/**
* @author 一一哥Sun
* @company 千锋教育
*/
public interface Shape {
//绘图的方法
void draw();
}
接着,我们可以定义不同的类来实现Shape接口,表示不同的图形:
java
/**
* @author 一一哥Sun
* @company 千锋教育
* 圆形
*/
class Circle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a 圆形...");
}
}
class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a rectangle");
}
}
class Square implements Shape {
@Override
public void draw() {
System.out.println("Drawing a 矩形...");
}
}
最后,我们可以定义一个工厂类ShapeFactory,根据不同的枚举类型来创建不同的图形:
java
/**
* @author 一一哥Sun
* @company 千锋教育
*/
public class ShapeFactory {
public static Shape createShape(ShapeType shapeType) {
//使用枚举,创建工厂模式
switch (shapeType) {
case CIRCLE:
return new Circle();
case RECTANGLE:
return new Rectangle();
case SQUARE:
return new Square();
default:
throw new IllegalArgumentException("Invalid shape type: " + shapeType);
}
}
public static void main(String[] args) {
// 使用枚举值,调用工厂方法
Shape shape = ShapeFactory.createShape(ShapeType.CIRCLE);
shape.draw();
}
}
这样我们就通过枚举类作为工厂模式的一种实现方式,从而实现了将对象的创建过程封装起来的目的。
三. Enum类
1. 简介
Enum类是Java中的一个特殊类型,是所有枚举类的父类,用于定义枚举类型。但我们在Java中定义枚举类型时,一般都是通过enum关键字来定义。
2. Enum中的方法
Enum类给我们提供了许多方法来支持枚举类型的操作,下面是一些常用的Enum类方法:
- values():返回枚举类型中的所有常量;
- valueOf(String name):根据常量的名称返回该常量;
- name():返回枚举常量的名称;
- ordinal():返回枚举常量在枚举类型中的顺序;
- compareTo(E o):比较枚举常量在枚举类型中的顺序;
- getDeclaringClass():返回枚举常量所属的枚举类型;
- toString():返回枚举常量的名称。
以上这些方法都是在Enum类中定义的,因此我们在所有的枚举类型中都可以使用这些方法。其中,values()和valueOf()方法是我们最常用的方法,可以用来获取枚举常量和根据常量的名称获取常量。另外还有一些其他的方法,如name()方法可以返回枚举常量的名称,ordinal()方法可以返回枚举常量在枚举类型中的顺序,compareTo()方法可以比较枚举常量在枚举类型中的顺序,getDeclaringClass()方法可以返回枚举常量所属的枚举类型,toString()方法可以返回枚举常量的名称。
3. Enum实现原理
枚举类型的底层实现原理其实并不复杂!枚举类的底层实现原理和Enum类密切相关。
在Java中,枚举类型实际上是一种特殊的类,它们都是java.lang.Enum类的子类。在枚举类型中,每个常量都是Enum类的实例,因此它们都具有Enum类中定义的方法和属性。当我们定义一个枚举类型时,编译器会自动为其生成一个类,该类继承自Enum类,并且包含枚举类型中定义的所有常量和方法。
在Enum类中给我们提供了一些方法来支持枚举类型,如values()和valueOf()方法。其中,values()方法可以返回枚举类型的所有常量,而valueOf()方法可以根据常量的名称返回该常量。在枚举类型中,这些方法都是自动添加的,因此可以直接使用。
并且在枚举的底层,给我们定义了一个final类型的数组,用于保存枚举常量,每个枚举常量都是该数组中的一个元素。又因为枚举类型的构造方法是私有的,枚举常量只会在类加载的时候被创建一次,这样就保证了枚举常量的唯一性。
总之,枚举类型的底层实现原理和Enum类密不可分,Enum类给我们提供了许多方法来支持枚举类型,同时枚举类型的底层实现原理也是基于Enum类实现的。
四. 枚举扩展类
为了方便我们操作枚举类,java.util包中给我们提供了两个新的类,EnumMap和EnumSet,这两个是关于枚举的集合类,可以方便我们操作多个枚举类。
1. EnumMap
EnumMap是一种特殊的Map,它的键是枚举类型,值可以是任意类型。与普通的Map不同,EnumMap的内部会使用数组来保存键值对,因此在效率上会更高。
EnumMap的使用方法与普通的Map类似,但需要我们注意的是,EnumMap的键必须是同一个枚举类型的枚举常量。例如:
java
enum Color {
RED, GREEN, BLUE
}
enum Size {
SMALL, MEDIUM, LARGE
}
EnumMap<Color, String> colorMap = new EnumMap<>(Color.class);
colorMap.put(Color.RED, "红色");
colorMap.put(Color.GREEN, "绿色");
colorMap.put(Color.BLUE, "蓝色");
// 错误示范:尝试将不同枚举类型的枚举常量作为键
EnumMap<Size, String> sizeMap = new EnumMap<>(Size.class);
sizeMap.put(Size.SMALL, "小");
sizeMap.put(Color.RED, "红色"); // 编译错误:Type mismatch: cannot convert from EnumMap<Color,String> to EnumMap<Size,String>
在上面的例子中,壹哥创建了一个EnumMap,将颜色映射到字符串。由于颜色是同一个枚举类型的枚举常量,因此可以作为EnumMap的键,但如果尝试将不同枚举类型的枚举常量作为键,就会导致编译错误。
另外EnumMap还给我们提供了一些特有的方法,如entrySet()方法可以返回一个包含Map.Entry对象的Set,这些Map.Entry对象的键是枚举类型的枚举常量,值是EnumMap中相应的值。代码如下:
java
EnumMap<Color, String> colorMap = new EnumMap<>(Color.class);
colorMap.put(Color.RED, "红色");
colorMap.put(Color.GREEN, "绿色");
colorMap.put(Color.BLUE, "蓝色");
Set<Map.Entry<Color, String>> entrySet = colorMap.entrySet();
for (Map.Entry<Color, String> entry : entrySet) {
Color color = entry.getKey();
String name = entry.getValue();
System.out.println(color + ":" + name);
}
在上面的例子中,我们可以将EnumMap转换为Set,并使用for循环遍历其中的键值对。
2. EnumSet
EnumSet则是一种特殊的Set,它的元素必须是同一个枚举类型的枚举常量。与普通的Set不同,EnumSet内部会使用一个long类型的位向量来保存元素,因此在效率上会更高。
EnumSet的使用方法与普通的Set类似,但是需要注意的是,EnumSet的元素必须是同一个枚举类型的枚举常量。代码如下:
java
import java.util.EnumSet;
enum Day {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
public class EnumSetExample {
public static void main(String[] args) {
EnumSet<Day> weekend = EnumSet.of(Day.SATURDAY, Day.SUNDAY);
EnumSet<Day> weekdays = EnumSet.complementOf(weekend);
System.out.println("Weekend: " + weekend);
System.out.println("Weekdays: " + weekdays);
}
}
在这个例子中,壹哥定义了一个枚举类型Day,用于表示一周中的每一天。然后,我们使用EnumSet来创建两个集合,一个包含周末,一个包含工作日。我们可以使用of()方法来创建一个包含指定元素的EnumSet,使用complementOf()方法来创建一个包含枚举类型中未包含的元素的EnumSet。最终的输出结果为:
java
Weekend: [SATURDAY, SUNDAY]
Weekdays: [MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY]
从输出结果可以看出,我们成功地使用EnumSet创建了两个集合,并分别包含了周末和工作日。总之,EnumSet是Java中一种非常有用的集合类型,它给我们提供了一种高效的方式来存储和操作枚举类型的枚举常量。
------------------------------正片已结束,来根事后烟----------------------------
五. 结语
至此,壹哥 就通过今天的这篇文章给大家讲解了枚举相关的内容,不知道你学会了吗?如果你还有别的问题,可以在评论区给壹哥留言哦。
另外如果你独自学习觉得有很多困难,可以加入壹哥的学习互助群,大家一起交流学习。