Java中的枚举,竟然有这么多不为人知的知识点

简介: Java中的枚举,竟然有这么多不为人知的知识点

Java枚举,也称作Java枚举类型,是一种字段由一组固定常量集合组成的类型。枚举的主要目的是加强编译时类型的安全性。enum关键字是Java中的保留关键字。


在编译或设计时,当我们知道所有变量的可能性时,尽量使用枚举类型。本篇文章就带大家全面系统的了解枚举的使用,以及会遇到的一些问题。


Java中的枚举

枚举通常是一组相关的常量集合,其他编程语言很早就开始用枚举了,比如C++。从JDK1.5起,Java也开始支持枚举类型。


枚举是一种特殊的数据类型,它既是一种类(class)类型却又比类类型多了些特殊的约束,这些约束也造就了枚举类型的简洁性、安全性以及便捷性。


在Java中,通过enum来声明枚举类型,默认继承自java.lang.Enum。所以声明枚举类时无法再继承其他类。


枚举声明

在生活中我们会经常辨认方向,东南西北,它们的名称、属性等基本都是确定的,我们就可以将其声明为枚举类型:


public enum Direction {

  EAST, WEST, NORTH, SOUTH;

}


同样,每周七天也可以声明成枚举类型:


enum Day {

   MONDAY, TUESDAY, WEDNESDAY,

   THURSDAY, FRIDAY, SATURDAY, SUNDAY

}


在没有枚举或没使用枚举的情况下,并不是说不可以定义变量,我们可以通过类或接口进行常量的定义:


public class Day {
    public static final int MONDAY =1;
    public static final int TUESDAY=2;
    public static final int WEDNESDAY=3;
    public static final int THURSDAY=4;
    public static final int FRIDAY=5;
    public static final int SATURDAY=6;
    public static final int SUNDAY=7;
}

但这样存在许多不足,如在类型安全和使用方便性上。如果存在定义int值相同的变量,混淆的几率还是很大的,编译器也不会提出任何警告。因此,当能使用枚举的时候,并不提倡这种写法。


枚举的底层实现

上面我们已经说了,枚举是一个特殊的类,每一个枚举项本质上都是枚举类自身的实例。


因此,上面枚举类Direction可以通过下面代码进行示例:


final class Direction extends Enum{
    public final static Direction EAST = new Direction();
    public final static Direction WEST = new Direction();
    public final static Direction NORTH = new Direction();
    public final static Direction SOUTH = new Direction();
}

首先通过javac命令对Direction进行编译,然后通过javap命令来查看一下对应class文件内容:

bogon:enums apple$ javap Direction.class 
Compiled from "Direction.java"
public final class com.choupangxia.enums.Direction extends java.lang.Enum<com.choupangxia.enums.Direction> {
  public static final com.choupangxia.enums.Direction EAST;
  public static final com.choupangxia.enums.Direction WEST;
  public static final com.choupangxia.enums.Direction NORTH;
  public static final com.choupangxia.enums.Direction SOUTH;
  public static com.choupangxia.enums.Direction[] values();
  public static com.choupangxia.enums.Direction valueOf(java.lang.String);
  static {};
}

可以看到,一个枚举在经过编译器编译过后,变成了一个抽象类,它继承了java.lang.Enum;而枚举中定义的枚举常量,变成了相应的public static final属性,而且其类型就抽象类的类型,名字就是枚举常量的名字。


枚举使用实例

通过上面的反编译我们可以看到,枚举的选项本质上就是public static final的变量,所以就把它当做这样的变量使用即可。


public class EnumExample {
    public static void main(String[] args) {
        Direction north = Direction.NORTH;
        System.out.println(north);        //Prints NORTH
    }
}

枚举的ordinal()方法

ordinal()方法用于获取枚举变量在枚举类中声明的顺序,下标从0开始,与数组中的下标很相似。它的设计是用于EumSet和EnumMap复杂的基于枚举的数据结构使用。


Direction.EAST.ordinal();     //0

Direction.NORTH.ordinal();    //2

1

2

3

需要注意的是如果枚举项声明的位置发生了变化,那么ordinal方法的值也随之变化。所以,进来避免使用该方法。不然,当枚举项比较多时,别人在中间增删一项,会导致后续的所有顺序变化。


枚举的values()和valueOf()

values()方法可获取枚举类中的所有变量,并作为数组返回:


Direction[] directions = Direction.values();
for (Direction d : directions) {
    System.out.println(d);
}
//Output:
EAST
WEST
NORTH
SOUTH

