Java-枚举

Java-枚举

转载声明

本文大量内容系转载自以下文章,有删改,并参考其他文档资料加入了一些内容:

1 概述

1.1 枚举类用途

枚举类一般用于一个类只可能拥有有限个实例,比如季节只可拥有春夏秋冬,性别只有男女。枚举类作用总结如下:

  • 替代代码硬编码的魔法值
  • 能和switch-case结合,简化大量的if-else,让代码更加优雅
  • 枚举类可以保存更多字段,可用于响应。
    比如java的后端编程中,controller层一般是要给前端返回一个响应码和对应的描述信息,如{code: 200, detail: “执行成功”},这时就可以将这个响应体写成一个枚举类:
    public enum  ErrorResult {
        /**
         * 错误类型,可传入Result类的相关方法使用
         */
        ILLEGAL_PARAMS(400, "illegal arguments"),
        INVALID_TOKEN(401, "invalid token");	
        private int code;
        private String message;
    
        public int getCode() {
            return code;
        }
    
        public String getMessage() {
            return message;
        }
    
        ErrorResult(int code, String message) {
            this.code = code;
            this.message = message;
        }
    }
    
    //@Data和@Builder使用lombok的注解,自动给类生成getter、setter方法和build
    @Data
    @Builder
    public class Result<E> implements Serializable {
    	private static final long serialVersionUID = -4578560278556203640L;
    	
        private Integer code;
        private String msg;
        private E data;
    
        public static <T> Result<T> success(T data) {
            return Result.<T>builder().code(0).msg("OK").data(data).build();
        }
    
        public static <T> Result<T> success() {
            return Result.<T>builder().code(0).msg("OK").data(null).build();
        }
    
        public static <T> Result<T> error(ErrorResult errorResult) {
            return Result.<T>builder().code(errorResult.getCode()).msg(errorResult.getMessage()).data(null).build();
        }
    
        public static <T> Result<T> error(int code, String message) {
            return Result.<T>builder().code(code).msg(message).data(null).build();
        }
    }
    
  • 数据库字段的前端展示的格式化可以借助枚举类实现
    比如说,数据库存储用户的性别是0和1,数据传回前端的时候要转成男和女就可以借助枚举类:
public enum  Gender {
    MAN("男", 0),
    WOMENR("女", 1);

    public static final Map<Integer,Gender> FROM_CODE_MAP = new HashMap<>();
    
    private String name;
    private Integer code;

    static {
        Arrays.stream(Gender.values()).forEach(e -> FROM_CODE_MAP.put(e.code, e));
    Gender(String name, int code) {
        this.name = name;
        this.code = code;
    }

    public static Gender fromCode(Integer code){
        return FROM_CODE_MAP.get(code);
    }
    public static String getNameFromCode(Integer code){
         return FROM_CODE_MAP.containsKey(code) ? FROM_CODE_MAP.get(code).name : null;
    }
}

1.2 枚举类特点

  • 枚举类不能继承(因为每个枚举类已经继承了java.lang.Enum类),也不能被继承(因为被编译为字节码后是final修饰的类),但可以实现接口
  • 枚举类构造函数私有
  • 枚举类也可以定义方法和属性
  • 枚举类的对象也可以定义属性
  • 枚举类和普通类有以下几个不同点:
    1. 枚举类不能指定继承的父类(因为继承了java.lang.Enum类),但是可以实现多个接口,枚举类默认实现了Comparable接口和Serializable接口
    2. 枚举类的构造方法的访问权限只可为private
    3. 枚举类的实例必须显式列出。

更多关于枚举类的特点详解在第四章详述。

1.3 枚举类规则

  • 枚举类不能继承只能实现接口
  • 枚举类的所有实例必须放在第一行显示
  • 枚举类可以有抽象方法,但是必须在它的实例中实现

2 枚举类实现原理

我们以下列代码为例来分析枚举类:

public enum Season {
	SPRING("春天"), SUMMER("夏天"), AUTUMN("秋天"), WINTER("冬天");
	
	private final String chinese;
	
	private Season(String chinese) {
		this.chinese = chinese;
	}

