Java知识点及其面试题整理三

来源:互联网 发布:淘宝店铺商品分类 编辑:程序博客网 时间:2024/06/06 17:47

本篇文章的知识点的索引(方便大家能够了解到不同文章中所提到的知识点):

(1)匿名内部类

(2)集合

(3)内存知识

(4)类型转换

(5)线程-----前面有一篇文章对这个进行了专题的介绍

(6)异常

(7)抽象类与接口

(8)对象

(9)枚举类

一:匿名内部类

(1)

//这样的匿名内部类是不符合规范的,因为String 是属于final类型的,是无法进行继承的,//而在匿名内部类的时候,是相当于新生成一个类,并且是继承该类,所以会报错String str = new String(){};//这样的匿名内部类是符合规范的,因为Object是可继承的类,//通过这样的匿名内部类的形式进行生成不会有影响,就相当于和new Runable()或者和new Thread()一样Object obj = new Object(){};

(2)下面程序的运行结果是什么?为什么?

    List list1 = new ArrayList();    List list2 = new ArrayList(){};   List list3 = new ArrayList(){{}};    List list4 = new ArrayList(){{}{}{}};    //1    System.out.println(list1.getClass() == list2.getClass());    //2    System.out.println(list1.getClass() == list3.getClass());    //3    System.out.println(list1.getClass() == list4.getClass());    //4    System.out.println(list2.getClass() == list3.getClass());    //5    System.out.println(list2.getClass() == list4.getClass());    //6    System.out.println(list3.getClass() == list4.getClass());

答:程序运行返回 6 个 false。

首先 list1 指向一个 ArrayList 对象实例;list2 指向一个继承自 ArrayList 的匿名类内部类对象;list3 也指向一个继承自 ArrayList 的匿名内部类(里面一对括弧为初始化代码块)对象;list4 也指向一个继承自 ArrayList 的匿名内部类(里面多对括弧为多个初始化代码块)对象;由于这些匿名内部类都出现在同一个类中,所以编译后其实得到的是 Demo$1、Demo$2、Demo$3 的形式,所以自然都互补相等了,不信你可以通过 listX.getClass().getName() 进行验证。

(3)开发中使用 Java 匿名内部类有哪些注意事项(经验)?

答:常见的注意事项如下。

1:使用匿名内部类时必须是继承一个类或实现一个接口(二者不可兼得且只能继承一个类或者实现一个接口)。

2:匿名内部类中是不能定义构造函数的,如需初始化可以通过构造代码块处理。(这个也会在面试被问匿名内部类如何初始化内容,就是只有通过代码块来实现)

3:匿名内部类中不能存在任何的静态成员变量和静态方法。

4:匿名内部类为局部内部类,所以局部内部类的所有限制同样对匿名内部类生效。

5:匿名内部类不能是抽象类,必须要实现继承的类或者实现接口的所有抽象方法。

(4)非静态内部类里面为什么不能有静态属性和静态方法?

答:static 类型的属性和方法在类加载的时候就会存在于内存中,要使用某个类的 static 属性或者方法的前提是这个类已经加载到 JVM 中,非 static 内部类默认是持有外部类的引用且依赖外部类存在,所以如果一个非 static 的内部类一旦具有 static 的属性或者方法就会出现内部类未加载时却试图在内存中创建内部类的 static 属性和方法,这自然是错误的,类都不存在(没被加载)却希望操作它的属性和方法。从另一个角度讲非 static 的内部类在实例化的时候才会加载(不自动跟随主类加载),而 static 的语义是类能直接通过类名来访问类的 static 属性或者方法,所以如果没有实例化非 static 的内部类就等于非 static 的内部类没有被加载,所以无从谈起通过类名访问 static 属性或者方法。

(5)Java 匿名内部类为什么不能直接使用构造方法,匿名内部类有没有构造方法?

答:因为类是匿名的(相当于没有名字),而且每次创建的匿名内部类同时被实例化后只能使用一次,所以就无从创建一个同名的构造方法了,但是可以直接调用父类的构造方法(譬如 new InnerClass(xxx, xxx) {})。实质上匿名内部类是有构造方法的,是通过编译器在编译时帮忙生成的,如下代码:

class InnerClass {}public class OutClass {    InnerClass clazz = new InnerClass(){};}
通过编译后生成了 InnerClass.class、OutClass$1.class、OutClass.class,可以看见 OutClass$1.class 就是我们匿名内部类的字节码名字,我们通过 javap -v OutClass$1.class 可以看到如下:
......{  final OutClass this$0;  ......  OutClass$1(OutClass);    descriptor: (LOutClass;)V    flags:    Code:      stack=2, locals=2, args_size=2         0: aload_0         1: aload_1        2: putfield      #1    // Field this$0:LOutClass;         5: aload_0         6: invokespecial #2    // Method InnerClass."<init>":()V         9: return      LineNumberTable:       line 8: 0}.....
可以很明显看到内部类的字节码中编译器为我们生成了参数为外部类引用的构造方法,其构造方法和普通类的构造方法没有区别,都是执行 <init> 方式。

(6)Java 中非静态内部类和静态内部类有什么区别?

答:常见的区别如下。

1:非静态内部类默认持有外部类的引用,静态内部类不存在该特性。

2:非静态内部类中不能定义静态成员或者方法,静态内部类中可以随便定义。

3:非静态内部类可以直接访问外部类的成员变量或者方法,静态内部类只能直接访问外部类的静态成员或者方法(实质是持有外部类名)。

4:非静态内部类可以定义在外部类的任何位置(方法里外均可,在方法外面定义的内部类的 class 访问类型可以是 public、protected 等,方法里的只能是默认 class,类似局部变量),静态内部类只能定义在外部类中最外层,class 修饰符可以是 public、protected 等。

5:非静态内部类创建实例时必须先创建外部类实例,静态内部类不依赖外部类实例。

6:静态方法中定义的内部类是静态内部类(这时不能在类前面加 static 关键字),静态方法中的静态内部类与普通方法中的内部类使用类似,除了可以直接访问外部类的 static 成员变量或者方法外还可以访问静态方法中的局部变量(java 1.8 以前局部变量前必须加 final 修饰符)

(7)Java 中内部类有什么好处(即 Java 的内部类有什么作用)?

答:具体好处如下。

1:内部类可以很好的实现隐蔽,一般的非内部类,是不允许有 private 与 protected 等权限的,但内部类(除过方法内部类)可以通过这些修饰符来实现隐藏。

2:内部类拥有外部类的的访问权限(分静态非静态情况),通过这一特性可以比较好的处理类之间的关联性,将一类事物的流程放在一起内部处理。

3:通过内部类可以实现多重继承,java 默认是单继承,我们可以通过多个内部类继承实现多个父类,接着由于外部类完全可访问内部类,所以就实现了类似多继承的效果。

4:通过内部类可以避免修改接口而实现同一个类中两种同名方法的调用(譬如你的类 A 中有一个参数为 int 的 func 方法,现在类 A 需要继承实现一个接口 B,而接口 B 中也有一个参数为 int 的 func 方法,此时如果直接继承实现就会出现同名方法矛盾问题,这时候如果不允许修改 A、B 类的 func 方法名则可以通过内部类来实现 B 接口,因为内部类对外部类来说是完全可访问的)。

(8)anonymousinnerclass(匿名内部类)是否可以extends(继承)其它类,是否可以implements(实现)interface(接口)?

不行,对于匿名内部类,看到的一句话说的很到位:

new <类或接口> <类的主体>

这种形式的new语句声明一个新的匿名类,他对一个给定的类进行扩展,或实现一个给定的接口。他还创建那个类的一个新实例,并把他作为语句的结果而返回。

这就是匿名类的实质,它本身就是一个类的继承或一个接口的实现,所以继承其它类或借口是无意义的.在eclipse中冶证实了我的想法.无论是继承其它类还是实现其它借口都显示Syntax error on token

(9)staticnestedclass和innerclass的不同,说得越多越好(面试题有的很笼统)。

static nested class 指静态嵌套类,或称嵌套类,是C++中常用的说法,inner class指内部类,是JAVA中的说法。

内部类是一个类内部类的统称,具体分为四种:成员类,静态成员类,局部类,匿名类。其中匿名类是局部类的特殊情况。对于成员类和静态成员类都存在于类的顶层代码中。相当于类的静态方法和非静态方法的关系。区别在于成员类依赖于类实例而静态成员类不依赖。所以前者只能访问实例方法和成员而后者只能访问静态方法和成员。它们都用于创建一个只和当前类有关。和其它类无关的依赖类.是否静态取决于是否依赖类的实例。局部类相当于局部变量。存在于类的局部代码中。相当于在main()中随意定义和使用类。唯一的不同是它只能使用final型的局部变量。这和垃圾回收机制有关。即局部变量会在代码块结束后被回收。而对象不一定。所以对象只能使用final的局部变量。同样,局部类存在的局部代码块也可以有静态和非静态的差别。局部类罪常见的应用就是匿名类。匿名类就是无名子的局部类。常在SWING设计中的添加监听中出现。

static nested class相当于inner class中的静态成员类。

(10)

二:集合

(1)collection和collections的区别

你千万别说一个是单数一个是复数。collection是集合类的上级接口,子接口有List和Set等,Collections是java.util下的一个工具类,提供一些静态方法对集合搜索丶排序丶线程同步化等。

(2)set里的元素是不能重复的,那么用什么方法来区分重复与否呢?是用hashcode()还是equals()?它们有何区别?

这个属于集合类的查找机制问题,在集合类中,确定两个元素是否相同,是用equals方法进行比较,hashcode的存在在于可以给元素快速分配一个index来存储。可以将collection看做很多个大箱子,index是箱子的编号,先将要扔进去的物品进行hash确定index,扔进响应箱子,然后再喝箱子里德其它物品equals()来比较是否相同。此外。相等的物品一定具有相同的hashcode,不等的物品不一定。具有相同hashcode的元素不一定相等。不同的hashcode的元素肯定不等。以上规则可以想象一下相等物品一定要分在同一个箱子中的情况。

(3)HashMap和HashTable的区别

1:起源JDK版本:HashMap出自JDK1.2,而HashTable出自JDK1,1

2:接口实现:两者都实现了Map,Cloneable,Serizaable三个接口,但是HashMap继承抽象类AbstractMap,而HashTable继承抽象类Dictionary(这个类已经被废弃了)。

3:存储的内容格式:HashMap支持有Null的key和value,并且将null的hash值设置为0(这个从源码可以看到的),而HashTable是不能存在有null的状态的。(这两者的差别,主要是在于HashMap的源码中的put方法中,针对null进行了特别的处理)。

4:Map初始容量和扩充容量大小:Entry数组的初始化大小和扩充容量不一样,HashMap是16(说一下,为什么是16,这是为了在后面的hash算法的优化的,因为在计算hash的时候会首先获取到key的hashcode,然后因为取模的效率低,所以hashmap采取计算index公式就是:index=HashCode(key)&(Length-1),这里因为Length用了16的话,这样得到的就是15,转为二进制就是1111,这样的话,计算出来就是索引index的位置了。而且通过利用1111这二进制使得hash值的分布更加均匀,可以想想,如果是用的10,那么就有一些二进制不会取到,比如0111,这样是不是就会导致hash分部不均匀了呢?),扩充为2倍,而HashTable是11,扩充为2倍+1.。(如果,在创建对象的时候,给了特定的容量大小的话,在HashMap是以你指定数值的2的幂次方的大小(这个原因也就是为什么初始容量设置为16),就比如,你指定的是18,但是18不是2的幂次方,所以会自动初始化为32;而HashTable是指定多少就初始化多少)

5:hash值获取方式:两者的hash方式不一样,在HashMap中,有使用了高位移位的形式来进行key的hash映射,主要是为了弥补hashMap的映射冲突的问题。(其实这个在JDK1.8的时候,对于其中的Entry数组的hash冲突,由原来的链表,当链表的节点数的个数超过8的时候,就使用了红黑树的数据结构来优化查找效率)。

6:线程安全区别:HashMap是线程不安全的,而HashTable是线程安全的,因为其方法中都采取了synchronized关键字。当然,目前如果你想实现线程安全的map,其实使用concurrentHashMap就好了,毕竟在JDK中,对HashTabble都添加了废弃注释了。

三:内存知识

(1)GC是什么?为什么要有GC?

GC 即 garbage collection(垃圾收集),是JAVA用于回收内存的一种方式,主要的实现方法有引用计数,标记回收,复制清除等,GC可以避免内存泄露和堆栈溢出,有效提高内存的利用效率,同时将程序员从繁琐的内存管理中释放出来

(2)内存回收的方法

1:复制:将内存按容量分为两块,每次只使用其中的一块,当进行内存回收的时候,将在当前在使用的块中存活的对象,复制到另一块中,然后清除当前块中的所有对象。这个方法比较简单快速和高效,但是可用内存减少了一半。。

2:标记--清除:采取两次扫描的方式,第一次扫描会将存活的对象进行标记,然后再通过第二次扫描的时候,将依然存活的对象进行保留,然后将非存活的对象进行清除。这个方法简单,但是会出现很多碎小的空间,利用率会变低

3:标记--整理:类似标记--清除方法和复制方法的结合,但是第二次扫描的时候,会把存活的对象,复制到另外一块中,这样就解决了上述两种方法的弊端。

4:分代回收:实际上就是标记整理方法,只是一般分为:老年代(很少垃圾需要进行回收)和新生代(很多内容需要进行回收),所以针对不同的年代,就进行不同的回收方法来实现一种高效的形式。对于老年代,一般采取标记清除或者标记整理,而对于新生代的话,一般就是采取复制算法的效率是最高的。

详细说一下新生代的内存分配问题:在上面讲到复制方法一般是将内存分了1:1的,但是实际上并不是这样的,在新生代中,进行的是8:1:1的比例。为什么会这样呢?因为8表示的Eden区,两个1分别表示的是Survivor区。所以新创建的对象都是放在Eden区。所有新创建的对象都是放在Eden区的,所以有很多的临时变量,故每次大部分回收的垃圾都是在Eden区的,所以Eden区会分配那么多的空间,那为什么要分两块Survivor区呢? 是因为,在进行一次coping算法回收时将Eden区中存活的对象复制到Survivor区,然后在进行一次回收时,Survivor区的存活对象没地方存放了,因为Eden区每次都有新创建的对象存在。故在新建一块Survivor B区,这时将Eden和Survivor A区存活的对象放在B区,然后清空Eden和Survivor A区。这样就相当于A和B每次回收后都有一个是全新的也就是空的,就是为了循环这种操作。

(3)触发Minor GC 和 Full GC 的条件(年轻代内容回收和老年代内存回收)

首先要知道Java虚拟机在运行的时候,数据的区有哪些类型:

1:程序计数器:线程私有。是一块较小的内存,是当前线程所执行的字节码的行号指示器。是Java虚拟机规范中唯一没有规定OOM(OutOfMemoryError)的区域。

2:Java栈:线程私有。生命周期和线程相同。是Java方法执行的内存模型。执行每个方法都会创建一个栈帧,用于存储局部变量和操作数(对象引用)。局部变量所需要的内存空间大小在编译期间完成分配。所以栈帧的大小不会改变。存在两种异常情况:若线程请求深度大于栈的深度,抛StackOverflowError。若栈在动态扩展时无法请求足够内存,抛OOM。

3:Java堆:所有线程共享。虚拟机启动时创建。存放对象实力和数组。所占内存最大。分为新生代(Young区),老年代(Old区)。新生代分Eden区,Servior区。Servior区又分为From space区和To Space区。Eden区和Servior区的内存比为8:1。 当扩展内存大于可用内存,抛OOM。

4:方法区:所有线程共享。用于存储已被虚拟机加载的类信息、常量、静态变量等数据。又称为非堆(Non – Heap)。方法区又称“永久代”。GC很少在这个区域进行,但不代表不会回收。这个区域回收目标主要是针对常量池的回收和对类型的卸载。当内存申请大于实际可用内存,抛OOM。

本地方法栈:线程私有。与Java栈类似,但是不是为Java方法(字节码)服务,而是为本地非Java方法服务。也会抛StackOverflowError和OOM。:

 Minor GC ,Full GC 触发条件

Minor GC触发条件:当Eden区满时,触发Minor GC。

Full GC触发条件:

(1)调用System.gc时,系统建议执行Full GC,但是不必然执行

(2)老年代空间不足

(3)方法去空间不足

(4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存

(5)由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小

四:类型转换

(1)short s1=1;s1=s1+1;有什么错?short s1=1;s1+=1;有什么错?

面试题都是很变态的,要做好受虐的准备。

s1=s1+1会出错,s1+1是int型,不能将int赋值给s1.需要显示转换,s1=(int)(s1+1).而s1+=1不会出错,至于原因,有人说和编译器的机制有关,需要看编译原理,话说编译原理什么的最讨厌了,就这样吧

(2)

五:线程

(1)sleep()和wait()有什么区别?

1,sleep()是java.lang.Thread中的静态方法,wait()是java.lang.Object中的方法;

2,sleep()用作当前线程阻塞自己,并在制定时间后恢复,wait()用于当前线程决定其他线程阻塞,是线程通信的表现;

3,sleep()不释放资源,wait()释放资源;

4,sleep()必须捕获异常,而wait()不需要。

(2)当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法?

多线程编程涉及不多,运用到锁的更是少的可怜

1 可以进入此对象其他非同步方法.

2 不可进入此对象此同步方法

3 不可进入此对象其他同步方法

(3)

六:异常

(1)说说最常见到的runtimeexception。

ClassCastException - 类型强制转换异常。
IllegalArgumentException - 传递非法参数异常。
ArithmeticException - 算术运算异常
ArrayStoreException - 向数组中存放与声明类型不兼容对象异常
IndexOutOfBoundsException - 下标越界异常
NegativeArraySizeException - 创建一个大小为负数的数组错误异常
NumberFormatException - 数字格式异常
SecurityException - 安全异常
UnsupportedOperationException - 不支持的操作异常  

算术异常类:ArithmeticExecption
空指针异常类:NullPointerException
类型强制转换异常:ClassCastException
数组负下标异常:NegativeArrayException
数组下标越界异常:ArrayIndexOutOfBoundsException
违背安全原则异常:SecturityException
文件已结束异常:EOFException
文件未找到异常:FileNotFoundException
字符串转换为数字异常:NumberFormatException
操作数据库异常:SQLException
输入输出异常:IOException
方法未找到异常:NoSuchMethodException

java.lang.AbstractMethodError
抽象方法错误。当应用试图调用抽象方法时抛出。

(2)error和exception有什么区别?

Error类和Exception类的父类都是throwable类,他们的区别是:

Error类一般是指与虚拟机相关的问题,如系统崩溃,虚拟机错误,内存空间不足,方法调用栈溢等。对于这类错误的导致的应用程序中断,仅靠程序本身无法恢复和和预防,遇到这样的错误,建议让程序终止。

Exception类表示程序可以处理的异常,可以捕获且可能恢复。遇到这类异常,应该尽可能处理异常,使程序恢复运行,而不应该随意终止异常。

Exception类又分为运行时异常(Runtime Exception)和受检查的异常(Checked Exception ),运行时异常;ArithmaticException,IllegalArgumentException,编译能通过,但是一运行就终止了,程序不会处理运行时异常,出现这类异常,程序会终止。而受检查的异常,要么用try。。。catch捕获,要么用throws字句声明抛出,交给它的父类处理,否则编译不会通过。

(3)try{}里有一个return语句,那么紧跟在这个try后的finally{}里的code会不会被执行,什么时候被执行,在return前还是后?

首先是执行try里面的代码,其次执行finally,最后是return。

(4)

七:抽象类和接口

(1)abstractclass和interface有什么区别?

抽象类可以有抽象方法和普通方法,也可以有自己的数据成员。接口只允许有常量,抽象方法和静态类成员。接口可以被多继承,抽象类不行。接口被实现时,所有方法必须被重写。抽象类被继承时如果有抽象方法没被重写,则子类也为抽象类。

(2)abstract的method是否可同时是static,是否可同时是native,是否可同时是synchronized?

abstact方法需要子类重写,重写的概念是就实例方法而言, 所以不能是static。因为同一原因,要重写就不能native,抽象方法在抽象类中,无实例,不能被调用,也没有内容,所以synchronized没有意义。abstract方法的修饰符只有private和public。

(3)接口是否可继承接口?抽象类是否可实现(implements)接口?抽象类是否可继承实体类(concreteclass)?

接口可以继承接口,抽象类可以实现接口,抽象类可以继承实体类。换句话说,抽象类除了不能实例化以外和普通类没什么区别。

(4)

八:对象

(1)创建对象的五种方法

使用new关键字} → 调用了构造函数使用Class类的newInstance方法} → 调用了构造函数使用Constructor类的newInstance方法} → 调用了构造函数使用clone方法} → 没有调用构造函数使用反序列化} → 没有调用构造函数

