IT七剑客 IT七剑客
首页
wresource
郭霖
孤寒者
IT邦德
沉默王二
老麦
stackoverflow
GitHub (opens new window)
首页
wresource
郭霖
孤寒者
IT邦德
沉默王二
老麦
stackoverflow
GitHub (opens new window)
  • Java基础语法

  • 程序人生

  • 实用工具

  • Java重要知识点

    • Java中常用的48个关键字和2个保留字
    • 深入理解Java中的注解
    • 深入剖析Java中的拆箱和装箱
    • 先有Class还是先有Object?
    • 详解Java中Comparable和Comparator的区别
    • 彻底讲明白的Java浅拷贝与深拷贝
    • Java枚举(enum)
    • 一次性搞清楚equals和hashCode
    • 大白话说Java反射:入门、使用、原理
    • 深入理解Java泛型
    • 深入理解Java中的hashCode方法
    • 深入理解Java中的不可变对象
    • instanceof关键字是如何实现的?
    • Java中int、Integer、new Integer之间的区别
    • Java命名规范,告别编码 5 分钟,命名 2 小时
    • 彻底弄懂Java中的Unicode和UTF-8编码
    • jdk9为何要将String的底层实现由char[]改成了byte[]?
    • 为什么JDK源码中,无限循环大多使用for(;;)而不是while(true)?
    • Java 方法重写 Override 和方法重载 Overload 的区别,一下子就明白了
    • Java重写(Overriding)时应当遵守的11条规则
    • Java到底是值传递还是引用传递?
    • Java不能实现真正泛型的原因是什么?
    • Java中可变参数的使用
  • Java工具

  • 数组与字符串

  • 沉默王二 Java
  • Java重要知识点
沉默王二
2023-01-22

Java枚举(enum)

# Java枚举(enum)

“今天我们来学习枚举吧,三妹!”我说,“同学让你去她家玩了两天,感觉怎么样呀?”

“心情放松了不少。”三妹说,“可以开始学 Java 了,二哥。”

“OK。”

“枚举(enum),是 Java 1.5 时引入的关键字,它表示一种特殊类型的类,继承自 java.lang.Enum。”

“我们来新建一个枚举 PlayerType。”

public enum PlayerType {
    TENNIS,
    FOOTBALL,
    BASKETBALL
}
1
2
3
4
5

“二哥,我没看到有继承关系呀!”

“别着急,看一下反编译后的字节码,你就明白了。”

public final class PlayerType extends Enum
{

    public static PlayerType[] values()
    {
        return (PlayerType[])$VALUES.clone();
    }

    public static PlayerType valueOf(String name)
    {
        return (PlayerType)Enum.valueOf(com/cmower/baeldung/enum1/PlayerType, name);
    }

    private PlayerType(String s, int i)
    {
        super(s, i);
    }

    public static final PlayerType TENNIS;
    public static final PlayerType FOOTBALL;
    public static final PlayerType BASKETBALL;
    private static final PlayerType $VALUES[];