	public String getChinese() {
		return chinese;
	}
}

其实上述代码相当于(可在编译成class文件之后,用javap解析字节码:javap -v Season.class):

// 注意是final修饰的,说明枚举类不能被继承
public final class Season extends Enum<Season> {
	public static final Season SPRING;
	public static final Season SUMMER;
	public static final Season AUTUMN;
	public static final Season WINTER;
	private static final Season[] ENUM$VALUES;
	//  增加一个静态初始化块和一个静态数组,静态初始化块负责构造枚举类的实例并将其保存到数组中。
	static {
		SPRING = new Season("SPRING", 0, "春天");
		SUMMER = new Season("SUMMER", 1, "夏天");
		AUTUMN = new Season("AUTUMN", 2, "秋天");
		WINTER = new Season("WINTER", 3, "冬天");
		ENUM$VALUES = new Season[]{SPRING, SUMMER, AUTUMN, WINTER}
	}
	
	private final String chinese;
	
	// 1. 修改了当前枚举类的构造方法,向构造方法前面增加两个参数:name, ordinal。
	// 分别代表枚举类的名称和初始化顺序,并且使用super调用父类java.lang.Enum的构造方法。
	// 2. 还需要注意枚举类的构造函数是private的
	private Season(String name, int ordinal, String chinese) {
		super(name, ordinal);
		this.chinese = chinese;
	}

	public String getChinese() {
		return chinese;
	}
	
	// 添加两个静态方法:values和valueOf
	// 该方法返回一个数组,包含了该枚举类所有的实例
	//(并不是返回枚举类自己保存实例的数组,而是通过对其复制,以确保实例不被篡改)
	public static Season[] values() {
		Season[] arr = new Session[ENUM$VALUES.length];
		System.arraycopy(ENUM$VALUES, 0, arr, 0, arr.length);
		return arr;
	}
	// 该方法通过当前枚举类的名称返回对应的实例。比如valueOf("SPRING")返回Season.SPRING)。
	public static Season valueOf(String name) {
		return Enum.valueOf(Season.class, name);
	}
}

3 java.lang.Enum源码解析

看完枚举类实际实现后,我们再来分析Enum类的源码。

3.1 类定义

public abstract class Enum<E extends Enum<E>>
        implements Comparable<E>, Serializable {

//只可由子类访问,即枚举类
   protected Enum(String name, int ordinal) {
       this.name = name;
       this.ordinal = ordinal;
   }
}    

继承的接口:

  • Comparable
    说明枚举类实例之间可比较,但不同枚举类的实例不可比较大小。

Serializable接口
说明可序列化。

3.2 成员变量

//枚举的名称,以上面Season.SPRING为例,这里name就为"SPRING"
private final String name;

//枚举的顺序,从0开始,比如SPRING为0,SUMMER为1
private final int ordinal;

Enum类有两个成员变量:

  • name
    代表枚举类的名称
  • ordinal
    代表枚举类的初始化顺序

这两个字段被final关键字修饰,所以一个枚举类在构造时必须调用其父类Enum的构造方法完成赋值操作,前面同样也说过了编译器会自动修改子类的构造方法。

3.3 成员方法

3.3.1 继承自Object的方法

// Enum类的toString方法默认返回枚举类的名称(即name属性),子类可以重写该方法。
public String toString() {
    return name;
}
//实现和Object一样,只不过加了final防止重写,下面的hashCode也一样
public final boolean equals(Object other) {
    return this == other;
}

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

//不可重写finalize
protected final void finalize() { }

// Enum对象不能克隆
protected final Object clone() throws CloneNotSupportedException {
    throw new CloneNotSupportedException();
}

Enum类重写了Object类的四个方法:finalize,toString, hashCode, equals,clone方法
其中,finalize,hashCode, equals,clone方法被final关键字修饰,枚举类不可重写这些方法。

下面来分析这些方法被final关键字修饰的原因:

  • finalize
    由于枚举类的实例被static final变量强引用,所以不可被垃圾回收,自然重写finalize就没有任何意义了。
  • hashCode
    在Enum类中该方法的实现就是调用Object里的hashCode方法,枚举类的实例是不变的,并且重写这个方法并不会比Object的hashCode本地实现更快,如果你想深入了解Object里的hashCode方法实现原理,可以参考这篇博客:
    https://blog.csdn.net/xusiwei1236/article/details/45152201。
  • equals
    枚举类的实例是不变的,所以直接用==判断是否相等就可以了
  • clone
    枚举类的实例不可被克隆

3.3.2 实现自Comparable的方法

  • compareTo方法的实现是通过ordinal成员变量(即枚举类实例初始化顺序)判断大小,且该方法被final修饰,不可被重写。
  • 只可和同一个枚举类的枚举比较
public final int compareTo(E o) {
    Enum<?> other = (Enum<?>)o;
    Enum<E> self = this;
    if (self.getClass() != other.getClass() &&
        self.getDeclaringClass() != other.getDeclaringClass())
        throw new ClassCastException();
    //通过比较枚举的顺序判定大小
    return self.ordinal - other.ordinal;
}

3.3.3 Enum自定义方法

// 同toString,返回枚举name
public final String name() {
    return name;
}

// 返回枚举序号下标,从0开始
public final int ordinal() {
    return ordinal;
}

@SuppressWarnings("unchecked")
public final Class<E> getDeclaringClass() {
    Class<?> clazz = getClass();
    Class<?> zuper = clazz.getSuperclass();
    return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
}

public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name) {
    T result = enumType.enumConstantDirectory().get(name);
    if (result != null)
        return result;
    if (name == null)
        throw new NullPointerException("Name is null");
    throw new IllegalArgumentException(
        "No enum constant " + enumType.getCanonicalName() + "." + name);
}


private void readObject(ObjectInputStream in) throws IOException,
    ClassNotFoundException {
    throw new InvalidObjectException("can't deserialize enum");
}

private void readObjectNoData() throws ObjectStreamException {
    throw new InvalidObjectException("can't deserialize enum");
}
  • 静态方法valueOf
    • Enum提供了静态方法valueOf,使其可以通过枚举类对应Class类和实例的名称获取到枚举实例:

      • 比如调用Enum.valueOf(Season.class, "SPRING")返回的是Season.SPRING
      • 如果传入的name为null,则会抛出NullPointerException异常;
      • 如果没有找到对应的实例,则会抛出IllegalArgumentException异常。
    • 该方法通过反射获取实例。
      T result = enumType.enumConstantDirectory().get(name);
      Class#enumConstantDirectory方法如下:

    public final class Class<T> implements java.io.Serializable, GenericDeclaration,
    		Type, AnnotatedElement{
    
    	private volatile transient Map<String, T> enumConstantDirectory = null;
    
    	Map<String, T> enumConstantDirectory() {
            if (enumConstantDirectory == null) {
                T[] universe = getEnumConstantsShared();
                if (universe == null)
                    throw new IllegalArgumentException(getName() + " is not an enum type");
                Map<String, T> m = new HashMap<>(2 * universe.length);
                for (T constant : universe)
                    m.put(((Enum<?>)constant).name(), constant);
                enumConstantDirectory = m;
            }
            return enumConstantDirectory;
    	}
    	
        private volatile transient T[] enumConstants = null;
        
    	T[] getEnumConstantsShared() {
            if (enumConstants == null) {
                if (!isEnum()) return null;
                try {
    	            //注意这里
                    final Method values = getMethod("values");
                    java.security.AccessController.doPrivileged(
                        new java.security.PrivilegedAction<Void>() {
                            public Void run() {
                                    values.setAccessible(true);
                                    return null;
                                }
                            });
                    @SuppressWarnings("unchecked")
                    T[] temporaryConstants = (T[])values.invoke(null);
                    enumConstants = temporaryConstants;
                } catch (InvocationTargetException | NoSuchMethodException |
                       IllegalAccessException ex) { return null; }
            }
            return enumConstants;
        }
    }
    
    getEnumConstantsShared方法实际上是调用了Season枚举类的values方法返回了保存当前枚举类所有实例类的数组,再将这个数组赋值给Class对象的enumConstants成员变量并返回给enumConstantDirectory方法。enumConstantDirectory方法将这个数组里的实例以键值对形式(枚举名为键,对应的对象为值)保存到enumConstantDirectory这个Map中,并返回这个Map,然后直接调用Map的get方法并传入name就可以获取到我们想要的枚举对象了。

