转载

还有你不知道的Java枚举特性(下篇)

这篇是博文 还有你不知道的Java枚举特性(上篇) 的下篇,可以点击下面的链接前往。

本篇主要内容:

  • Java 枚举是一个特殊的类,聊聊其方法的重写
  • 如何使用接口来组织 Java 枚举?
  • 如何使用枚举实现 Java 的单例模式
  • JDK 数据结构中关于枚举的集合 EnumSet 和字典 EnumMap

重写枚举的方法

所有的枚举类都继承自 Enum ,在这个父类当中 toStringequalshashCode 的三个方法,可以看一下,源码如下:

public String toString(){
    return name;
}

public final boolean equals(Object other){ 
    return this==other;
}

public final int hashCode(){
    return super.hashCode();
}

可以看出在这三个方法当中,我们只能重写 toString 方法,另外两个方法都是 final 修饰的方法,不可以被子类重写。

我们在自定义的枚举中,可以重写 toString 方法的,示例如下:

public enum Color {
    RED("red color"),
    GREEN("green color"),
    BLUE("blue color"),
    YELLOW("yellow color");

    Color(String name) {
        _name = name;
    }

    private String _name;
    
    @Override
    public String toString(){
        return "this.name: " + _name;
    }
}

关于 Enum 的源码,可以在博文Java 枚举的本质 中的文末翻阅。

对于 Java 中所有枚举都是继承自 Enum ,大家可以去使用 javap 命令反编译看看,如下代码是 javap 后的简单示例:

public final class com.veryitman.Colorextends java.lang.Enum<com.veryitman.Color>{
  public static final com.veryitman.Color RED;
  public static final com.veryitman.Color GREEN;
  public static final com.veryitman.Color BLUE;
  public static final com.veryitman.Color YELLOW;
  public static com.veryitman.Color[] values();
  public static com.veryitman.ColorvalueOf(java.lang.String);
  static {};
}

可以看到 Color 是继承自 java.lang.Enum 的, Enum 是一个抽象类并实现了 ComparableSerializable 这两个接口,如下:

public abstract class Enum<Eextends Enum<E>>implements Comparable<E>,Serializable

使用接口组织枚举

当我们定义的枚举过多且又有很多嵌套,可以使用接口来组织这些枚举,将其归类,这样一来不仅代码看起来很规范,并且也很好管理代码。

如下示例,使用接口 MobileTool 来组织两个枚举。

public interface MobileTool{
        enum Phone implements MobileTool {
            HUAWEI, iPhone, OPPO, XIAOMI
        }

        enum Pad implements MobileTool {
            iPad, WEPad, sPad
        }
    }

简单的可以这样使用,示例如下:

MobileTool mphone = MobileTool.Phone.HUAWEI;
mphone = MobileTool.Phone.iPhone;

MobileTool mpad = MobileTool.Pad.iPad;
mpad = MobileTool.Pad.sPad;

枚举实现单例模式

Effective Java 这本书籍中,作者有个这样下面的描述:

"This approach is functionally equivalent to the public field approach, except that it is more concise, provides the serialization machinery for free, and provides an ironclad guarantee against multiple instantiation, even in the face of sophisticated serialization or reflection attacks. While this approach has yet to be widely adopted, a single-element enum type is the best way to implement a singleton."

核心的意思是

使用枚举实现单例的方法虽然还没有广泛采用,但单元素的枚举类型已经成为实现 Java 单例模式的最佳方法。

我们用枚举实现一下单例模式,示例代码如下:

public enum Foo {
    INSTANCE;

    public void printFoo(){
        System.out.println("Foo here.");
    }
}

相比 Java 中其他的单例实现方式,此时此刻你会发现,枚举实现单例的代码会精简很多。

那么,枚举实现单例模式到底有哪些优势呢?或者换句话说,就这样实现单例靠谱吗?

经过大量例子和 Java 编程专家的讲解,枚举实现单例模式相当靠谱,它具有以下一些特点:

1、枚举实现的单例模式是线程安全的

本质上面来讲,枚举实现的单例之所以是线程安全的,这个跟 Java 的类加载机制有关。从上面反编译的代码来看,枚举是 final class 并且每个枚举值都是 static 的,这里牵扯到 ClassLoader 的相关知识,如果有兴趣建议大家去研究一下。

总之,对于我们任何一个枚举在第一次被真正用到之时,会被 Java 虚拟机加载并且完成初始化,而这个初始化过程是线程安全的,所以你需要记住枚举实现的单例模式是多线程安全的就可以了。

2、枚举可解决反射/反序列化问题

我们知道,一般的单例模式都存在两个问题,一个是可以通过反射调用,另一个就是可以通过序列化和反序列化来破坏单例。

一般解决反射调用可以通过私有构造方法中做处理,示例代码如下:

private static boolean flag = false;

private Foo(){
    synchronized (this) {
        if (false == flag) {
            flag = true;
        } else {
            throw new RuntimeException("不能反复创建");
        }
    }
};

了解序列化原理的同学,可以通过在单例类中实现 readResolve 方法就可以避免反序列化攻击这个问题了。示例代码如下:

private Object readResolve(){
    return INSTANCE;
}

Java 的枚举的反序列化实现并不是通过反射实现的,也就是说枚举的序列化和反序列化是有经过特殊定制和处理的,这就可以避免反序列化过程中由于反射而导致的单例被破坏问题。

总之,枚举实现的单例模式不仅可以防止反射破坏,还可以防止序列化破坏单例。

除枚举实现单这种方式以外,我一般使用下面两种方式来实现单例模式。

饿汉式的单例模式写法

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

静态内部类实现单例模式

public class Foo{
    private static class FooHolder{
    	private static final Foo INSTANCE = new Foo();  
    }
    
    private Foo(){
    	
    }
    
    public static final Foo getInstance(){  
    	return FooHolder.INSTANCE;
    }
}

枚举集合

EnumSet 是一个专为枚举设计的集合类, EnumSet 中的所有元素都必须是指定枚举类型的枚举值。

EnumSet 类结构图如下:

还有你不知道的Java枚举特性(下篇)

EnumSet 是一个抽象类,无法被实例化,但是可以通过静态方法获取该类的实例。

public abstract class EnumSet<Eextends Enum<E>>extends AbstractSet<E>
implements Cloneable,java.io.Serializable

EnumMap 类结构图如下:

还有你不知道的Java枚举特性(下篇)

EnumMap 定义如下:

public class EnumMap<Kextends Enum<K>,V>extends AbstractMap<K,V>
implements java.io.Serializable,Cloneable

EnumSet 保证集合中的元素不重复; EnumMap 中的 key 是 enum 类型,而 value 则可以是任意类型。

关于这两个数据结构的使用方法,大家可以参考 JDK 手册。

赠人玫瑰,手留余香~

还有你不知道的Java枚举特性(下篇)

原文  http://www.veryitman.com/2019/08/11/还有你不知道的Java枚举特性-下篇/
正文到此结束
Loading...