values()方法是由编译器插入到枚举类中的static方法,而它的父类Enum中并不存在这个方法。

valueOf(String name)方法与Enum类中的valueOf方法的作用类似根据名称获取枚举变量,同样是由编译器生成的,但更简洁些,只需传递一个参数。

Direction east = Direction.valueOf("EAST");
System.out.println(east);
//Output:
EAST

枚举命名约定

按照约定,枚举属于常量,因此采用所有字母大写,下划线分割的风格(UPPER_CASE)。也就是说枚举类名与普通类约定一样,而枚举中的变量与静态变量的命名规范一致。


枚举的构造方法

默认情况下,枚举类是不需要构造方法的,默认的变量就是声明时的字符串。当然,你也可以通过自定义构造方法,来初始化枚举的一些状态信息。通常情况下,我们会在构造参数中传入两个参数,比如,一个编码,一个描述。


以上面的方向为例:


public enum Direction {
    // enum fields
    EAST(0), WEST(180), NORTH(90), SOUTH(270);
    // constructor
    private Direction(final int angle) {
        this.angle = angle;
    }
    // internal state
    private int angle;
    public int getAngle() {
        return angle;
    }
}

如果我们想访问每个方向的角度,可以通过简单的方法调用:

Direction north = Direction.NORTH;
System.out.println(north);                      //NORTH
System.out.println(north.getAngle());           //90
System.out.println(Direction.NORTH.getAngle()); //90

枚举中的方法

枚举就是一个特殊的类,因此也可以像普通的类一样拥有方法和属性。在枚举中不仅可以声明具体的方法,还可以声明抽象方法。

方法的访问权限可以是private、protected和public。可以通过这些方法返回枚举项的值,也可以做一些内部的私有处理。

public enum Direction {
    // enum fields
    EAST, WEST, NORTH, SOUTH;
    protected String printDirection() {
        String message = &quot;You are moving in &quot; + this + &quot; direction&quot;;
        System.out.println( message );
        return message;
    }
}

对应方法的使用如下:

Direction.NORTH.printDirection(); 
Direction.EAST.printDirection(); 
• 1
• 2

枚举类中还可以定义抽象的方法,但每个枚举项中必须实现对应的抽象方法:

public enum Direction 
{
    // enum fields
    EAST {
        @Override
        public String printDirection() {
            String message = &quot;You are moving in east. You will face sun in morning time.&quot;;
            return message;
        }
    },
    WEST {
        @Override
        public String printDirection() {
            String message = &quot;You are moving in west. You will face sun in evening time.&quot;;
            return message;
        }
    },
    NORTH {
        @Override
        public String printDirection() {
            String message = &quot;You are moving in north. You will face head in daytime.&quot;;
            return message;
        }
    },
    SOUTH {
        @Override
        public String printDirection() {
            String message = &quot;You are moving in south. Sea ahead.&quot;;
            return message;
        }
    };
    public abstract String printDirection();
}

抽象方法的调用,与普通方法一样:


Direction.NORTH.printDirection();

Direction.EAST.printDirection();

1

2

通过这种方式就可以轻而易举地定义每个枚举实例的不同行为方式。比如需要每个枚举项都打印出方向的名称,就可以定义这么一个抽象的方法。


上面的实例enum类似乎表现出了多态的特性,可惜的是枚举类型的实例终究不能作为类型传递使用。下面的方式编译器都无法通过:


//无法通过编译,Direction.NORTH是个实例对象

public void text(Direction.NORTH instance){ }

1

2

枚举的继承

上面已经提到过枚举继承自java.lang.Enum,Enum是一个抽象类:


public abstract class Enum<E extends Enum<E>>

       implements Comparable<E>, Serializable {

   // ...

}

1

2

3

4

也就是说,所有的枚举类都支持比较(Comparable)和序列化(Serializable)的特性。也正因为所有的枚举类都继承了Enum,所以无法再继承其他类了,但是可以实现接口。


枚举的比较

所有的枚举默认都是Comparable和单例的,因此可以通过equals方法进行比较,甚至可以直接用双等号“==”进行比较。


Direction east = Direction.EAST;

Direction eastNew = Direction.valueOf("EAST");

System.out.println( east == eastNew );           //true

System.out.println( east.equals( eastNew ) );    //true

1

2

3

4

5

枚举集合:EnumSet和EnumMap

在java.util包下引入了两个枚举集合类:EnumSet和EnumMap。


EnumSet

EnumSet类的定义如下:


public abstract class EnumSet<E extends Enum<E>> extends AbstractSet<E>

   implements Cloneable, java.io.Serializable{

   // ...

}

1

2

3

4

EnumSet是与枚举类型一起使用的专用Set集合,EnumSet中所有元素都必须是枚举类型。与其他Set接口的实现类HashSet/TreeSet不同的是,EnumSet在内部实现是位向量。


位向量是一种极为高效的位运算操作,由于直接存储和操作都是bit,因此EnumSet空间和时间性能都十分可观,足以媲美传统上基于int的“位标志”的运算,关键是我们可像操作set集合一般来操作位运算。


EnumSet不允许使用null元素,试图插入null将抛出 NullPointerException,但测试判断是否存在null元素或移除null元素则不会抛出异常,与大多数Collection实现一样,EnumSet不是线程安全的,在多线程环境下需注意数据同步问题。


使用实例:


public class Test {
   public static void main(String[] args) {
     Set enumSet = EnumSet.of(  Direction.EAST,
                                Direction.WEST,
                                Direction.NORTH,
                                Direction.SOUTH
                              );
   }
 }

EnumMap

EnumMap的声明如下:


public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V>

   implements java.io.Serializable, Cloneable

{}

1

2

3

与EnumSet类似,EnumMap是一个特殊的Map,Map的Key必须是枚举类型。EnumMap内部是通过数组实现的,效率比普通的Map更高一些。EnumMap的key值不能为null,并且EnumMap也不是线程安全的。


EnumMap使用实例如下:


public class Test {
  public static void main(String[] args){
    //Keys can be only of type Direction
    Map enumMap = new EnumMap(Direction.class);
    //Populate the Map
    enumMap.put(Direction.EAST, Direction.EAST.getAngle());
    enumMap.put(Direction.WEST, Direction.WEST.getAngle());
    enumMap.put(Direction.NORTH, Direction.NORTH.getAngle());
    enumMap.put(Direction.SOUTH, Direction.SOUTH.getAngle());
  }
}

枚举与switch

使用switch进行条件判断时,条件参数一般只能是整型,字符型,同时也支持枚举型,在java7后switch也对字符串进行了支持。

使用实例如下:

enum Color {GREEN,RED,BLUE}
public class EnumDemo4 {
    public static void printName(Color color){
        switch (color){
            //无需使用Color进行引用
            case BLUE: 
                System.out.println("蓝色");
                break;
            case RED:
                System.out.println("红色");
                break;
            case GREEN:
                System.out.println("绿色");
                break;
        }
    }
    public static void main(String[] args){
        printName(Color.BLUE);
        printName(Color.RED);
        printName(Color.GREEN);
    }
}

枚举与单例

单例模式是日常使用中最常见的设计模式之一了,单例的实现有很多种实现方法(饿汉模式、懒汉模式等),这里就不再赘述,只以一个最普通的单例来做对照,进而看看基于枚举如何来实现单例模式。

饿汉模式的实现:

public class Singleton {
    private static Singleton instance = new Singleton();
    private Singleton() {
    }
    public static Singleton getInstance() {
        return instance;
    }
}

简单直接,缺点是可能在还不需要时就把实例创建出来了,没起到lazy loading的效果。优点就是实现简单,而且安全可靠。

这样一个单例场景,如果通过枚举进行实现如下:

public enum Singleton {
    INSTANCE;
    public void doSomething() {
        System.out.println("doSomething");
    }
}

在effective java中说道,最佳的单例实现模式就是枚举模式。利用枚举的特性,让JVM来帮我们保证线程安全和单一实例的问题。除此之外,写法还特别简单。


直接通过Singleton.INSTANCE.doSomething()的方式调用即可。方便、简洁又安全。


小结

枚举在日常编码中几乎是必不可少的,如何用好,如何用精,还需要基础知识的铺垫,本文也正是基于此带大家从头到尾梳理了一遍。有所收获就点个赞吧。