4 枚举类的特性详解

4.1 枚举类对象定义方法

枚举类支持定义方法,同时支持方法的重写(Override)。

枚举类的字节码也是个普通的类,支持定义方法自然就不用说了。但是枚举类的字节码是被final修饰的,final修饰的类是不能被继承的,我们一般方法的重写是子类重写父类的方法,既然枚举不能被继承,那重写又是怎么实现的呢?我们先看代码:

public enum Color {
    RED{
        @Override
        public void paint() {
            System.out.println("I am red");
        }
    },
    BLUE{
        @Override
        public void paint() {
            System.out.println("I am blue");
        }
    },
    DEFAULT;

    public void paint() {
        System.out.println("I am color");
    }

    public static void main(String[] args) {
        RED.paint();
        BLUE.paint();
        DEFAULT.paint();
    }
}

执行main()方法,输出如下:

I am red
I am blue
I am color

RED和BLUE不都是Color类的一个对象吗,怎么就可以重写方法了呢? 这里不难猜到,RED和BLUE应该都是Color类的子类的实例,是子类对方法进行了重写,为了验证我们的猜想,我们看看编译的class文件
在这里插入图片描述
熟悉匿名内部类的很容易就看出这个Color$1.class和Color$2.class是Color的匿名内部类了。
我们反编译一下Color$1.class,会发现final class Color$1 extends Color,居然继承了final修饰的Color枚举类?这应该是JVM内部机制才能这样搞。

  • 匿名内部类:
    • 使用匿名内部类时,我们必须是继承一个类或者实现一个接口,但是两者不可兼得,同时也只能继承一个类或者实现一个接口。
    • 匿名内部类中是不能定义构造函数的。
    • 匿名内部类中不能存在任何的静态成员变量和静态方法。
    • 匿名内部类为局部内部类,所以局部内部类的所有限制同样对匿名内部类生效。
    • 匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。

4.2 含有抽象方法的枚举类

  • 如果枚举类定义了抽象方法,枚举类的所有实例必须实现抽象方法。
  • 匿名内部类
    MONDAY本身就是一个AbstractWeek对象的引用。在初始化这个枚举类的时候,等同于执行的是AbstractWeek MONDAY= new AbstractWeek(0,“星期一”),然后用匿名内部类的方式实现getNextDay():
AbstractWeek MONDAY= new AbstractWeek (){
        @Override
        public AbstractWeek getNextDay() {
            return TUESDAY;
        }
};

该枚举类定义:

/**
 * 枚举类可以有抽象方法,但是必须在它的实例中实现
 */
public enum AbstractWeek {
 
    MONDAY(0,"星期一") {
        @Override
        public AbstractWeek getNextDay() {
            return TUESDAY;
        }
    }, TUESDAY(1,"星期二") {
        @Override
        public AbstractWeek getNextDay() {
            return WEDNESDAY;
        }
    }, WEDNESDAY(2,"星期三") {
        @Override
        public AbstractWeek getNextDay() {
            return THURSDAY;
        }
    }, THURSDAY(3,"星期四") {
        @Override
        public AbstractWeek getNextDay() {
            return FRIDAY;
        }
    }, FRIDAY(4,"星期五") {
        @Override
        public AbstractWeek getNextDay() {
            return SATURDAY;
        }
    }, SATURDAY(5,"星期六") {
        @Override
        public AbstractWeek getNextDay() {
            return SUNDAY;
        }
    }, SUNDAY(6,"星期日") {
        @Override
        public AbstractWeek getNextDay() {
            return MONDAY;
        }
    };
 
    private int num;
    private String desc;
 
    AbstractWeek(int num,String desc) {
        this.num = num;
        this.desc=desc;
    }
 
