Java-deep enum

澳门新浦京娱乐游戏 2

本文由码农网 –
小峰原创翻译,转载请看清文末的转载要求,欢迎参与我们的付费投稿计划!

概念

枚举类型(Enumerated Type)
很早就出现在编程语言中,它被用来将一组类似的值包含到一种类型当中。而这种枚举类型的名称则会被定义成独一无二的类型描述符,在这一点上和常量的定义相似。不过相比较常量类型,枚举类型可以为申明的变量提供更大的取值范围。

一般来说,Map是一种由键值对组成的数据结构,其中键(key)在Map中是不能重复的;

Java
Enum是一个非常有用的功能,但很多人通常并不能充分利用,这是因为一些库不会优先择用该功能。通常我们也可以正确使用Java枚举功能,但在许多代码库中往往存在着这样一个问题,于是有了这篇文章。问题很简单:我们应该如何通过名称或值获取枚举,并忽略不存在的值?

用法

本篇文章总结了9个最常见的问题(关于Java Map及其实现类);

枚举

这是我们将在示例中使用的枚举。
挑选更复杂的枚举是为了也可以通过其他字段展示查找枚举。

public enum CardColor {
    RED,
    BLACK,
    ;
}
// Jackson annotation to print the enum as an Object instead of the default name.
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum CardSuit {
    // Unicode suits - https://en.wikipedia.org/wiki/Playing_cards_in_Unicode
    SPADE("Spade", String.valueOf((char) 0x2660), CardColor.BLACK),
    HEART("Heart", String.valueOf((char) 0x2665), CardColor.RED),
    DIAMOND("Diamond", String.valueOf((char) 0x2666), CardColor.RED),
    CLUB("Club", String.valueOf((char) 0x2663), CardColor.BLACK),
    ;
    private String displayName;
    private String symbol;
    private CardColor color;
    private CardSuit(String displayName, String symbol, CardColor color) {
        this.displayName = displayName;
        this.symbol =  symbol;
        this.color = color;
    }
    public String getDisplayName() {
        return displayName;
    }
    public void setDisplayName(String displayName) {
        this.displayName = displayName;
    }
    public String getSymbol() {
        return symbol;
    }
    public void setSymbol(String symbol) {
        this.symbol = symbol;
    }
    public CardColor getColor() {
        return color;
    }
    public void setColor(CardColor color) {
        this.color = color;
    }

在 GitHub 上查看。

常量

public enum Color {  
  RED, GREEN, BLANK, YELLOW  
}  

出于简单考虑,在代码例子中我将不使用泛型,因此,我将仅仅写上Map而不是写上Map<K,
V>,且Map中的Key和Value都是可以比较的,意味着K和V都实现了Comparable接口;

问题

在你知道输入有效的时候,使用Enum.valueOf非常棒。但是,如果传入无效的名称,那么将抛出异常。在某些情况下,这很好。不过,在通常情况下,我们宁愿忽略异常并返回null。

log.debug("Running valueOf");
for (String name : names) {
    try {
        log.debug("looking up {} found {}", name, Json.serializer().toString(CardSuit.valueOf(name)));
    } catch (Exception ex) {
        log.warn("Exception Thrown", ex);
    }
}


GitHub 上查看。

2017-02-22 14:46:38.556 [main] DEBUG c.s.examples.common.EnumLookup - Running valueOf
2017-02-22 14:46:38.804 [main] DEBUG c.s.examples.common.EnumLookup - looking up SPADE found {"displayName":"Spade","symbol":"♠","color":"BLACK"}
2017-02-22 14:46:38.806 [main] DEBUG c.s.examples.common.EnumLookup - looking up HEART found {"displayName":"Heart","symbol":"♥","color":"RED"}
2017-02-22 14:46:38.806 [main] DEBUG c.s.examples.common.EnumLookup - looking up DIAMOND found {"displayName":"Diamond","symbol":"♦","color":"RED"}
2017-02-22 14:46:38.806 [main] DEBUG c.s.examples.common.EnumLookup - looking up CLUB found {"displayName":"Club","symbol":"♣","color":"BLACK"}
2017-02-22 14:46:38.808 [main] WARN  c.s.examples.common.EnumLookup - Exception Thrown
java.lang.IllegalArgumentException: No enum constant com.stubbornjava.examples.common.EnumLookup.CardSuit.Missing
  at java.lang.Enum.valueOf(Enum.java:238)
  at com.stubbornjava.examples.common.EnumLookup$CardSuit.valueOf(EnumLookup.java:1)
  at com.stubbornjava.examples.common.EnumLookup.main(EnumLookup.java:154)

switch

enum Signal {  
    GREEN, YELLOW, RED  
}  
public class TrafficLight {  
    Signal color = Signal.RED;  
    public void change() {  
        switch (color) {  
        case RED:  
            color = Signal.GREEN;  
            break;  
        case YELLOW:  
            color = Signal.RED;  
            break;  
        case GREEN:  
            color = Signal.YELLOW;  
            break;  
        }  
    }  
}  

1.Map转List

在Java中,Map接口提供了三个集合视图,即key set, value set,和key-value
set。它们都可以转成List集合,如下代码所示:

// key list
List keyList = new ArrayList(map.keySet());
// value list
List valueList = new ArrayList(map.valueSet());
// key-value list
List entryList = new ArrayList(map.entrySet());

拙劣的实现

很不幸的是,以下两种方法在代码库中出现得是这么的频繁。反面例子,勿学。

向枚举中添加新方法

public enum Color {  
    RED("红色", 1), GREEN("绿色", 2), BLANK("白色", 3), YELLO("黄色", 4);  
    // 成员变量  
    private String name;  
    private int index;  
    // 构造方法  
    private Color(String name, int index) {  
        this.name = name;  
        this.index = index;  
    }  
    // 普通方法  
    public static String getName(int index) {  
        for (Color c : Color.values()) {  
            if (c.getIndex() == index) {  
                return c.name;  
            }  
        }  
        return null;  
    }  
    // get set 方法  
    public String getName() {  
        return name;  
    }  
    public void setName(String name) {  
        this.name = name;  
    }  
    public int getIndex() {  
        return index;  
    }  
    public void setIndex(int index) {  
        this.index = index;  
    }  
}  

2.迭代遍历Map中的key-value

迭代遍历Map中的键值对是最基本的操作。在Java中,键值对信息存储在Map.Entry类中。通过调用Map.entrySet()方法会返回键值对集合,因此最有效的遍历Map中的键值对如下代码所示:

for(Entry entry: map.entrySet()) {
    // get key
    K key = entry.getKey();
    // get value
    V value = entry.getValue();
}

当然,也可以通过迭代器遍历,如下代码:

while(itr.hasNext()) {
    Entry entry = itr.next();
    // get key
    K key = entry.getKey();
    // get value
    V value = entry.getValue();
}

Enum.valueOf With Try Catch(劣)

这种拙劣的做法最常见于初学者。异常不应该用于控制流,并且这样可能会有一些性能影响。不要偷懒。你必须用正确的方式去做。

/*
 * Please don't do this! Using try / catch for
 * control flow is a bad practice.
 */
public static CardSuit trycatchValueOf(String name) {
    try {
        return CardSuit.valueOf(name);
    } catch (Exception ex) {
        log.warn("Exception Thrown", ex);
        return null;
    }
}


GitHub 上查看。

log.debug("Running trycatchValueOf");
for (String name : names) {
    log.debug("looking up {} found {}", name, Json.serializer().toString(CardSuit.trycatchValueOf(name)));
}


澳门新浦京娱乐游戏,GitHub
上查看。

2017-02-22 14:46:38.809 [main] DEBUG c.s.examples.common.EnumLookup - Running trycatchValueOf
2017-02-22 14:46:38.809 [main] DEBUG c.s.examples.common.EnumLookup - looking up SPADE found {"displayName":"Spade","symbol":"♠","color":"BLACK"}
2017-02-22 14:46:38.809 [main] DEBUG c.s.examples.common.EnumLookup - looking up HEART found {"displayName":"Heart","symbol":"♥","color":"RED"}
2017-02-22 14:46:38.809 [main] DEBUG c.s.examples.common.EnumLookup - looking up DIAMOND found {"displayName":"Diamond","symbol":"♦","color":"RED"}
2017-02-22 14:46:38.809 [main] DEBUG c.s.examples.common.EnumLookup - looking up CLUB found {"displayName":"Club","symbol":"♣","color":"BLACK"}
2017-02-22 14:46:38.809 [main] WARN  c.s.examples.common.EnumLookup - Exception Thrown
java.lang.IllegalArgumentException: No enum constant com.stubbornjava.examples.common.EnumLookup.CardSuit.Missing
  at java.lang.Enum.valueOf(Enum.java:238)
  at com.stubbornjava.examples.common.EnumLookup$CardSuit.valueOf(EnumLookup.java:1)
  at com.stubbornjava.examples.common.EnumLookup$CardSuit.trycatchValueOf(EnumLookup.java:89)
  at com.stubbornjava.examples.common.EnumLookup.main(EnumLookup.java:171)
2017-02-22 14:46:38.809 [main] DEBUG c.s.examples.common.EnumLookup - looking up Missing found null

覆盖枚举的方法

public enum Color {  
    RED("红色", 1), GREEN("绿色", 2), BLANK("白色", 3), YELLO("黄色", 4);  
    // 成员变量  
    private String name;  
    private int index;  
    // 构造方法  
    private Color(String name, int index) {  
        this.name = name;  
        this.index = index;  
    }  
    //覆盖方法  
    @Override  
    public String toString() {  
        return this.index+"_"+this.name;  
    }  
}  

3.Map中的元素按Key排序

对Map中的元素按key排序是另一个经常使用的操作。有一种方法是将Map中的元素放进List集合,然后通过一个比较器对集合进行排序,如下代码示例:

        List list = new ArrayList(map.entrySet());
        Collections.sort(list, new Comparator() {
            public int compare(Entry e1, Entry e2) {
                return e1.getKey().compareTo(e2.getKey());
                }
            });
        }

