찾다
Javajava지도 시간Java의 열거형 Enum에 대한 심층 분석

什么是Enum

Enum是自Java 5 引入的特性,用来方便Java开发者实现枚举应用。一个简单的Enum使用如下。

// ColorEnum.javapublic enum ColorEmun {
    RED,
    GREEN,
    YELLOW
}public void setColorEnum(ColorEmun colorEnum) {    //some code here}

setColorEnum(ColorEmun.GREEN);

为什么会有Enum

在Enum之前的我们使用类似如下的代码实现枚举的功能.

public static final int COLOR_RED = 0;public static final int COLOR_GREEN = 1;public static final int COLOR_YELLOW = 2;public void setColor(int color) {    //some code here}//调用setColor(COLOR_RED)

然而上面的还是有不尽完美的地方

setColor(COLOR_RED)与setColor(0)效果一样,而后者可读性很差,但却可以正常运行

setColor方法可以接受枚举之外的值,比如setColor(3),这种情况下程序可能出问题

概括而言,传统枚举有如下两个弊端

安全性

可读性,尤其是打印日志时

因此Java引入了Enum,使用Enum,我们实现上面的枚举就很简单了,而且还可以轻松避免传入非法值的风险.

枚举原理是什么

Java中Enum的本质其实是在编译时期转换成对应的类的形式。

首先,为了探究枚举的原理,我们先简单定义一个枚举类,这里以季节为例,类名为Season,包含春夏秋冬四个枚举条目.

public enum Season {    SPRING,    SUMMER,    AUTUMN,    WINTER}

然后我们使用javac编译上面的类,得到class文件.

javac Season.java

然后,我们利用反编译的方法来看看字节码文件究竟是什么.这里使用的工具是javap的简单命令,先列举一下这个Season下的全部元素.

company javap Season
Warning: Binary file Season contains com.company.Season
Compiled from "Season.java"public final class com.company.Season extends java.lang.Enum<com.company.season> {  public static final com.company.Season SPRING;  public static final com.company.Season SUMMER;  public static final com.company.Season AUTUMN;  public static final com.company.Season WINTER;  public static com.company.Season[] values();  public static com.company.Season valueOf(java.lang.String);  static {};
}</com.company.season>

从上反编译结果可知

java代码中的Season转换成了继承自的java.lang.enum的类

既然隐式继承自java.lang.enum,也就意味java代码中,Season不能再继承其他的类

Season被标记成了final,意味着它不能被继承

static代码块

使用javap具体反编译class文件,得到静态代码块相关的结果为

static {};
    Code:       0: new           #4                  // class com/company/Season
       3: dup       4: ldc           #7                  // String SPRING
       6: iconst_0       7: invokespecial #8                  // Method "<init>":(Ljava/lang/String;I)V
      10: putstatic     #9                  // Field SPRING:Lcom/company/Season;
      13: new           #4                  // class com/company/Season
      16: dup      17: ldc           #10                 // String SUMMER
      19: iconst_1      20: invokespecial #8                  // Method "<init>":(Ljava/lang/String;I)V
      23: putstatic     #11                 // Field SUMMER:Lcom/company/Season;
      26: new           #4                  // class com/company/Season
      29: dup      30: ldc           #12                 // String AUTUMN
      32: iconst_2      33: invokespecial #8                  // Method "<init>":(Ljava/lang/String;I)V
      36: putstatic     #13                 // Field AUTUMN:Lcom/company/Season;
      39: new           #4                  // class com/company/Season
      42: dup      43: ldc           #14                 // String WINTER
      45: iconst_3      46: invokespecial #8                  // Method "<init>":(Ljava/lang/String;I)V
      49: putstatic     #15                 // Field WINTER:Lcom/company/Season;
      52: iconst_4      53: anewarray     #4                  // class com/company/Season
      56: dup      57: iconst_0      58: getstatic     #9                  // Field SPRING:Lcom/company/Season;
      61: aastore      62: dup      63: iconst_1      64: getstatic     #11                 // Field SUMMER:Lcom/company/Season;
      67: aastore      68: dup      69: iconst_2      70: getstatic     #13                 // Field AUTUMN:Lcom/company/Season;
      73: aastore      74: dup      75: iconst_3      76: getstatic     #15                 // Field WINTER:Lcom/company/Season;
      79: aastore      80: putstatic     #1                  // Field $VALUES:[Lcom/company/Season;
      83: return}</init></init></init></init>

其中

0~52为实例化SPRING, SUMMER, AUTUMN, WINTER

53~83为创建Season[]数组$VALUES,并将上面的四个对象放入数组的操作.

values方法

values方法的的返回值实际上就是上面$VALUES数组对象

swtich中的枚举

在Java中,switch-case是我们经常使用的流程控制语句.当枚举出来之后,switch-case也很好的进行了支持.

比如下面的代码是完全正常编译,正常运行的.

public static void main(String[] args) {
        Season season = Season.SPRING;        switch(season) {            case SPRING:
                System.out.println("It's Spring");                break;            case WINTER:
                System.out.println("It's Winter");                break;            case SUMMER:
                System.out.println("It's Summer");                break;            case AUTUMN:
                System.out.println("It's Autumn");                break;
        }
    }

不过,通常情况下switch-case支持类似int的类型,那么它是怎么做到对Enum的支持呢,我们反编译上述方法看一下字节码的真实情况.

public static void main(java.lang.String[]);
    Code:       0: getstatic     #2                  // Field com/company/Season.SPRING:Lcom/company/Season;
       3: astore_1       4: getstatic     #3                  // Field com/company/Main$1.$SwitchMap$com$company$Season:[I
       7: aload_1       8: invokevirtual #4                  // Method com/company/Season.ordinal:()I
      11: iaload      12: tableswitch   { // 1 to 4
                     1: 44
                     2: 55
                     3: 66
                     4: 77
               default: 85
          }      44: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
      47: ldc           #6                  // String It's Spring
      49: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      52: goto          85
      55: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
      58: ldc           #8                  // String It's Winter
      60: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      63: goto          85
      66: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
      69: ldc           #9                  // String It's Summer
      71: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      74: goto          85
      77: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
      80: ldc           #10                 // String It's Autumn
      82: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      88: return

注意上面代码块有这样的一段代码

8: invokevirtual #4                  // Method com/company/Season.ordinal:()I

事实果真如此,在switch-case中,还是将Enum转成了int值(通过调用Enum.oridinal()方法)

枚举与混淆

在Android开发中,进行混淆是我们在发布前必不可少的工作,混下后,我们能增强反编译的难度,在一定程度上保护了增强了安全性.

而开发人员处理混淆更多的是将某些元素加入不混淆的名单,这里枚举就是需要排除混淆的.

在默认的混淆配置文件中,已经加入了关于对枚举混淆的处理

# For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations-keepclassmembers enum * {    public static **[] values();    public static ** valueOf(java.lang.String);
}