    static 
    {
        TENNIS = new PlayerType("TENNIS", 0);
        FOOTBALL = new PlayerType("FOOTBALL", 1);
        BASKETBALL = new PlayerType("BASKETBALL", 2);
        $VALUES = (new PlayerType[] {
            TENNIS, FOOTBALL, BASKETBALL
        });
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

“看到没?Java 编译器帮我们做了很多隐式的工作,不然手写一个枚举就没那么省心省事了。”

  • 要继承 Enum 类;
  • 要写构造方法;
  • 要声明静态变量和数组;
  • 要用 static 块来初始化静态变量和数组;
  • 要提供静态方法,比如说 values() 和 valueOf(String name)。

“确实,作为开发者,我们的代码量减少了,枚举看起来简洁明了。”三妹说。

“既然枚举是一种特殊的类,那它其实是可以定义在一个类的内部的,这样它的作用域就可以限定于这个外部类中使用。”我说。

public class Player {
    private PlayerType type;
    public enum PlayerType {
        TENNIS,
        FOOTBALL,
        BASKETBALL
    }
    
    public boolean isBasketballPlayer() {
      return getType() == PlayerType.BASKETBALL;
    }

    public PlayerType getType() {
        return type;
    }

    public void setType(PlayerType type) {
        this.type = type;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

PlayerType 就相当于 Player 的内部类。

由于枚举是 final 的,所以可以确保在 Java 虚拟机中仅有一个常量对象,基于这个原因,我们可以使用“==”运算符来比较两个枚举是否相等,参照 isBasketballPlayer() 方法。

“那为什么不使用 equals() 方法判断呢?”三妹问。

if(player.getType().equals(Player.PlayerType.BASKETBALL)){};
1

“我来给你解释下。”

“==”运算符比较的时候,如果两个对象都为 null,并不会发生 NullPointerException,而 equals() 方法则会。

另外, “==”运算符会在编译时进行检查,如果两侧的类型不匹配,会提示错误,而 equals() 方法则不会。

“枚举还可用于 switch 语句,和基本数据类型的用法一致。”我说。

switch (playerType) {
        case TENNIS:
            return "网球运动员费德勒";
        case FOOTBALL:
            return "足球运动员C罗";
        case BASKETBALL:
            return "篮球运动员詹姆斯";
        case UNKNOWN:
            throw new IllegalArgumentException("未知");
        default:
            throw new IllegalArgumentException(
                    "运动员类型: " + playerType);

    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14

“如果枚举中需要包含更多信息的话,可以为其添加一些字段,比如下面示例中的 name,此时需要为枚举添加一个带参的构造方法,这样就可以在定义枚举时添加对应的名称了。”我继续说。

public enum PlayerType {
    TENNIS("网球"),
    FOOTBALL("足球"),
    BASKETBALL("篮球");

    private String name;

    PlayerType(String name) {
        this.name = name;
    }
}
1
2
3
4
5
6
7
8
9
10
11

“get 了吧,三妹?”

“嗯,比较好理解。”

“那接下来,我就来说点不一样的。”

“来吧,我准备好了。”

“EnumSet 是一个专门针对枚举类型的 Set 接口(后面会讲)的实现类,它是处理枚举类型数据的一把利器,非常高效。”我说,“从名字上就可以看得出,EnumSet 不仅和 Set 有关系,和枚举也有关系。”

“因为 EnumSet 是一个抽象类,所以创建 EnumSet 时不能使用 new 关键字。不过,EnumSet 提供了很多有用的静态工厂方法。”

“来看下面这个例子,我们使用 noneOf() 静态工厂方法创建了一个空的 PlayerType 类型的 EnumSet;使用 allOf() 静态工厂方法创建了一个包含所有 PlayerType 类型的 EnumSet。”

public class EnumSetTest {
    public enum PlayerType {
        TENNIS,
        FOOTBALL,
        BASKETBALL
    }

    public static void main(String[] args) {
        EnumSet<PlayerType> enumSetNone = EnumSet.noneOf(PlayerType.class);
        System.out.println(enumSetNone);

        EnumSet<PlayerType> enumSetAll = EnumSet.allOf(PlayerType.class);
        System.out.println(enumSetAll);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

“来看一下输出结果。”

[]
[TENNIS, FOOTBALL, BASKETBALL]
1
2

有了 EnumSet 后,就可以使用 Set 的一些方法了,见下图。

“除了 EnumSet,还有 EnumMap,是一个专门针对枚举类型的 Map 接口的实现类,它可以将枚举常量作为键来使用。EnumMap 的效率比 HashMap 还要高,可以直接通过数组下标(枚举的 ordinal 值)访问到元素。”

“和 EnumSet 不同,EnumMap 不是一个抽象类,所以创建 EnumMap 时可以使用 new 关键字。”

EnumMap<PlayerType, String> enumMap = new EnumMap<>(PlayerType.class);
1

有了 EnumMap 对象后就可以使用 Map 的一些方法了,见下图。

和 HashMap(后面会讲)的使用方法大致相同,来看下面的例子。

EnumMap<PlayerType, String> enumMap = new EnumMap<>(PlayerType.class);
enumMap.put(PlayerType.BASKETBALL,"篮球运动员");
enumMap.put(PlayerType.FOOTBALL,"足球运动员");
enumMap.put(PlayerType.TENNIS,"网球运动员");
System.out.println(enumMap);

System.out.println(enumMap.get(PlayerType.BASKETBALL));
System.out.println(enumMap.containsKey(PlayerType.BASKETBALL));
System.out.println(enumMap.remove(PlayerType.BASKETBALL));
1
2
3
4
5
6
7
8
9

“来看一下输出结果。”

{TENNIS=网球运动员, FOOTBALL=足球运动员, BASKETBALL=篮球运动员}
篮球运动员
true
篮球运动员
1
2
3
4

“除了以上这些,《Effective Java》这本书里还提到了一点,如果要实现单例的话,最好使用枚举的方式。”我说。

“等等二哥,单例是什么?”三妹没等我往下说,就连忙问道。

“单例(Singleton)用来保证一个类仅有一个对象,并提供一个访问它的全局访问点,在一个进程中。因为这个类只有一个对象,所以就不能再使用 new 关键字来创建新的对象了。”

“Java 标准库有一些类就是单例,比如说 Runtime 这个类。”

Runtime runtime = Runtime.getRuntime();
1

“Runtime 类可以用来获取 Java 程序运行时的环境。”

“关于单例,懂了些吧?”我问三妹。

“噢噢噢噢。”三妹点了点头。

“通常情况下,实现单例并非易事,来看下面这种写法。”

public class Singleton {  
    private volatile static Singleton singleton; 
    private Singleton (){}  
    public static Singleton getSingleton() {  
    if (singleton == null) {
        synchronized (Singleton.class) { 
        if (singleton == null) {  
            singleton = new Singleton(); 
        }  
        }  
    }  
    return singleton;  
    }  
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

“要用到 volatile、synchronized 关键字等等,但枚举的出现,让代码量减少到极致。”

public enum EasySingleton{
    INSTANCE;
}
1
2
3

“就这?”三妹睁大了眼睛。

“对啊,枚举默认实现了 Serializable 接口,因此 Java 虚拟机可以保证该类为单例,这与传统的实现方式不大相同。传统方式中,我们必须确保单例在反序列化期间不能创建任何新实例。”我说。

“好了,关于枚举就讲这么多吧,三妹,你把这些代码都手敲一遍吧!”

“好勒,这就安排。二哥,你去休息吧。”

“嗯嗯。”讲了这么多,必须跑去抽烟机那里安排一根华子了。


最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关 等等等等……详情戳:可以说是2022年全网最全的学习和找工作的PDF资源了 (opens new window)

关注二哥的原创公众号 沉默王二,回复111 即可免费领取。

编辑 (opens new window)
#沉默王二#Java
上次更新: 2023/01/22, 15:00:47
彻底讲明白的Java浅拷贝与深拷贝
一次性搞清楚equals和hashCode

← 彻底讲明白的Java浅拷贝与深拷贝 一次性搞清楚equals和hashCode→

最近更新
01
Coding 102 Writing code other people can read
02-26
02
Kotlin Flow响应式编程,StateFlow和SharedFlow
02-05
03
Kotlin Flow响应式编程,操作符函数进阶
02-05
更多文章>
Theme by Vdoing | Copyright © 2022-2023 IT七剑客 | MIT License
  • 闽ICP备2021006579号-4
  • 闽公网安备 35012102500470号
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式