另一种方法是使用SortedMap,其提供了对Key进行排序的功能;前提是Map中的Key需要实现Comparable接口或者通过构造方法传入比较器;

TreeMap是SortedMap的一个实现类,它的构造方法能够接收一个比较器,以下代码展示了如何将普通Map转成SortedMap:

SortedMap sortedMap = new TreeMap(new Comparator() {
    @Override
    public int compare(K k1, K k2) {
    return k1.compareTo(k2);
    }
});
sortedMap.putAll(map);

通过迭代查找(劣)

这种方法也很常见(见这里),但至少程序员知道不能用try/catch来捕获异常。那么,这种方法有什么问题呢?没错,它会迭代所有枚举,直到找到匹配的枚举或返回null——最坏的情况下需要n次,其中n就是枚举值的数量。有些人可能会认为这微不足道,这是过早优化了而已。但是,数据结构和算法是CS基础。使用Map而不是迭代集合要省力得多。这会大大提高性能吗?不,但它是一个很好的习惯。在面试候选人时,你会对线性复杂度搜索算法感到满意吗?此时,你不应该让这样的代码审查通过。

/*
 * Please don't do this! It is inefficient and it's
 * not very hard to use Guava or a static Map as an index.
 */
public static CardSuit iterationFindByName(String name) {
    for (CardSuit suit : CardSuit.values()) {
        if (name.equals(suit.name())) {
            return suit;
        }
    }
    return null;
}