    // 定义一个抽象方法
    public abstract AbstractWeek getNextDay();
 
    public static void main(String[] args) {
        String nextDay=AbstractWeek.MONDAY.getNextDay().toString();
        System.out.println(nextDay);
    }
}

4.3 实现自定义的toString方法

toString()name()两个方法一样,返回当前枚举类变量的name属性,如果默认的toString()不能满足需求,我们可以结合switch来灵活的实现toString()方法:

/**
 * 用switch重写toString方法,提高代码健壮性
 * @return
 */
@Override
public String toString() {
    //switch支持Enum类型
    switch (this) {
        case MONDAY:
            return "今天星期一";
        case TUESDAY:
            return "今天星期二";
        case WEDNESDAY:
            return "今天星期三";
        case THURSDAY:
            return "今天星期四";
        case FRIDAY:
            return "今天星期五";
        case SATURDAY:
            return "今天星期六";
        case SUNDAY:
            return "今天星期日";
        default:
            return "Unknow Day";
    }
}

5 Enum的序列化

虽然说Enum实现了Serializable接口,但实际上枚举类的序列化只是将名称(成员变量name)输出,反序列化则是默认调用Enum静态方法valueOf。

可以看下面这一段测试代码:

public class Test {
	public static void main(String[] arg) 
			throws IOException, ClassNotFoundException {
		FileOutputStream fos = new FileOutputStream("D:\\out.txt");
		ObjectOutputStream oos = new ObjectOutputStream(fos);
		oos.writeObject(Season.SPRING);
		oos.close();
		FileInputStream fis = new FileInputStream("D:\\out.txt");
		ObjectInputStream ois = new ObjectInputStream(fis);
		Object obj = ois.readObject();
		System.out.println(obj.getClass().getName());
		System.out.println(obj == Season.SPRING);
	}
}

运行上述代码后,序列化文件out.txt内容为:
在这里插入图片描述
序列化文件只保存了name属性的值和枚举对象的类型

输出结果为:

com.test.Season
true

可以看出反序列化只是通过序列化文件中name属性调用valueOf返回Season类已经构造好的实例而已。

6 Enum应用

6.1 EnumSet

EnumSet是元素为枚举类的Set集合类,底层使用位域保存信息,速度比HashSet还要快。EnumSet是基于一个long类型的变量保存集合元素,一个long类型有64位(bit),每一个枚举元素需要1bit来表示该枚举元素是否处于EnumSet中。

@SafeVarargs
public static <E extends Enum<E>> EnumSet<E> of(E first, E... rest) {
    EnumSet<E> result = noneOf(first.getDeclaringClass());
    result.add(first);
    for (E e : rest)
        result.add(e);
    return result;
}

public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {
    Enum<?>[] universe = getUniverse(elementType);
    if (universe == null)
        throw new ClassCastException(elementType + " not an enum");

    if (universe.length <= 64)
        return new RegularEnumSet<>(elementType, universe);
    else
        return new JumboEnumSet<>(elementType, universe);
}

可以看出,当枚举类的枚举量少于64时,会使用RegularEnumSet,否则会使用JumboEnumSet,这两个类都是EnumSet的子类,我们简单地看一下这两个类。

class RegularEnumSet<E extends Enum<E>> extends EnumSet<E> {
	/**
     * Bit vector representation of this set.  The 2^k bit indicates the
     * presence of universe[k] in this set.
     */
    private long elements = 0L;
    
	/**
     * Adds the specified element to this set if it is not already present.
     *
     * @param e element to be added to this set
     * @return <tt>true</tt> if the set changed as a result of the call
     *
     * @throws NullPointerException if <tt>e</tt> is null
     */
    public boolean add(E e) {
        typeCheck(e);

        long oldElements = elements;
        elements |= (1L << ((Enum<?>)e).ordinal());
        return elements != oldElements;
    }

