枚举类比较用 == 还是 equals,有啥区别?

java 枚举值比较用 == 和 equals 方法没啥区别,两个随便用都是一样的效果。因为枚举 Enum 类的 equals 方法默认实现就是通过 == 来比较的;类似的 Enum 的 compareTo 方法比较的是 Enum 的 ordinal 顺序大小;类似的还有 Enum 的 name 方法和 toString 方法一样都返回的是 Enum 的 name 值。

简单谈谈你理解的 Java 枚举本质原理?

java 枚举的本质原理是通过普通类来实现的,只是编译器为我们进行了加工处理,每个

public enum Status {

START("a"),

RUNNING("b"),

STOP();

private Status() {

this("def");

}

private Status(String name) {

this.name = name;

}

public String name;

}

我们对如上枚举类型进行 javac 编译后通过 javap -v Status.class 可以查看其编译后字节码如下:

......

public final class Status extends java.lang.Enum

......

{

//枚举类型值都成了Status类型类的静态常量成员属性

public static final Status START;

public static final Status RUNNING;

public static final Status STOP;

public java.lang.String name;

public static Status[] values();

......

public static Status valueOf(java.lang.String);

......

//静态代码块,类加载时执行

static {};

flags: ACC_STATIC

Code:

stack=5, locals=0, args_size=0

//创建一个Status对象通过参数为字符串常量"a"的构造方法初始化赋值给START

0: new #4 // class Status

3: dup

4: ldc #10 // String START

6: iconst_0

7: ldc #11 // String a

9: invokespecial #7 // Method "":(Ljava/lang/String;ILjava/lang/String;)V

12: putstatic #12 // Field START:LStatus;

......

}

上面例子已经解释的很清楚了,记住枚举的本质是编译器处理成了类,枚举值为类的静态常量属性,其属性在类加载时的静态代码块中被初始化实例赋值。枚举可以有修饰符不大于默认修饰符的构造方法(修饰符可为 private,不可为 public 等)等,枚举只是一种语法糖,被编译器生成了最终的类而已。

所以枚举类型其实和我们自己使用 Java 普通类实现的类似,如下:

public class Status {

public static final Status START;

public static final Status RUNNING;

public static final Status STOP;

static {

START = new Status("a");

RUNNING = new Status("b");

STOP = new Status();

}

private Status() {

this("def");

}

private Status(String name) {

this.name = name;

}

public String name;

}

所以从某种意义上可以说 JDK 1.5 后引入的枚举类型是上面枚举常量类的代码封装而已。

Java 枚举类与常量的区别有哪些,有啥优缺点?

答:枚举相对于常量类来说定义更简单,其不需要定义枚举值,而常量类中的每个常量必须要手动添加值。枚举作为参数使用时可以在编译时避免弱类型错误,而常量类中的常量作为参数使用时在编译时无法避免弱类型错误(譬如常量类型为 int,参数传递一个常量类中没定义的 int 值)。枚举自动具备内置方法(如 values 方法可以获得所有值的集合来遍历,ordinal 方法可以获得排序值,compareTo 方法可以基于 ordinal 比较),而常量类默认不具备这些方法。枚举的缺点就是不能被继承(编译后生成的类是 final class 的),也不能通过 extends 继承其他类(枚举类编译后实质就是继承了 Enum 类,Java 是单继承机制),但是定义的枚举类可以通过 implements 实现其他接口,枚举值定义完毕后除非修改重构,否则无法做扩展,而常量类可以随意继承。

Java 枚举类可以继承其他类(或实现其他接口)或者被其他类继承吗,为什么?

答:枚举类可以实现其他接口但不能继承其他类,因为所有枚举类在编译后的字节码中都继承自 java.lang.Enum(由编译器添加),而 Java 不支持多继承,所以枚举类不可以继承其他类。

枚举类不可以被继承,因为所有枚举类在编译后的字节码中都是继承自 java.lang.Enum(由编译器添加)的 final class 类,final 的类是不允许被派生继承的。(不清楚的可以查看前一篇历史推送枚举原理题)

Java switch 为什么能使用枚举类型?

答:Java 1.7 之前 switch 参数可用类型为 short、byte、int、char,枚举类型之所以能使用其实是编译器层面实现的,编译器会将枚举 switch 转换为类似 switch(s.ordinal()) { case Status.START.ordinal() } 形式,所以实质还是 int 参数类型,感兴趣的可以自己写个使用枚举的 switch 代码然后通过 javap -v 去看下字节码就明白了。

此问题延伸出一个新问题就是 JDK 1.7 中 switch 支持 String 类型参数的原理是什么?

实际上 JDK1.7 的 switch 支持 String 也是在编译器层面实现的,在 Java 虚拟机和字节代码层面上依然只支持在 switch 语句中使用与整数类型兼容的类型。我们在 switch 中使用的 String 类型在编译的过程中会将字符串类型转换成与整数类型兼容的格式(譬如基于字符串常量的哈希码等),不同的 Java 编译器可能采用不同的方式和优化策略来完成这个转换。

Java 枚举会比静态常量更消耗内存吗?