关于为什么要保留values()方法和valueOf()方法,请参考文章读懂 Android 中的代码混淆 关于枚举的部分

使用proguard优化

使用Proguard进行优化,可以将枚举尽可能的转换成int。配置如下

-optimizations class/unboxing/enum

确保上述代码生效,需要确proguard配置文件不包含-dontoptimize指令。

当我们使用gradlew打包是,看到类似下面的输出,即Number of unboxed enum classes:1代表已经将一个枚举转换成了int的形式。

Optimizing...
  Number of finalized classes:                 0   (disabled)  Number of unboxed enum classes:              1
  Number of vertically merged classes:         0   (disabled)  Number of horizontally merged classes:       0   (disabled)

枚举单例

单例模式是我们在日常开发中可谓是最常用的设计模式.

然后要设计好单例模式,无非考虑一下几点

确保只有唯一实例,不多创建多余实例

确保实例按需创建.

因此传统的做法想要实现单例,大致有一下几种

饿汉式加载

懒汉式synchronize和双重检查

利用java的静态加载机制

相比上述的方法,使用枚举也可以实现单例,而且还更加简单.

public enum AppManager {
    INSTANCE;    private String tagName;    public void setTag(String tagName) {        this.tagName = tagName;
    }    public String getTag() {        return tagName;
    }
}