GitHub
上查看。

log.debug("Running iteration");
for (String name : names) {
    log.debug("looking up {} found {}", name, Json.serializer().toString(CardSuit.iterationFindByName(name)));
}


GitHub
上查看。

2017-02-22 14:46:38.808 [main] DEBUG c.s.examples.common.EnumLookup - Running iteration
2017-02-22 14:46:38.809 [main] DEBUG c.s.examples.common.EnumLookup - looking up SPADE found {"displayName":"Spade","symbol":"♠","color":"BLACK"}
2017-02-22 14:46:38.809 [main] DEBUG c.s.examples.common.EnumLookup - looking up HEART found {"displayName":"Heart","symbol":"♥","color":"RED"}
2017-02-22 14:46:38.809 [main] DEBUG c.s.examples.common.EnumLookup - looking up DIAMOND found {"displayName":"Diamond","symbol":"♦","color":"RED"}
2017-02-22 14:46:38.809 [main] DEBUG c.s.examples.common.EnumLookup - looking up CLUB found {"displayName":"Club","symbol":"♣","color":"BLACK"}
2017-02-22 14:46:38.809 [main] DEBUG c.s.examples.common.EnumLookup - looking up Missing found null

实现接口

所有的枚举都继承自java.lang.Enum类。由于Java
不支持多继承,所以枚举对象不能再继承其他类。

public interface Behaviour {  
    void print();  
    String getInfo();  
}  
public enum Color implements Behaviour{  
    RED("红色", 1), GREEN("绿色", 2), BLANK("白色", 3), YELLO("黄色", 4);  
    // 成员变量  
    private String name;  
    private int index;  
    // 构造方法  
    private Color(String name, int index) {  
        this.name = name;  
        this.index = index;  
    }  
//接口方法  
    @Override  
    public String getInfo() {  
        return this.name;  
    }  
    //接口方法  
    @Override  
    public void print() {  
        System.out.println(this.index+":"+this.name);  
    }  
}  