1:Student  aa  = new Student();2:Strudent bb = Class.forname("com.scw.model.student").newInstance();或者Student  bb2 = Student.Class.newInstance();3:Constructor <Student> constructor = Student.class.getConstructor();Student cc = constructor.newInstance();这(2)和(3)两种newInstance方法就是大家所说的反射。事实上Class的newInstance方法内部调用Constructor的newInstance方法。这也是众多框架,如Spring、Hibernate、Struts等使用后者的原因。4:Student dd = (Student)aa.clone();-------------------------------aa就是一个student对象,这个实现的是浅拷贝5:ObjectInputStream in = new ObjectInputStream(new FileInputStream("data.obj"));    Student ee = (Student) in.readObject();           ------------------这个实现的是深拷贝
九:枚举类

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

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

2:简单谈谈你理解的 Java 枚举本质原理?
答:java 枚举的本质原理是通过普通类来实现的,只是编译器为我们进行了加工处理,每个枚举类型编译后的字节码实质都继承自 java.lang.Enum 的枚举类型同名普通类,而每个枚举常量实质是一个枚举类型同名普通类的静态常量对象,所有枚举常量都通过静态代码块进行初始化实例赋值(由于是静态块,所以在类加载期间就初始化了)。记住枚举的本质是编译器处理成了类,枚举值为类的静态常量属性,其属性在类加载时的静态代码块中被初始化实例赋值。枚举可以有修饰符不大于默认修饰符的构造方法(修饰符可为 private,不可为 public 等)等,枚举只是一种语法糖,被编译器生成了最终的类而已。所以枚举类型其实和我们自己使用 Java 普通类实现的类似,如下:
//枚举类public enum Person{LEADER("小王");EMPLOYEE("小红");private Person(){this.("小黑");}public String name ;private Person(String name){ this.name = name;}}//等价于下面的普通类(所以说枚举类只是枚举常量类的封装而已)public Person{public final static Person LEADER;public final static Person EMPLYOEE;static{LEANDER = new Person("小王");EMPLYOEE = new Person("小红");}public String name ;private Person(){this.("小黑");}private Person(String name ){this.name = name;}}
3:枚举类与常量类的优缺点
答:优点:
(1)枚举类定义方便,简单,而常量类需要手动写static块去进行初始化内容
(2)枚举类自动具备内置方法(如 values 方法可以获得所有值的集合来遍历,ordinal 方法可以获得排序值,compareTo 方法可以基于 ordinal 比较),而常量类默认不具备这些方法。
缺点:
(1)枚举类不能继承其他的类,只能实现接口(因为枚举类在编译器进行处理后,是默认继承了Enum类的,而Java中只能单继承)
(2)扩展性没有常量类扩展好
4:Java 枚举类可以继承其他类(或实现其他接口)或者被其他类继承吗,为什么?
答:枚举类可以实现其他接口但不能继承其他类,因为所有枚举类在编译后的字节码中都继承自 java.lang.Enum(由编译器添加),而 Java 不支持多继承,所以枚举类不可以继承其他类。
枚举类不可以被继承,因为所有枚举类在编译后的字节码中都是继承自 java.lang.Enum(由编译器添加)的 final class 类,final 的类是不允许被派生继承的。(不清楚的可以查看前一篇历史推送枚举原理题)
5: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 编译器可能采用不同的方式和优化策略来完成这个转换。
6:Java 枚举会比静态常量更消耗内存吗?
答:会更消耗,一般场景下不仅编译后的字节码会比静态常量多,而且运行时也会比静态常量需要更多的内存,不过这个多取决于场景和枚举的规模等等,不能明确的定论多多少(一般都至少翻倍以上),此外更不能因为多就一刀切的认为静态常量应该优于枚举使用,枚举有自己的特性和场景,优化也不能过度。我们在上一篇枚举实质原理中已经解释了每个枚举类中的具体枚举类型都是对应类中的一个静态常量,该常量在 static 块中被初始实例化,此外枚举类还有自己的一些特有方法,而静态常量实质却很简单,所以从对象占用内存大小方面来计算肯定是枚举类比静态常量更加占体积和消耗运行时内存,至于具体怎么算其实很简单,大家可以自己下去搜一下 java 对象占用内存大小即可了解更多,搞清楚特定场合下具体大多少没有什么实际意义,搞清楚为什么大和怎么算出来的本质原因即可
7:Java 枚举是如何保证线程安全的?
答:因为 Java 类加载与初始化是 JVM 保证线程安全,而 Java enum 枚举在编译器编译后的字节码实质是一个 final 类,每个枚举类型是这个 final 类中的一个静态常量属性,其属性初始化是在该 final 类的 static 块中进行,而 static 的常量属性和代码块都是在类加载时初始化完成的,所以自然就是 JVM 保证了并发安全。
8:不使用 synchronized 和 lock 如何创建一个线程安全的单例?(非常重要)