目录
相关文章
|
4月前
|
IDE Java 开发工具
Java 基础篇必背综合知识点最新技术与实操应用全面总结指南
本总结梳理了Java 17+的核心知识点与新技术,涵盖基础概念(模块化系统、GraalVM)、数据类型(文本块、模式匹配)、流程控制(增强switch)、面向对象(Record类、密封类)、常用类库(Stream API、HttpClient)、实战案例(文件处理)、构建工具(Maven、Gradle)、测试框架(JUnit 5)、开发工具(IDE、Git)及云原生开发(Spring Boot 3、Docker)。通过理论结合实操,帮助开发者掌握Java最新特性并应用于项目中。代码示例丰富,建议配合实践加深理解。
119 4
|
3月前
|
Java 数据库连接 数据库
Java 相关知识点总结含基础语法进阶技巧及面试重点知识
本文全面总结了Java核心知识点,涵盖基础语法、面向对象、集合框架、并发编程、网络编程及主流框架如Spring生态、MyBatis等,结合JVM原理与性能优化技巧,并通过一个学生信息管理系统的实战案例,帮助你快速掌握Java开发技能,适合Java学习与面试准备。
138 2
Java 相关知识点总结含基础语法进阶技巧及面试重点知识
|
3月前
|
存储 Java 程序员
Java 基础知识点全面梳理包含核心要点及难点解析 Java 基础知识点
本文档系统梳理了Java基础知识点,涵盖核心特性、语法基础、面向对象编程、数组字符串、集合框架、异常处理及应用实例,帮助初学者全面掌握Java入门知识,提升编程实践能力。附示例代码下载链接。
131 1
|
3月前
|
Java 编译器 数据安全/隐私保护
Java 大学期末考试真题与答案 含知识点总结 重难点归纳及题库汇总 Java 期末备考资料
本文汇总了Java大学期末考试相关资料,包含真题与答案、知识点总结、重难点归纳及题库,涵盖Java基础、面向对象编程、异常处理、IO流等内容,并提供完整代码示例与技术方案,助你高效复习备考。
109 3
|
3月前
|
存储 缓存 安全
Java基础 - 知识点
Java基础知识点涵盖语言特性、面向对象与基本数据类型、缓存池机制、String类特性、参数传递、类型转换、继承、抽象类与接口区别、重写与重载、Object通用方法及关键字使用等核心内容,是掌握Java编程的重要基石。
|
4月前
|
存储 安全 Java
2025 年最新 40 个 Java 基础核心知识点全面梳理一文掌握 Java 基础关键概念
本文系统梳理了Java编程的40个核心知识点,涵盖基础语法、面向对象、集合框架、异常处理、多线程、IO流、反射机制等关键领域。重点包括:JVM运行原理、基本数据类型、封装/继承/多态三大特性、集合类对比(ArrayList vs LinkedList、HashMap vs TreeMap)、异常分类及处理方式、线程创建与同步机制、IO流体系结构以及反射的应用场景。这些基础知识是Java开发的根基,掌握后能为后续框架学习和项目开发奠定坚实基础。文中还提供了代码资源获取方式,方便读者进一步实践学习。
879 2
|
4月前
|
并行计算 Java API
Java 入门循环结构基础知识点详解
摘要:本文介绍了Java现代循环技术的进阶应用,包括Stream API、响应式编程和模式匹配,展示了如何用Stream API替代传统循环进行声明式集合处理(如过滤、映射和并行计算),以及响应式编程在异步非阻塞场景下的优势。文章还通过电商订单处理系统的案例演示了这些技术的综合应用,并提供了性能优化建议,如合理使用并行处理和避免循环内对象创建。这些现代特性使Java代码更简洁、高效,更适合高并发和I/O密集型场景。
58 1
|
4月前
|
缓存 算法 NoSQL
校招 Java 面试高频常见知识点深度解析与实战案例详细分享
《2025校招Java面试核心指南》总结了Java技术栈的最新考点,涵盖基础语法、并发编程和云原生技术三大维度: 现代Java特性:重点解析Java 17密封类、Record类型及响应式Stream API,通过电商案例演示函数式数据处理 并发革命:对比传统线程池与Java 21虚拟线程,详解Reactor模式在秒杀系统中的应用及背压机制 云原生实践:提供Spring Boot容器化部署方案,分析Spring WebFlux响应式编程和Redis Cluster缓存策略。
93 0
|
3月前
|
缓存 安全 前端开发
Java 核心知识点与实战应用解析
我梳理的这些内容涵盖了 Java 众多核心知识点。包括 final 关键字的作用(修饰类、方法、变量的特性);重载与重写的区别;反射机制的定义、优缺点及项目中的应用(如结合自定义注解处理数据、框架底层实现)。 还涉及 String、StringBuffer、StringBuilder 的差异;常见集合类及线程安全类,ArrayList 与 LinkedList 的区别;HashMap 的实现原理、put 流程、扩容机制,以及 ConcurrentHashMap 的底层实现。 线程相关知识中,创建线程的四种方式,Runnable 与 Callable 的区别,加锁方式(synchronize