4.Map中的元素按Value排序

同样可以参考前面的将Map的元素放进List集合然后传入比较器进行排序,不过这次要比较的对象是Entry.getValue()。如下代码所示:

List list = new ArrayList(map.entrySet());
Collections.sort(list, new Comparator() {
    @Override
    public int compare(Entry e1, Entry e2) {
        return e1.getValue().compareTo(e2.getValue());
    }
});

对于这个问题,当map中的value都是唯一的情况,我们仍然可以使用sorted
map来排序,在这种情况下,你可以将key赋值给value,value赋值给key来实现该功能;但是这种解决方法十分受限,非常不建议使用;

较优的实现

以下都可以通过使用Map形式的索引起作用。但是,它们之间也有一些细微的差别。

静态Map索引(较优)

用于快速查找固定大小的正确数据结构是什么?那就是HashMap。现在通过一些额外的样板,只要我们有很好的散列函数的话,我们就可以进行更有效的查找。稍微更冗长,但如果有办法减少样板的话,这将是极好的。

private static final Map<String, CardSuit> nameIndex =
        Maps.newHashMapWithExpectedSize(CardSuit.values().length);
static {
    for (CardSuit suit : CardSuit.values()) {
        nameIndex.put(suit.name(), suit);
    }
}
public static CardSuit lookupByName(String name) {
    return nameIndex.get(name);
}


GitHub
上查看。

log.debug("Running lookupByName");
for (String name : names) {
    log.debug("looking up {} found {}", name, Json.serializer().toString(CardSuit.lookupByName(name)));
}


GitHub
上查看。

2017-02-22 14:46:38.809 [main] DEBUG c.s.examples.common.EnumLookup - Running lookupByName
2017-02-22 14:46:38.809 [main] DEBUG c.s.examples.common.EnumLookup - looking up SPADE found {"displayName":"Spade","symbol":"♠","color":"BLACK"}
2017-02-22 14:46:38.810 [main] DEBUG c.s.examples.common.EnumLookup - looking up HEART found {"displayName":"Heart","symbol":"♥","color":"RED"}
2017-02-22 14:46:38.810 [main] DEBUG c.s.examples.common.EnumLookup - looking up DIAMOND found {"displayName":"Diamond","symbol":"♦","color":"RED"}
2017-02-22 14:46:38.813 [main] DEBUG c.s.examples.common.EnumLookup - looking up CLUB found {"displayName":"Club","symbol":"♣","color":"BLACK"}
2017-02-22 14:46:38.813 [main] DEBUG c.s.examples.common.EnumLookup - looking up Missing found null

Guava Enums.getIfPresent(推荐)

这是一个常见的用例,我们在Google的朋友为此提供了一个非常干净且无需样板的解决方案。探其究竟,它甚至使用了WeakReferences和WeakHashMaps。基本说来,这段代码将创建一个全局的静态地图,并以Enum的类名称键入,并将其用于查找。

public static CardSuit getIfPresent(String name) {
    return Enums.getIfPresent(CardSuit.class, name).orNull();
}


GitHub
上查看。

log.debug("Running Guava getIfPresent");
for (String name : names) {
    log.debug("looking up {} found {}", name, Json.serializer().toString(CardSuit.getIfPresent(name)));
}


GitHub
上查看。

2017-02-22 14:46:38.813 [main] DEBUG c.s.examples.common.EnumLookup - Running Guava getIfPresent
2017-02-22 14:46:38.814 [main] DEBUG c.s.examples.common.EnumLookup - looking up SPADE found {"displayName":"Spade","symbol":"♠","color":"BLACK"}
2017-02-22 14:46:38.814 [main] DEBUG c.s.examples.common.EnumLookup - looking up HEART found {"displayName":"Heart","symbol":"♥","color":"RED"}
2017-02-22 14:46:38.815 [main] DEBUG c.s.examples.common.EnumLookup - looking up DIAMOND found {"displayName":"Diamond","symbol":"♦","color":"RED"}
2017-02-22 14:46:38.815 [main] DEBUG c.s.examples.common.EnumLookup - looking up CLUB found {"displayName":"Club","symbol":"♣","color":"BLACK"}
2017-02-22 14:46:38.815 [main] DEBUG c.s.examples.common.EnumLookup - looking up Missing found null