会更消耗,一般场景下不仅编译后的字节码会比静态常量多,而且运行时也会比静态常量需要更多的内存,不过这个多取决于场景和枚举的规模等等,不能明确的定论多多少(一般都至少翻倍以上),此外更不能因为多就一刀切的认为静态常量应该优于枚举使用,枚举有自己的特性和场景,优化也不能过度。我们在上一篇枚举实质原理中已经解释了每个枚举类中的具体枚举类型都是对应类中的一个静态常量,该常量在 static 块中被初始实例化,此外枚举类还有自己的一些特有方法,而静态常量实质却很简单,所以从对象占用内存大小方面来计算肯定是枚举类比静态常量更加占体积和消耗运行时内存,至于具体怎么算其实很简单,大家可以自己下去搜一下 java 对象占用内存大小即可了解更多,搞清楚特定场合下具体大多少没有什么实际意义,搞清楚为什么大和怎么算出来的本质原因即可。

Java 枚举是如何保证线程安全的?

答:因为 Java 类加载与初始化是 JVM 保证线程安全,而 Java enum 枚举在编译器编译后的字节码实质是一个 final 类,每个枚举类型是这个 final 类中的一个静态常量属性,其属性初始化是在该 final 类的 static 块中进行,而 static 的常量属性和代码块都是在类加载时初始化完成的,所以自然就是 JVM 保证了并发安全。(不清楚 enum 编译后为啥是静态常量的可以查看历史推送了解更多)

不使用 synchronized 和 lock 如何创建一个线程安全的单例?

答:这是一个很 open 的题目,我们平时提到单例并发都是用锁机制,实际抛开锁机制也有几种实现方式可以保证创建单例的并发安全,而且各具特色。

//通过枚举实现单例模式

public enum Singleton {

INSTANCE;

public void func() {}

}

//通过饿汉模式实现单例

public class Singleton {

private static Singleton instance = new Singleton();

private Singleton (){}

public static Singleton getInstance() {

return instance;

}

}

//通过静态内部类模式实现单例

public class Singleton {

private static class SingletonHolder {

private static final Singleton INSTANCE = new Singleton();

}

private Singleton (){}

public static final Singleton getInstance() {

return SingletonHolder.INSTANCE;

}

}

//通过 CAS(AtomicReference)实现单例模式

public class Singleton {

private static final AtomicReference INSTANCE = new AtomicReference();

private Singleton() {}

public static Singleton getInstance() {

for (;;) {

Singleton singleton = INSTANCE.get();

if (null != singleton) {

return singleton;

}

singleton = new Singleton();

if (INSTANCE.compareAndSet(null, singleton)) {

return singleton;

}

}

}

}

可以看到,上面四种方式都可以不使用 synchronized 或者 lock 来保证了单例创建的并发安全。前面三种都是借助了 JVM 的 ClassLoader 类加载初始化保证并发安全的机制(至于 JVM 底层其实也是使用了 synchronized 或者 lock 的机制),而对于最后一种通过 CAS 机制保证了并发安全(至于什么是 CAS 我们后面并发相关每日一题会再详细推送讨论的,这里先记住 CAS 就是一种非阻塞乐观锁机制,是一种基于忙等待的算法,依赖底层硬件实现,相对于锁其没有线程切换和阻塞的额外消耗,但是如果忙等待一直执行不成功的死循环会对 CPU 造成较大的开销),最后一种才是真正的无锁实现。

为什么有人说在一些场景下通过枚举实现的单例是最好的方式,原因是什么?

其实这个题目算是一箭双雕,既考察了 Java 枚举的实质特性又考察了单例模式的一些弊端问题。除过枚举实现的单例模式以外的其他实现方式都有一个比较大的问题是一旦实现了 Serializable 接口后就不再是单例了,因为每次调用 readObject() 方法返回的都是一个新创建出来的对象(当然可以通过使用 readResolve() 方法来避免,但是终归麻烦),而 Java 规范中保证了每一个枚举类型及其定义的枚举变量在 JVM 中都是唯一的,在枚举类型的序列化和反序列化上 Java 做了特殊处理,序列化时 Java 仅仅是将枚举对象的 name 属性输出到结果中,反序列化时则是通过 java.lang.Enum 的 valueOf 方法来根据名字查找枚举对象,同时禁用了 writeObject、readObject、readObjectNoData、writeReplace 和 readResolve 等方法。

这个问题也暴露出另一个新问题,Java 枚举序列化有哪些坑?

如果我们枚举被序列化本地持久化了,那我们就不能删除原来枚举类型中定义的任何枚举对象,否则程序在运行过程中反序列化时 JVM 就会找不到与某个名字对应的枚举对象了,所以我们要尽量避免多枚举对象序列化的使用(当然了,枚举实现的单例枚举对象一般都不会增删改,所以不存在问题)。

Java 迭代器和枚举器的区别是什么?

主要区别如下。

Enumeration 枚举器接口是 JDK 1.0 提供的,适用于传统类,而 Iterator 迭代器接口是 JDK 1.2 提供的,适用于 Collections。

Enumeration 只有两个方法接口,我们只能读取集合的数据而不能对数据进行修改,而 Iterator 有三个方法接口,除了能读取集合的数据外也能对数据进行删除操作。

