Javaエニュメレーションタイプ(enum)-5

8390 ワード

EnumMap
EumMapの基本的な使い方
まずこのような問題を考えてみましょう。今は同じサイズで、色が違うデータがあります。各色の数を統計して、データを倉庫に入力する必要があります。次のように列挙して、色のColorを表します。
enum Color {
    GREEN,RED,BLUE,YELLOW
}
次のような解決策があります。Map集合を使って統計します。key値は色の名前として、valueは服の数を表しています。
import java.util.*;

public class EnumMapDemo {
    public static void main(String[] args){
        List list = new ArrayList<>();
        list.add(new Clothes("C001",Color.BLUE));
        list.add(new Clothes("C002",Color.YELLOW));
        list.add(new Clothes("C003",Color.RED));
        list.add(new Clothes("C004",Color.GREEN));
        list.add(new Clothes("C005",Color.BLUE));
        list.add(new Clothes("C006",Color.BLUE));
        list.add(new Clothes("C007",Color.RED));
        list.add(new Clothes("C008",Color.YELLOW));
        list.add(new Clothes("C009",Color.YELLOW));
        list.add(new Clothes("C010",Color.GREEN));
        //  1:  HashMap
        Map map = new HashMap<>();
        for (Clothes clothes:list){
           String colorName=clothes.getColor().name();
           Integer count = map.get(colorName);
            if(count!=null){
                map.put(colorName,count+1);
            }else {
                map.put(colorName,1);
            }
        }

        System.out.println(map.toString());

        System.out.println("---------------");

        //  2:  EnumMap
        Map enumMap=new EnumMap<>(Color.class);

        for (Clothes clothes:list){
            Color color=clothes.getColor();
            Integer count = enumMap.get(color);
            if(count!=null){
                enumMap.put(color,count+1);
            }else {
                enumMap.put(color,1);
            }
        }

        System.out.println(enumMap.toString());
    }

    /**
         :
     {RED=2, BLUE=3, YELLOW=3, GREEN=2}
     ---------------
     {GREEN=2, RED=2, BLUE=3, YELLOW=3}
     */
}
コードは簡単です。二つの解決案を使っています。一つはHashMapで、一つはEnumMapです。全部正しい結果を集計しましたが、EnumMapは列挙の専用の集合として、私達はHashMapを使う理由がありません。結局、EnumMapはEnumタイプでなければならないので、Color列挙の実例を使ってkeyとして最適です。nameを取得するステップも避けました。さらに重要なのはEnumMapの効率が高いことです。内部は配列によって実現されるので(後で分析します)、EnumMapのkey値はnullではないことに注意してください。エニュメレーション専用集合ですが、その操作は普通のMapと同じです。概括的にはEnumMapはエニュメレート・タイプのためにカスタマイズされたMapです。他のMapを使用しても同じ機能を達成できますが、EnumMapを使用するとより効率的になります。同じ列挙タイプのインスタンスをキーとして受信することができます。nullは列挙タイプのインスタンスの数が相対的に固定され、限定されていますので、EnumMapは列挙タイプに対応する値を配列で保存します。配列は一連のメモリ空間です。プログラムの局部原理によって、効率がかなり高くなります。EumMapの使い方を詳しく調べて、まず構造関数を見ます。
//                 。
EnumMap(Class keyType) 
//                      ,           (     )。     
EnumMap(EnumMap m) 
//        ,          。
EnumMap(Map m)  
HashMapとは違って、Classオブジェクトのタイプ情報を伝達する必要があります。このパラメータでEnumMapはタイプ情報に基づいて内部データ構造を初期化できます。他の2つは初期化時にMapセットに入ってきます。コードのデモは以下の通りです。
//       
Map enumMap=new EnumMap<>(Color.class);
//       
Map enumMap2=new EnumMap<>(enumMap);
//       
Map hashMap = new HashMap<>();
hashMap.put(Color.GREEN, 2);
hashMap.put(Color.BLUE, 3);
Map enumMap = new EnumMap<>(hashMap);
EnumMapの方法は普通のmapとほとんど変わりません。HashMapの主な違いは構造方法と伝達型パラメータとEnumMap保証Key順序と列挙中の順序が一致していることですが、Keyはnullではないことを覚えてください。
EumMapは原理分析を実現します。
EnumMapのソースコードは700行以上あります。ここでは主に内部の記憶構造を分析し、検索の実現を追加します。この点を理解して、EnumMap内部の実現原理に対応しています。まずデータ構造と構造関数を見ます。
public class EnumMap, V> extends AbstractMap
    implements java.io.Serializable, Cloneable
{
    //Class    
    private final Class keyType;

    //  Key    
    private transient K[] keyUniverse;

    //  Value    
    private transient Object[] vals;

    //map size
    private transient int size = 0;

    // map
    private static final Enum>[] ZERO_LENGTH_ENUM_ARRAY = new Enum>[0];

    //    
    public EnumMap(Class keyType) {
        this.keyType = keyType;
        keyUniverse = getKeyUniverse(keyType);
        vals = new Object[keyUniverse.length];
    }

}
EnumMapはAbstractMapクラスを継承していますので、EnumMapは一般的なmapの使用方法を備えています。keyTypeはタイプ情報を表しています。keyUniverseはキー配列を表しています。記憶されているのはすべての可能なエニュメレート・値です。vals配列はキーの対応する値を表しています。構造関数においては、keyUniverse = getKeyUniverse(keyType);によってkeyUniverse配列の値を初期化し、内部に記憶されているのはすべての可能なエニュメレーション値であり、次いでValue配列の存在価値valsを初期化し、そのサイズはエニュメレート・インスタンスの個数と同じであり、getKeyUniverse方法は以下のように実現される。
//      
private static > K[] getKeyUniverse(Class keyType) {
        //          values  ,values            
        return SharedSecrets.getJavaLangAccess()
                                        .getEnumConstantsShared(keyType);
    }