使用接口组织枚举

有时候,我们可能需要对一组数据进行分类,比如进行食物菜单分类而且希望这些菜单都属于food类型,appetizer(开胃菜)、mainCourse(主菜)、dessert(点心)、Coffee等,每种分类下有多种具体的菜式或食品,此时可以利用接口来组织,如下(代码引用自Thinking
in Java):

public interface Food {
  enum Appetizer implements Food {
    SALAD, SOUP, SPRING_ROLLS;
  }
  enum MainCourse implements Food {
    LASAGNE, BURRITO, PAD_THAI,
    LENTILS, HUMMOUS, VINDALOO;
  }
  enum Dessert implements Food {
    TIRAMISU, GELATO, BLACK_FOREST_CAKE,
    FRUIT, CREME_CARAMEL;
  }
  enum Coffee implements Food {
    BLACK_COFFEE, DECAF_COFFEE, ESPRESSO,
    LATTE, CAPPUCCINO, TEA, HERB_TEA;
  }
}

public class TypeOfFood {
  public static void main(String[] args) {
    Food food = Appetizer.SALAD;
    food = MainCourse.LASAGNE;
    food = Dessert.GELATO;
    food = Coffee.CAPPUCCINO;
  }
} 
public interface Food {  
    enum Coffee implements Food{  
        BLACK_COFFEE,DECAF_COFFEE,LATTE,CAPPUCCINO  
    }  
    enum Dessert implements Food{  
        FRUIT, CAKE, GELATO  
    }  
}  

5.初始化一个不可修改的Map

当你想要使用Map来存储一些常量时,将他们存储到一个不可修改的Map是一个好习惯。这种防御性编程技术会帮助你创建一个能安全使用且线程安全的map;

我们可以使用静态代码块来初始化一个静态不可变的map,如下代码:

public class Test {
private static final Map map;
    static {
        map = new HashMap();
        map.put(1, "one");
        map.put(2, "two");
    }
}

然而以上代码是有问题的,即使map被声明成static
final,我们仍然可以操作该map,如Test.map.put(3,”three”);因此,这不是不可变的map;

为了创建一个不可变的map,我们需要一个额外的map对象,将它作为入参传入Collections.unmodifiableMap方法中,然后得到一个不可变的Map,如下代码:

public class Test {
private static final Map map;
static {
Map aMap = new HashMap();
aMap.put(1, "one");
aMap.put(2, "two");
map = Collections.unmodifiableMap(aMap);
}
}

此时,当我们运行Test.map.put(3,”three”)将抛出UnsupportedOperationException异常;

Google的Guava库支持多种方式创建不可变的Map,想学习的可以参考其官方使用手册;

通过字段的进一步索引

这个完全相同的方法可以用于枚举的其他字段。想要通过其显示的名称或其他属性来查找枚举并不少见。

通过字段索引的静态Map(较优)

与上述相同的方法,但是在显示名称上而不是枚举名称上进行索引。

private static final Map<String, CardSuit> displayNameIndex =
        Maps.newHashMapWithExpectedSize(CardSuit.values().length);
static {
    for (CardSuit suit : CardSuit.values()) {
        displayNameIndex.put(suit.getDisplayName(), suit);
    }
}
public static CardSuit lookupByDisplayName(String name) {
    return displayNameIndex.get(name);
}


GitHub
上查看。

log.debug("Running lookupByDisplayName");
for (String displayName : displayNames) {
    log.debug("looking up {} found {}", displayName, Json.serializer().toString(CardSuit.lookupByDisplayName(displayName)));
}


GitHub
上查看。

2017-02-22 14:46:38.815 [main] DEBUG c.s.examples.common.EnumLookup - Running lookupByDisplayName
2017-02-22 14:46:38.815 [main] DEBUG c.s.examples.common.EnumLookup - looking up Spade found {"displayName":"Spade","symbol":"♠","color":"BLACK"}
2017-02-22 14:46:38.815 [main] DEBUG c.s.examples.common.EnumLookup - looking up Heart found {"displayName":"Heart","symbol":"♥","color":"RED"}
2017-02-22 14:46:38.815 [main] DEBUG c.s.examples.common.EnumLookup - looking up Diamond found {"displayName":"Diamond","symbol":"♦","color":"RED"}
2017-02-22 14:46:38.816 [main] DEBUG c.s.examples.common.EnumLookup - looking up Club found {"displayName":"Club","symbol":"♣","color":"BLACK"}
2017-02-22 14:46:38.816 [main] DEBUG c.s.examples.common.EnumLookup - looking up Missing found null