调用起来也更加简单

AppManager.INSTANCE.getTag();

枚举如何确保唯一实例

因为获得实例只能通过AppManager.INSTANCE

下面的方式是不可以的

AppManager appManager = new AppManager(); //compile error

关于单例模式,可以阅读单例这种设计模式了解更多。

(Android中)该不该用枚举

既然上面提到了枚举会转换成类,这样理论上造成了下面的问题

增加了dex包的大小,理论上dex包越大,加载速度越慢

同时使用枚举,运行时的内存占用也会相对变大

关于上面两点的验证,秋百万已经做了详细的论证,大家可以参考这篇文章《Android 中的 Enum 到底占多少内存?该如何用?》

关于枚举是否使用的结论,大家可以参考

如果你开发的是Framework不建议使用enum

如果是简单的enum,可以使用int很轻松代替,则不建议使用enum

另外,如果是Android中,可以使用下面介绍的枚举注解来实现。

除此之外,我们还需要对比可读性和易维护性来与性能进行衡量,从中进行做出折中

在Android中的替代

Android中新引入的替代枚举的注解有IntDef和StringDef,这里以IntDef做例子说明一下.

public class Colors {    @IntDef({RED, GREEN, YELLOW})    @Retention(RetentionPolicy.SOURCE)    public @interface LightColors{}    public static final int RED = 0;    public static final int GREEN = 1;    public static final int YELLOW = 2;
}

声明必要的int常量

声明一个注解为LightColors

使用@IntDef修饰LightColors,参数设置为待枚举的集合

使用@Retention(RetentionPolicy.SOURCE)指定注解仅存在与源码中,不加入到class文件中

比如我们用来标注方法的参数

private void setColor(@Colors.LightColors int color) {
        Log.d("MainActivity", "setColor color=" + color);
}

调用的该方法的时候

setColor(Colors.GREEN);

关于Android中的枚举,可以参考探究Android中的注解

更多Java中枚举Enum的深入剖析相关文章请关注PHP中文网!

성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
고급 Java 프로젝트 관리, 구축 자동화 및 종속성 해상도에 Maven 또는 Gradle을 어떻게 사용합니까?고급 Java 프로젝트 관리, 구축 자동화 및 종속성 해상도에 Maven 또는 Gradle을 어떻게 사용합니까?Mar 17, 2025 pm 05:46 PM

이 기사에서는 Java 프로젝트 관리, 구축 자동화 및 종속성 해상도에 Maven 및 Gradle을 사용하여 접근 방식과 최적화 전략을 비교합니다.

적절한 버전 및 종속성 관리로 Custom Java 라이브러리 (JAR Files)를 작성하고 사용하려면 어떻게해야합니까?적절한 버전 및 종속성 관리로 Custom Java 라이브러리 (JAR Files)를 작성하고 사용하려면 어떻게해야합니까?Mar 17, 2025 pm 05:45 PM

이 기사에서는 Maven 및 Gradle과 같은 도구를 사용하여 적절한 버전 및 종속성 관리로 사용자 정의 Java 라이브러리 (JAR Files)를 작성하고 사용하는 것에 대해 설명합니다.

카페인 또는 구아바 캐시와 같은 라이브러리를 사용하여 자바 애플리케이션에서 다단계 캐싱을 구현하려면 어떻게해야합니까?카페인 또는 구아바 캐시와 같은 라이브러리를 사용하여 자바 애플리케이션에서 다단계 캐싱을 구현하려면 어떻게해야합니까?Mar 17, 2025 pm 05:44 PM