方法の戻り値から見ると、戻りのタイプはエニュメレート・アレイであり、事実も同様であり、最終的な戻り値はエニュメレート・タイプのvalues方法の戻り値であり、以前はvalues方法を分析して、可能なエニュメレート・値をすべて返したので、keyUniverse配列記憶はエニュメレート・タイプの可能なエニュメレート・値である。続いてput方法の実現を見ます。
public V put(K key, V value) {
        typeCheck(key);//  key   
        //    value      
        int index = key.ordinal();
        //    
        Object oldValue = vals[index];
        //  value 
        vals[index] = maskNull(value);
        if (oldValue == null)
            size++;
        return unmaskNull(oldValue);//    
    }
ここではtype Check方法でkeyタイプの検出を行い、列挙タイプかどうかを判断します。タイプが違うと、例外を投げます。
private void typeCheck(K key) {
   Class> keyClass = key.getClass();//      
   if (keyClass != keyType && keyClass.getSuperclass() != keyType)
       throw new ClassCastException(keyClass + " != " + keyType);
}
次に、int index = key.ordinal()によって、エニュメレート・インスタンスの順序値を取得し、この値を以下のものとして利用して、vals配列に対応する下付き要素、すなわちvals[index]に値を格納し、これもエニュメレート・インスタンスと同じ記憶順序を維持できる理由である。vals[]における要素の割り当てと古い値に戻る時にそれぞれmasNull方法とunmaskNull方法を呼び出したことを発見しました。
//  NULL       
  private static final Object NULL = new Object() {
        public int hashCode() {
            return 0;
        }

        public String toString() {
            return "java.util.EnumMap.NULL";
        }
    };

    private Object maskNull(Object value) {
        //     ,  NULL  ,    value
        return (value == null ? NULL : value);
    }

    @SuppressWarnings("unchecked")
    private V unmaskNull(Object value) {
        // NULL     null 
        return (V)(value == NULL ? null : value);
    }
このようにEnumMapはやはりnull値を許容していますが、keyは絶対nullではなく、null値に対してEnumMapは特殊処理を行い、NULLオブジェクトに包装しました。結局vals[]はObject、masNull方法とunmaskNull方法はnull包装と和解包装に用いられます。これがEumMap集合の追加過程です。次に取得方法を見ます。
public V get(Object key) {
        return (isValidKey(key) ?
                unmaskNull(vals[((Enum>)key).ordinal()]) : null);
    }

 // Key              
 private boolean isValidKey(Object key) {
      if (key == null)
          return false;

      // Cheaper than instanceof Enum followed by getDeclaringClass
      Class> keyClass = key.getClass();
      return keyClass == keyType || keyClass.getSuperclass() == keyType;
  }
対応するput方法に対して、get方法はかなり簡潔であり、keyが有効であれば、直接ordinal方法でインデックスを取り、値配列valsでインデックスを取得して返します。removeの方法は以下の通りです。
public V remove(Object key) {
        //  key     
        if (!isValidKey(key))
            return null;
        //      
        int index = ((Enum>)key).ordinal();

        Object oldValue = vals[index];
        //          null
        vals[index] = null;
        if (oldValue != null)
            size--;// size
        return unmaskNull(oldValue);
    }
非常に簡単で、key値が有効で、keyにより下付きインデックス値を取得し、vals[]に対する下付き値をnullに設定し、sizeを1つ減らす。値が含まれているかどうかを確認します。
       value
public boolean containsValue(Object value) {
    value = maskNull(value);
    //      
    for (Object val : vals)
        if (value.equals(val))
            return true;

    return false;
}
//      key
public boolean containsKey(Object key) {
    return isValidKey(key) && vals[((Enum>)key).ordinal()] != null;
}
valueが直接的に巡回配列によって実現されると判断し、keyが有効かどうかを判断し、vals[]に該当する値があるかどうかを判断する。これはEnumMapの主な実現原理です。つまり内部には二つの配列があり、長さは同じです。一つは可能なすべてのキー(列挙値)を表し、一つは対応する値を表しています。keynullは許されませんが、valueはnullとなります。キーには対応するインデックスがあります。インデックスに従って直接アクセスして、そのキー配列と値配列を操作します。
次の章:Javaエニュメレーションタイプ(enum)-6