通过字段索引的静态Map(较优)

我们不能在这里利用Guava,因为对于静态索引创建唯一的全局密钥将是一件困难的事。但是,这并不意味着我们没有帮手!

public class EnumUtils {
    public static <T, E extends Enum<E>> Function<T, E> lookupMap(Class<E> clazz, Function<E, T> mapper) {
        @SuppressWarnings("unchecked")
        E[] emptyArray = (E[]) Array.newInstance(clazz, 0);
        return lookupMap(EnumSet.allOf(clazz).toArray(emptyArray), mapper);
    }
    public static <T, E extends Enum<E>> Function<T, E> lookupMap(E[] values, Function<E, T> mapper) {
        Map<T, E> index = Maps.newHashMapWithExpectedSize(values.length);
        for (E value : values) {
            index.put(mapper.apply(value), value);
        }
        return (T key) -> index.get(key);
    }
}


GitHub
上查看。

现在我们有了一个相与样板没多大关联的通用解决方案。

private static final Function<String, CardSuit> func =
        EnumUtils.lookupMap(CardSuit.class, e -> e.getDisplayName());
public static CardSuit lookupByDisplayNameUtil(String name) {
    return func.apply(name);
}


GitHub
上查看。

log.debug("Running lookupByDisplayNameUtil");
for (String displayName : displayNames) {
    log.debug("looking up {} found {}", displayName, Json.serializer().toString(CardSuit.lookupByDisplayNameUtil(displayName)));
}


GitHub
上查看。

2017-02-22 14:46:38.816 [main] DEBUG c.s.examples.common.EnumLookup - Running lookupByDisplayNameUtil
2017-02-22 14:46:38.816 [main] DEBUG c.s.examples.common.EnumLookup - looking up Spade found {"displayName":"Spade","symbol":"♠","color":"BLACK"}
2017-02-22 14:46:38.816 [main] DEBUG c.s.examples.common.EnumLookup - looking up Heart found {"displayName":"Heart","symbol":"♥","color":"RED"}
2017-02-22 14:46:38.816 [main] DEBUG c.s.examples.common.EnumLookup - looking up Diamond found {"displayName":"Diamond","symbol":"♦","color":"RED"}
2017-02-22 14:46:38.816 [main] DEBUG c.s.examples.common.EnumLookup - looking up Club found {"displayName":"Club","symbol":"♣","color":"BLACK"}
2017-02-22 14:46:38.816 [main] DEBUG c.s.examples.common.EnumLookup - looking up Missing found null

枚举与单例模式

public enum  Singleton {
    INSTANCE;
    private int id;
    private String name;
    public String getId(){
        return id;
    }
    public String getName(){
        return name;
    }
    public void setName(int id, String name){
        this.id = id;
        this.name = name;
    }
}

完全不用考虑序列化和反射的问题。枚举序列化是由jvm保证的,每一个枚举类型和定义的枚举变量在JVM中都是唯一的,在枚举类型的序列化和反序列化上,Java做了特殊的规定:在序列化时Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。同时,编译器是不允许任何对这种序列化机制的定制的并禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法,从而保证了枚举实例的唯一性,这里我们不妨再次看看Enum类的valueOf方法:

6.HashMap, TreeMap, 和Hashtable的区别

HashMap, TreeMap,
和Hashtable是Map接口的三个主要实现类,它们之间的主要区别如下:

  • 是否有序。HashMap和HashTable不会保证元素的有序性,随着时间推移,元素的顺序可能会发生改变;然而TreeMap是有序的,会根据自然顺序或依据比较器排序;
  • key-
    value限制。HashMap允许key和value为null(key只能有一个为null,因为key不能重复)。HashTable不允许key
    或value为null;TreeMap如果使用自然顺序或者比较器不允许key为null,使用null
    key将抛出异常;另外,在TreeMap中value是可以为null的;
  • 是否同步。只有Hashtable是同步的,其它的不是。如果线程安全不在考虑范围的话,强烈建议使用HashTable。

另一个比较完整的比较如下:

澳门新浦京娱乐游戏 1

它们之间的更多比较信息可参考如下链接:

结论

这里有若干方法都可用于解决同一个问题。有些拙劣,有些更优。

关于枚举集合的使用