    /**
     * Removes the specified element from this set if it is present.
     *
     * @param e element to be removed from this set, if present
     * @return <tt>true</tt> if the set contained the specified element
     */
    public boolean remove(Object e) {
        if (e == null)
            return false;
        Class<?> eClass = e.getClass();
        if (eClass != elementType && eClass.getSuperclass() != elementType)
            return false;

        long oldElements = elements;
        elements &= ~(1L << ((Enum<?>)e).ordinal());
        return elements != oldElements;
    }
}

RegularEnumSet是用一个long类型的变量elements来表示集合里的元素:

  • 添加元素关键代码:
elements |= (1L << ((Enum<?>)e).ordinal());
  • 删除元素关键代码:
elements &= ~(1L << ((Enum<?>)e).ordinal());
class JumboEnumSet<E extends Enum<E>> extends EnumSet<E> {
 /**
     * Bit vector representation of this set.  The ith bit of the jth
     * element of this array represents the  presence of universe[64*j +i]
     * in this set.
     */
    private long elements[];
}

JumboEnumSet只是用long类型的数组elements代替long类型的变量elements来保存数量超过64的枚举量,集合类操作具体的实现细节就不细说了。

6.2 EnumMap

如果Map的Key类型是枚举类型的话, 相比于HashMap,更推荐使用EnumMap。EnumMap的key只能是Enum类,底层实现是基于数组的。一般用于保存一个枚举类对应的额外的业务信息。下面简单看下其部分代码:

public class EnumMap<K extends Enum<K>, V> extends AbstractMap<K, V>
    implements java.io.Serializable, Cloneable
{
    /**
     * The <tt>Class</tt> object for the enum type of all the keys of this map.
     *
     * @serial
     */
    private final Class<K> keyType;

    /**
     * All of the values comprising K.  (Cached for performance.)  
     * 这个数组对应枚举类的所有枚举量
     */
    private transient K[] keyUniverse;

    /**
     * Array representation of this map.  The ith element is the value
     * to which universe[i] is currently mapped, or null if it isn't
     * mapped to anything, or NULL if it's mapped to null.
     * 这里就是枚举量对应的value的值的存放的数组
     */
    private transient Object[] vals;

	public V put(K key, V value) {
        typeCheck(key);
		//用枚举量的ordinal值,作为vals数组的index,存放其对应value值
        int index = key.ordinal();
        Object oldValue = vals[index];
        vals[index] = maskNull(value);
        if (oldValue == null)
            size++;
        return unmaskNull(oldValue);
    }
}

6.3 单例模式的最佳实现:枚举

6.3.1 饿汉式单例:

public class People{
	public static final People INSTANCE = new People();
	private People(){
	}
	public void eat(){
	}
}

6.3.2 基于静态工厂方法的饿汉式单例:

public class People{
	private static final People INSTANCE = new People();
	private People(){
	}
	public static People getInstance(){
		return INSTANCE;
	}
	public void eat(){
	}
}

为了使以上实现的单例类变成可序列化的,仅仅在类的声明中添加implements Serializable是不够的。为了维护并保证单例,必须所有声明的实例变量都是transient的,并提供一个readResolve方法。否则,每次反序列化一个序列化的实例的时候,都会创建一个新的实例。

private Object readResolve(){
	return INSTANCE;
}

对于一个正在被反序列化的对象,如果它的类定义了一个readResolve方法,并且具有正确的声明,那么在反序列化之后,新建对象上的readResolve方法就会被调用。然后,该方法返回的对象引用将被返回,取代新建的对象。

6.3.3 基于双检查锁机制的懒汉式单例(线程安全)

public class People{  
    //使用volatile关键字保其可见性  
    volatile private static People INSTANCE = null;  
      
    private People (){}  
       
    public static People getInstance() {  
        try {    
	            if(INSTANCE == null){
	                synchronized (People.class) {  
	                    if(INSTANCE == null){//二次检查  
	                        INSTANCE = new People();  
	                    }   
		            }
	            }   
        } catch (InterruptedException e) {   
            e.printStackTrace();  
        }  
        return INSTANCE ;  
    }  
}  

6.3.4 基于静态内部类的懒汉式单例(线程安全)

静态内部类-延迟装载 推荐方法

  • 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例没有绑定关系,而且只有静态内部类被调用到时才会装载,从而实现了延迟加载。
  • 而且因为JVM对static修饰的变量线程安全