Enumeration 不支持 fail-fast 机制,而 Iterator 支持 fail-fast 机制(一种错误检测机制,当多线程对集合进行结构上的改变的操作时就有可能会产生 fail-fast 机制,譬如 ConcurrentModificationException 异常)。

总归现在尽量使用 Iterator 迭代器而不是 Enumeration 枚举器。

java 7种枚举类型_Java中的枚举类型相关推荐

  1. java不可变类型_Java中的值类型:为什么它们不可变?

    java不可变类型 值类型不必是不变的. 但是他们是. 在上一篇文章中,我讨论了Java中指针与引用之间的区别以及如何传递方法参数(按值传递或按引用传递). 这些与Java中尚不存在的值类型密切相关( ...

  2. java 枚举迭代_Java中的枚举和迭代器之间的区别

    java 枚举迭代 Java中的枚举与迭代器 (Enumeration vs Iterator in Java) Here, we will see how Enumeration differs f ...

  3. java中的string类型_Java中的字符串类型(String)

    String 字符串是一个引用数据类型,字符串都是对象. String特性:1.在程序中出现的字符串字面量(常量),在程序中运行时会以对象的形式保存在JVM内存的字符串池中,并且所有的这些字符串字面量 ...

  4. Java+包裹类型_java中的包裹类型

    包裹类型将一个基本数据类型的数据转换成对象的形式,从而使得它们可以像对象一样参与运算和传递.下表列出了基本数据类型所对应的包裹类型: 基本类型    包裹类型 boolean    Boolean c ...

  5. java中的枚举类_java中的枚举类型

    java中为了对参数类型使用限定,引入了泛型,实现了在编译期对参数类型是否合法的判断.同样,java为了对参数的值的限定,引入了枚举类,实现了在编译期对参数的值是否合法的判断. 首先我们用自定义类的方 ...

  6. java记录类型_Java中的记录类型

    java记录类型 于2020年3月发布的JDK 14引入了记录 (预览语言功能),该记录提供了一种紧凑的语法来声明主要用于保存数据的类. 在记录中 ,所有低级,重复且容易出错的代码都类似于构造函数,访 ...

  7. java 设计char类型_JAVA中的char类型

    1.JAVA中,char占2字节,16位.可在存放汉字 2.char赋值 char a='a';  //任意单个字符,加单引号. char a='中';//任意单个中文字,加单引号. char a=1 ...

  8. java四种修饰符_java中的四种修饰符

    在编程过程中,经常会遇到四种修饰符来控制访问权限.之前对这个知识点没有研究过,一直是一知半解,每次遇到问题都模棱两可,不能给出一个确切的答案.近几天系统的看了看,也有了自己的一点心得体会. 正文: 先 ...

  9. java 枚举常量_java中的枚举类和常量类区别在哪儿?

    假如有一笔业务需要审核,审核状态分:未审核,审核中,审核通过,审核不通过.我们在程序里是否可以直接这么写: if(state==1){//1代表未操作 //操作 }else{ //...... } 将 ...

最新文章

  1. 学术圈要炸锅:论文作者和审稿人串通欺骗盲审,ACM Fellow发文痛斥!顶会“想中就中”...
  2. 二分查找(等于x,小于x,小于等于x,大于x,大于等于x )
  3. linux 的 usr 文件
  4. C++实现邻接矩阵存储的图及dfs遍历
  5. python查询sql_Python处理SQL语句(提供SQL查询平台使用)
  6. ext3 tree tbar 初始化定义
  7. 799元!乐视智能门锁新品Le1S发布
  8. linux系统下载了qq怎么安装,怎么在linux系统里面安装QQ
  9. Excel中如何引用 「文件名」、「sheet 页」的名字
  10. LWN:Fedora 关于无驱动打印的讨论!
  11. html中的换行符也占空间,如何解决
  12. deepin20.7安装mysql8.0.30教程
  13. 软件服务化:管理当先
  14. Java包装类相关知识点
  15. 名帖269 董其昌 行书临《颜真卿裴将军诗卷》
  16. ldd显示可执行模块的dependenc
  17. js判断字符串是不是一个纯数字
  18. 关于void (visit)(const ElemType )的理解
  19. Github新手创建第一个 repository流程
  20. 软件著作权提交源代bai码格式_软件著作权提交源代码格式要求

热门文章

  1. Three.js - 使用法向贴图 normalMap 创建更加细致的凹凸皱纹
  2. Real-world Super-Resolution via Kernel Estimation and Noise Injection
  3. 为捧自己Diss同行,猎豹傅盛发布机器人,敢问路在何方?
  4. c语言输出数以空格格开,C语言输入输出函数格式详解.docx
  5. 如何将excel表格转化为word文档(去掉表格)
  6. 计算机病毒危害性分析,计算机病毒的毒性暨危害性分析系统
  7. 20-animation动画
  8. 【我的第一份开发工作】1.找工作前的经历
  9. 【总结】1619- 一个自学前端的4年工作总结【三十而立,拒绝躺平】
  10. java setdaemon_java教程--守护线程setDaemon