이 기사는 카페인 및 구아바 캐시를 사용하여 자바에서 다단계 캐싱을 구현하여 응용 프로그램 성능을 향상시키는 것에 대해 설명합니다. 구성 및 퇴거 정책 관리 Best Pra와 함께 설정, 통합 및 성능 이점을 다룹니다.

캐싱 및 게으른 하중과 같은 고급 기능을 사용하여 객체 관계 매핑에 JPA (Java Persistence API)를 어떻게 사용하려면 어떻게해야합니까?캐싱 및 게으른 하중과 같은 고급 기능을 사용하여 객체 관계 매핑에 JPA (Java Persistence API)를 어떻게 사용하려면 어떻게해야합니까?Mar 17, 2025 pm 05:43 PM

이 기사는 캐싱 및 게으른 하중과 같은 고급 기능을 사용하여 객체 관계 매핑에 JPA를 사용하는 것에 대해 설명합니다. 잠재적 인 함정을 강조하면서 성능을 최적화하기위한 설정, 엔티티 매핑 및 모범 사례를 다룹니다. [159 문자]

Java의 클래스로드 메커니즘은 다른 클래스 로더 및 대표 모델을 포함하여 어떻게 작동합니까?Java의 클래스로드 메커니즘은 다른 클래스 로더 및 대표 모델을 포함하여 어떻게 작동합니까?Mar 17, 2025 pm 05:35 PM

Java의 클래스 로딩에는 부트 스트랩, 확장 및 응용 프로그램 클래스 로더가있는 계층 적 시스템을 사용하여 클래스로드, 링크 및 초기화 클래스가 포함됩니다. 학부모 위임 모델은 핵심 클래스가 먼저로드되어 사용자 정의 클래스 LOA에 영향을 미치도록합니다.

See all articles

핫 AI 도구

Undresser.AI Undress

Undresser.AI Undress

사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover

AI Clothes Remover

사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool

Undress AI Tool

무료로 이미지를 벗다

Clothoff.io

Clothoff.io

AI 옷 제거제

AI Hentai Generator

AI Hentai Generator

AI Hentai를 무료로 생성하십시오.

인기 기사

R.E.P.O. 에너지 결정과 그들이하는 일 (노란색 크리스탈)
4 몇 주 전By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. 최고의 그래픽 설정
4 몇 주 전By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. 아무도들을 수없는 경우 오디오를 수정하는 방법
4 몇 주 전By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. 채팅 명령 및 사용 방법
4 몇 주 전By尊渡假赌尊渡假赌尊渡假赌

뜨거운 도구

mPDF

mPDF

mPDF는 UTF-8로 인코딩된 HTML에서 PDF 파일을 생성할 수 있는 PHP 라이브러리입니다. 원저자인 Ian Back은 자신의 웹 사이트에서 "즉시" PDF 파일을 출력하고 다양한 언어를 처리하기 위해 mPDF를 작성했습니다. HTML2FPDF와 같은 원본 스크립트보다 유니코드 글꼴을 사용할 때 속도가 느리고 더 큰 파일을 생성하지만 CSS 스타일 등을 지원하고 많은 개선 사항이 있습니다. RTL(아랍어, 히브리어), CJK(중국어, 일본어, 한국어)를 포함한 거의 모든 언어를 지원합니다. 중첩된 블록 수준 요소(예: P, DIV)를 지원합니다.

Atom Editor Mac 버전 다운로드

Atom Editor Mac 버전 다운로드

가장 인기 있는 오픈 소스 편집기

에디트플러스 중국어 크랙 버전

에디트플러스 중국어 크랙 버전

작은 크기, 구문 강조, 코드 프롬프트 기능을 지원하지 않음

PhpStorm 맥 버전

PhpStorm 맥 버전

최신(2018.2.1) 전문 PHP 통합 개발 도구

WebStorm Mac 버전

WebStorm Mac 버전

유용한 JavaScript 개발 도구