public class People{  
    //内部类  
    private static class PeopleHolder{  
        private static People instance = new People();  
    }   
    private People(){}  
       
    public static People getInstance() {   
        return PeopleHolder.instance;  
    }  
}  

这种使用内部类的懒汉式单例模式相比于双检查锁代码更加简洁易懂,借助JVM类加载时的锁机制(确保一个类只会被同一ClassLoader加载一次),实现线程安全的、延迟加载的单例模式。

6.3.5 基于枚举类的单例

虽然这种方法还没有被广泛采用,但是单元素的枚举类型已经成为实现Singleton的最佳方法:

public enum SingleClass6{
	INSTANCE;
	/**
     * 对于一个正在被反序列化的对象,如果它的类定义了一个`readResolve`方法,并且具有正确的声明,
     * 那么在反序列化之后,新建对象上的readResolve方法就会被调用。
     * 然后,该方法返回的对象引用将被返回,取代新建的对象。
     * 所以可保证在反序列化时依然是旧对象
     * @return
     */
    private Object readResolve(){
        return INSTANCE;
    }
    // 防止反射建立新对象
	private SingleClass6(){
        throw new AssertionError();
    }
}
  • 使用枚举实现单例将更加简洁
  • 无偿地提供了序列化机制
    父类Enum实现了Serializable接口
  • 绝对防止多次实例化,即使是在面对复杂的序列化或者反射攻击的时候。

6.3.6 基于静态工厂方法和枚举类单例:

public class People{
	public enum PeopleEnum{
		INSTANCE;
		private People instance;
		private PeopleEnum(){
            instance = new People();  
        }
        public People getInstance(){  
            return instance;  
        }  
	}
	private People(){
	}
	public static People getInstance(){
		return PeopleEnum.INSTANCE getInstance().;
	}
	public void eat(){
	}
}

个人觉得这么写虽然没有暴露内部实现,但是还不如直接写成前面的 基于静态内部类的懒汉式单例来的简洁,这样子使用枚举实现单例并没有体现出什么优势。

6.4 职责链模式的枚举实现

public class Mail{
	String type;
	public Mail(String type){
		this.type = type;
	}
}
public class PostOffice{
    enum MailHandler{
        A{
            @Override
            boolean handle(Mail m){
                if (m.type == "A"){
                    System.out.println("A handle success");
                    return true;
                }else {
                    return false;
                }
            }
        },
        B{
            @Override
            boolean handle(Mail m){
                if (m.type == "B"){
                    System.out.println("B handle success");
                    return true;
                }else {
                    return false;
                }
            }
        },
        C{
            @Override
            boolean handle(Mail m){
                if (m.type == "C"){
                    System.out.println("C handle success");
                    return true;
                }else {
                    return false;
                }
            }
        };
        abstract boolean handle(Mail m);
    }

    static void handler(Mail m){
        for(MailHandler handler : MailHandler.values()){
            if (handler.handle(m)) {
                return;
            }

        }
        System.out.println("handle fail");
    }

    public static void main(String[] args) {
        Mail mailA = new Mail("A");
        Mail mailB = new Mail("B");
        Mail mailC = new Mail("C");
        Mail mailD = new Mail("D");

        handler(mailA);
        System.out.println("-----------");
        handler(mailB);
        System.out.println("-----------");
        handler(mailC);
        System.out.println("-----------");
        handler(mailD);
    }
}

上面的例子简化了《thinking in java》的例子,其运行的输出如下:

A handle success
-----------
B handle success
-----------
C handle success
-----------
handle fail

通常实现职责链模式是通过继承抽象类或者实现接口,链中每新增一个新的元素,就新建一个类,然后通过修改配置文件,或者修改代码将该类加入责任链中。

Enum给责任链的实现提供了一个新的思路,链中新增一个元素只需要新增一个枚举量。

但是也有一定的不足,使用Enum实现的职责链,链中元素不能被其他链复用自由组合,是否使用Enum去实现职责链还是要根据实际情况去取舍。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值