答:
//通过饿汉模式public class Sington{private static Sington instance = new Singleton();private Singleton(){}public static Singleton getInstance(){return instance;}}//通过枚举实现单例public enum Singleton{INSTANCE;public void func(){}}//通过静态内部类public class Singleton{private static class SingletonHolder{private static finale Singleton Instance = new Singleton();}private Singleton(){}public static finale Singleton getInstance(){return SingletonHolder.Instance;}}//通过CAS(AtomicReference)实现单例模式--------真正没有用锁实现的方式public class Singleton{private static final AtomicReference<Singleton> Instance = new AtomicReference<Singleton>();private Singleton(){}public static Singleton getInstance(){for(;;){  Singleton singleton = Instance.get();  if(sinleton != null){    return singleton;}singleton = new Singleton();if(Instance.compareAndSet(null , singleton)){return singleton;}       }    }}
可以看到,上面四种方式都可以不使用 synchronized 或者 lock 来保证了单例创建的并发安全。前面三种都是借助了 JVM 的 ClassLoader 类加载初始化保证并发安全的机制(至于 JVM 底层其实也是使用了 synchronized 或者 lock 的机制),而对于最后一种通过 CAS 机制保证了并发安全(至于什么是 CAS 我们后面并发相关每日一题会再详细推送讨论的,这里先记住 CAS 就是一种非阻塞乐观锁机制,是一种基于忙等待的算法,依赖底层硬件实现,相对于锁其没有线程切换和阻塞的额外消耗,但是如果忙等待一直执行不成功的死循环会对 CPU 造成较大的开销),最后一种才是真正的无锁实现。
9:为什么有人说在一些场景下通过枚举实现的单例是最好的方式,原因是什么?
答:其实这个题目算是一箭双雕,既考察了 Java 枚举的实质特性又考察了单例模式的一些弊端问题。除过枚举实现的单例模式以外的其他实现方式都有一个比较大的问题是一旦实现了 Serializable 接口后就不再是单例了,因为每次调用 readObject() 方法返回的都是一个新创建出来的对象(当然可以通过使用 readResolve() 方法来避免,但是终归麻烦),而 Java 规范中保证了每一个枚举类型及其定义的枚举变量在 JVM 中都是唯一的,在枚举类型的序列化和反序列化上 Java 做了特殊处理,序列化时 Java 仅仅是将枚举对象的 name 属性输出到结果中,反序列化时则是通过 java.lang.Enum 的 valueOf 方法来根据名字查找枚举对象,同时禁用了 writeObject、readObject、readObjectNoData、writeReplace 和 readResolve 等方法。
这个问题也暴露出另一个新问题,Java 枚举序列化有哪些坑?
如果我们枚举被序列化本地持久化了,那我们就不能删除原来枚举类型中定义的任何枚举对象,否则程序在运行过程中反序列化时 JVM 就会找不到与某个名字对应的枚举对象了,所以我们要尽量避免多枚举对象序列化的使用(当然了,枚举实现的单例枚举对象一般都不会增删改,所以不存在问题)
10:Java 迭代器和枚举器的区别是什么?
答:主要区别如下。
Enumeration<E> 枚举器接口是 JDK 1.0 提供的,适用于传统类,而 Iterator<E> 迭代器接口是 JDK 1.2 提供的,适用于 Collections。
Enumeration 只有两个方法接口,我们只能读取集合的数据而不能对数据进行修改,而 Iterator 有三个方法接口,除了能读取集合的数据外也能对数据进行删除操作。
Enumeration 不支持 fail-fast 机制,而 Iterator 支持 fail-fast 机制(一种错误检测机制,当多线程对集合进行结构上的改变的操作时就有可能会产生 fail-fast 机制,譬如 ConcurrentModificationException 异常)。
总归现在尽量使用 Iterator 迭代器而不是 Enumeration 枚举器。

本篇会不断的进行更新的,欢迎大家关注我,这样的话就可以实时的看到最新的更新内容啦~~~~~~~~~~~也感谢网上一些学者的讨论和分享~~~~~~~~~~~~