java.util.EnumSet和java.util.EnumMap是两个枚举集合。EnumSet保证集合中的元素不重复;EnumMap中的key是enum类型,而value则可以是任意类型。关于这个两个集合的使用就不在这里赘述,可以参考JDK文档。

7.反向查找Map

有时候,我们需要key和value都唯一,这种情况允许我们创建一个双向查找Map,也就是说,可以通过唯一的value去查找唯一的Key,遗憾的是,JDK并不支持该数据结构;

Apache Common Collections 和 Guava
提供了对双向查找Map的支持,被叫做BidiMap和BiMap,它们规定了key和value必须是1对1的关系。

基本原理

定义一个Enum,通过编译之后的字节码,我们可以发现其实现原理。
编译器是在为我们创建一个类,这个类继承自
java.lang.Enum,有两个公共的、静态的、被声明成final的属性,它们的类型就是我们定义的FruitEnum。

编译器还生成了一个静态初始话器,就是字节码中static{};这一行下面的代码,其中的字节码创建了两个FruitEnum对象,同时分别赋值给APPLE和ORANGE这两个属性,调用的构造函数是定义在java.lang.Enum中的protected
Enum(String name, int ordinal)方法。

在创建完成两个FruitEnum对象并且分别赋值给APPLE和ORIGIN之后,还创建了一个名叫ENUM$VALUES的数组,然后把APPLE和ORIGIN按照定义的顺序放如这个数组中。

除了这个静态初始化器之外,编译器还为我们生成了两个静态方法,values()和
valueOf(java.lang.String)方法。其中values()方法将ENUM$VALUES数组拷贝一份然后返回,而valueOf(java.lang.String)方法则会调用java.lang.Enum类中的valueOf方法,其作用是根据参数名找到对应的具体的枚举对象,如果找不到的话会抛出一个IllegalArgumentException异常。

如:

//定义枚举类型
enum Day {
    MONDAY, TUESDAY, WEDNESDAY,
    THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

反编译Day.class文件:

//反编译Day.class
final class Day extends Enum
{
    //编译器为我们添加的静态的values()方法
    public static Day[] values()
    {
        return (Day[])$VALUES.clone();
    }
    //编译器为我们添加的静态的valueOf()方法,注意间接调用了Enum也类的valueOf方法
    public static Day valueOf(String s)
    {
        return (Day)Enum.valueOf(com/zejian/enumdemo/Day, s);
    }
    //私有构造函数
    private Day(String s, int i)
    {
        super(s, i);
    }
     //前面定义的7种枚举实例
    public static final Day MONDAY;
    public static final Day TUESDAY;
    public static final Day WEDNESDAY;
    public static final Day THURSDAY;
    public static final Day FRIDAY;
    public static final Day SATURDAY;
    public static final Day SUNDAY;
    private static final Day $VALUES[];

    static 
    {    
        //实例化枚举实例
        MONDAY = new Day("MONDAY", 0);
        TUESDAY = new Day("TUESDAY", 1);
        WEDNESDAY = new Day("WEDNESDAY", 2);
        THURSDAY = new Day("THURSDAY", 3);
        FRIDAY = new Day("FRIDAY", 4);
        SATURDAY = new Day("SATURDAY", 5);
        SUNDAY = new Day("SUNDAY", 6);
        $VALUES = (new Day[] {
            MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
        });
    }
}

8.Map浅拷贝

大部分Map实现类都提供了构造方法,用于对另外一个Map的复制,但是这个复制操作不是同步的,意味着一个线程在对Map进行复制操作的时候,另一个线程可能会对它结构性的修改(增删该等操作);为了阻止意外的非同步操作,应该事先使用Collections.synchronizedMap()方法去创建一个同步Map,如下:

Map copiedMap = Collections.synchronizedMap(map);

另外一种浅拷贝方法是使用clone()方法,然而它并不被集合框架作者Josh
Bloch推荐;他曾在一次访谈中谈到关于”拷贝构造方法VS克隆方法“,他说:

I often provide a public clone method on concrete classes because
people expect it. … It’s a shame that Cloneable is broken, but it
happens. … Cloneable is a weak spot, and I think people should be
aware of its limitations.

Enum的坑

  • enum 类型不支持 public 和 protected
    修饰符的构造方法,因此构造函数一定要是 private 或 friendly
    的。也正因为如此,所以枚举对象是无法在程序中通过直接调用其构造方法来初始化的。
  • 定义 enum
    类型时候,如果是简单类型,那么最后一个枚举值后不用跟任何一个符号;但如果有定制方法,那么最后一个枚举值与后面代码要用分号’;’隔开,不能用逗号或空格。
  • 由于 enum 类型的值实际上是通过运行期构造出对象来表示的,所以在
    cluster
    环境下,每个虚拟机都会构造出一个同义的枚举对象。因而在做比较操作时候就需要注意,如果直接通过使用等号
    ( ‘ == ’ )
    操作符,这些看似一样的枚举值一定不相等,因为这不是同一个对象实例。

9.创建一个空Map

如果是不可变Map,使用Collections工具类创建,如下:

map = Collections.emptyMap();

否则,直接new,如下:

map = new HashMap();

 

译文链接:

Enum序列化反序列化原理及问题

工具类:EnumSet、EnumMap

EnumSet

EnumSet 是一个针对枚举类型的高性能的 Set 接口实现。EnumSet
中装入的所有枚举对象都必须是同一种类型,在其内部,是通过 bit-vector
来实现,也就是通过一个 long 型数。EnumSet
支持在枚举类型的所有值的某个范围中进行迭代。

理解位向量

顾名思义位向量就是用一个bit位(0或1)标记一个元素的状态,用一组bit位表示一个集合的状态,而每个位对应一个元素,每个bit位的状态只可能有两种,即0或1。位向量能表示的元素个数与向量的bit位长度有关,如一个int类型能表示32个元素,而一个long类型则可以表示64个元素,对于EnumSet而言采用的就long类型或者long类型数组。比如现在有一个文件中的数据,该文件存储了N=1000000个无序的整数,需要把这些整数读取到内存并排序再重新写回文件中,该如何解决?最简单的方式是用int类型来存储每个数,并把其存入到数组(int
a[m])中,再进行排序,但是这种方式将会导致存储空间异常大,对数据操作起来效率也能成问题,那有没更高效的方式呢?的确是有的,那就是运用位向量,我们知道一个int型的数有4个字节,也就是32位,那么我们可以用N/32个int型数组来表示这N个数:

a[0]表示**第**1~32个数(0~31)
a[1]表示**第**33~64个数(32~63)
a[2]表示**第**65~96个数(64~95)
...... 以此类推

这样,每当输入一个数字m,我们应该先找到该数字在数组的第?个元素,也就是a[?],然后再确定在这个元素的第几个bit位,找到后设置为1,代表存在该数字。举个例子来说,比如输入40,那么40/32为1余8,则应该将a[1]元素值的第9个bit位置为1(1的二进制左移8位后就是第9个位置),表示该数字存在。

EnumMap

代码比较简单,我们使用两种解决方案,一种是HashMap,一种EnumMap,虽然都统计出了正确的结果,但是EnumMap作为枚举的专属的集合,我们没有理由再去使用HashMap,毕竟EnumMap要求其Key必须为Enum类型,因而使用Color枚举实例作为key是最恰当不过了,也避免了获取name的步骤
更重要的是EnumMap效率更高,因为其内部是通过数组实现的(稍后分析),注意EnumMap的key值不能为null,虽说是枚举专属集合,但其操作与一般的Map差不多,概括性来说EnumMap是专门为枚举类型量身定做的Map实现,虽然使用其它的Map(如HashMap)也能完成相同的功能,但是使用EnumMap会更加高效,它只能接收同一枚举类型的实例作为键值且不能为null,由于枚举类型实例的数量相对固定并且有限,所以EnumMap使用数组来存放与枚举类型对应的值,毕竟数组是一段连续的内存空间,根据程序局部性原理,效率会相当高。

// 定义一个 EnumMap 对象,映射表主键是日期枚举类型,值是颜色枚举类型
private static Map<WeekDayEnum, RainbowColor> schema = 
           new EnumMap<WeekDayEnum, RainbowColor>(WeekDayEnum.class); 

static{ 
   // 将一周的每一天与彩虹的某一种色彩映射起来
   for (int i = 0; i < WeekDayEnum.values().length; i++) { 
       schema.put(WeekDayEnum.values()[i], RainbowColor.values()[i]); 
   } 
} 
System.out.println("What is the lucky color today?"); 
System.out.println("It's " + schema.get(WeekDayEnum.Sat));

Enum抽象类常见方法

澳门新浦京娱乐游戏 2

Ref:
https://docs.oracle.com/javase/tutorial/java/javaOO/enum.html
http://blog.csdn.net/javazejian/article/details/71333103

You can leave a response, or trackback from your own site.

Leave a Reply

网站地图xml地图