MapのkeySet()メソッドから返されるSetは特定の順序を保証しないことを理解しています。
私の質問は、複数の反復にわたってsame順序を保証するかどうかです。例えば
Map<K,V> map = getMap();
for( K k : map.keySet() )
{
}
...
for( K k : map.keySet() )
{
}
上記のコードでは、マップがnot変更されていると仮定すると、keySetsの繰り返しは同じ順序になります。 Sunのjdk15 it doesを同じ順序で繰り返しますが、この動作に依存する前に、すべてのJDKが同じことを行うかどうかを知りたいと思います。
[〜#〜] edit [〜#〜]
答えから、私はそれに頼ることができないと思います。残念な。私は注文を保証するために新しいコレクションを構築する必要がないことで逃げることを望んでいました。私のコードは、同じ順序で繰り返し処理し、何らかのロジックを実行してから、繰り返し処理する必要がありました。順序を保証するkeySetから新しいArrayListを作成します。
APIドキュメントで保証されていない場合は、それに依存するべきではありません。同じベンダーのJDKであっても、JDKのリリースごとに動作が変わる場合があります。
セットを簡単に取得して、自分で並べ替えることができますか?
反復順序が変わらないHashMapが必要な場合は、 LinkedHashMap を使用できます。
さらに、コレクションを反復処理する場合は、常に使用する必要があります。 HashMapのentrySetまたはkeySetの繰り返しは、LinkedHashMapの繰り返しよりもはるかに遅くなります。
Mapは(クラスではなく)インターフェースにすぎません。つまり、それを実装する(そして多くの)基本クラスは異なる動作をする可能性があり、APIのkeySet()のコントラクトは一貫した反復が必要であることを示していません。
Mapを実装する特定のクラス(HashMap、LinkedHashMap、TreeMapなど)を見ている場合、ソースをチェックアウトすることで動作が何であるかを判断するためのkeySet()関数の実装方法を確認できます。実際にアルゴリズムをよく見て、探しているプロパティが保持されているかどうかを確認してください(つまり、マップに反復間の挿入/削除が行われていない場合の一貫した反復順序)。たとえば、HashMapのソースはこちら(JDK 6を開く): http://www.docjar.com/html/api/Java/util/HashMap.Java.html
JDKごとに大きく異なる可能性があるため、私は絶対にそれに依存しません。
ただし、一貫した反復順序が本当に必要な場合は、LinkedHashMapを試してみてください。
Map for APIは、同じオブジェクトでのメソッドの複数の呼び出し間でも、anyの順序付けを保証しません。
実際には、後続の複数の呼び出しで反復順序が変更された場合(マップ自体が変更されなかったと仮定して)、非常に驚くでしょう。
編集-一貫性のある反復順序に依存したい場合は、これらの保証を正確に提供する SortedMap が必要です。
楽しみのために、毎回ランダムな順序を保証するために使用できるコードを書くことにしました。これは、順序に依存しているが、そうではない場合をキャッチできるようにするのに便利です。他の人が言ったよりも順序に依存したい場合は、SortedMapを使用する必要があります。 Mapを使用して順序に依存する場合は、次のRandomIteratorを使用するとそれをキャッチします。コードをテストする場合にのみ使用します。使用しない場合よりも多くのメモリを使用するためです。
また、Map(またはSet)をラップしてRandomeIteratorを返し、for-eachループを使用できるようにすることもできます。
import Java.util.ArrayList;
import Java.util.Collections;
import Java.util.HashMap;
import Java.util.Iterator;
import Java.util.List;
import Java.util.Map;
public class Main
{
private Main()
{
}
public static void main(final String[] args)
{
final Map<String, String> items;
items = new HashMap<String, String>();
items.put("A", "1");
items.put("B", "2");
items.put("C", "3");
items.put("D", "4");
items.put("E", "5");
items.put("F", "6");
items.put("G", "7");
display(items.keySet().iterator());
System.out.println("---");
display(items.keySet().iterator());
System.out.println("---");
display(new RandomIterator<String>(items.keySet().iterator()));
System.out.println("---");
display(new RandomIterator<String>(items.keySet().iterator()));
System.out.println("---");
}
private static <T> void display(final Iterator<T> iterator)
{
while(iterator.hasNext())
{
final T item;
item = iterator.next();
System.out.println(item);
}
}
}
class RandomIterator<T>
implements Iterator<T>
{
private final Iterator<T> iterator;
public RandomIterator(final Iterator<T> i)
{
final List<T> items;
items = new ArrayList<T>();
while(i.hasNext())
{
final T item;
item = i.next();
items.add(item);
}
Collections.shuffle(items);
iterator = items.iterator();
}
public boolean hasNext()
{
return (iterator.hasNext());
}
public T next()
{
return (iterator.next());
}
public void remove()
{
iterator.remove();
}
}
ハッシュマップは、マップの順序が時間とともに一定であることを保証しません。
そうである必要はありません。マップのkeySet関数はSetを返し、セットのiteratorメソッドはそのドキュメントでこれを示しています。
「このセット内の要素の反復子を返します。要素は特定の順序で返されません(このセットが保証を提供するクラスのインスタンスでない限り)。」
したがって、これらのクラスのいずれかを保証付きで使用していない限り、何もありません。
マップはインターフェースであり、ドキュメントでは順序を同じにする必要はありません。つまり、順序に依存することはできません。ただし、getMap()によって返されるMap実装を制御する場合、LinkedHashMapまたはTreeMapを使用して、それらを反復処理するたびに同じ順序のキー/値を取得できます。
LinkedHashMapに同意します。 HashMapをキーで並べ替えようとしていたときに問題に直面していたときに、自分の発見と経験を置いただけです。
HashMapを作成するための私のコード:
HashMap<Integer, String> map;
@Before
public void initData() {
map = new HashMap<>();
map.put(55, "John");
map.put(22, "Apple");
map.put(66, "Earl");
map.put(77, "Pearl");
map.put(12, "George");
map.put(6, "Rocky");
}
マップのエントリを出力するshowMap関数があります。
public void showMap (Map<Integer, String> map1) {
for (Map.Entry<Integer, String> entry: map1.entrySet()) {
System.out.println("[Key: "+entry.getKey()+ " , "+"Value: "+entry.getValue() +"] ");
}
}
並べ替える前にマップを印刷すると、次のシーケンスが印刷されます。
Map before sorting :
[Key: 66 , Value: Earl]
[Key: 22 , Value: Apple]
[Key: 6 , Value: Rocky]
[Key: 55 , Value: John]
[Key: 12 , Value: George]
[Key: 77 , Value: Pearl]
基本的に、マップキーが配置された順序とは異なります。
今、マップキーでソートするとき:
List<Map.Entry<Integer, String>> entries = new ArrayList<>(map.entrySet());
Collections.sort(entries, new Comparator<Entry<Integer, String>>() {
@Override
public int compare(Entry<Integer, String> o1, Entry<Integer, String> o2) {
return o1.getKey().compareTo(o2.getKey());
}
});
HashMap<Integer, String> sortedMap = new LinkedHashMap<>();
for (Map.Entry<Integer, String> entry : entries) {
System.out.println("Putting key:"+entry.getKey());
sortedMap.put(entry.getKey(), entry.getValue());
}
System.out.println("Map after sorting:");
showMap(sortedMap);
出力は次のとおりです。
Sorting by keys :
Putting key:6
Putting key:12
Putting key:22
Putting key:55
Putting key:66
Putting key:77
Map after sorting:
[Key: 66 , Value: Earl]
[Key: 6 , Value: Rocky]
[Key: 22 , Value: Apple]
[Key: 55 , Value: John]
[Key: 12 , Value: George]
[Key: 77 , Value: Pearl]
キーの順序の違いを確認できます。キーのソート順は問題ありませんが、コピーされたマップのキーの順序は以前のマップと同じ順序になります。これが正しいかどうかはわかりませんが、同じキーを持つ2つのハッシュマップの場合、キーの順序は同じです。これは、キーの順序は保証されないが、このJVMバージョンのHashMap実装のキー挿入アルゴリズムの固有の性質のため、同じキーを持つ2つのマップで同じになる可能性があることを意味します。
LinkedHashMapを使用して、ソートされたエントリをHashMapにコピーすると、望ましい結果が得られます(これは当然ですが、それはポイントではありません。ポイントはHashMapのキーの順序に関するものです)
HashMap<Integer, String> sortedMap = new LinkedHashMap<>();
for (Map.Entry<Integer, String> entry : entries) {
System.out.println("Putting key:"+entry.getKey());
sortedMap.put(entry.getKey(), entry.getValue());
}
System.out.println("Map after sorting:");
showMap(sortedMap);
出力:
Sorting by keys :
Putting key:6
Putting key:12
Putting key:22
Putting key:55
Putting key:66
Putting key:77
Map after sorting:
[Key: 6 , Value: Rocky]
[Key: 12 , Value: George]
[Key: 22 , Value: Apple]
[Key: 55 , Value: John]
[Key: 66 , Value: Earl]
[Key: 77 , Value: Pearl]
論理的には、契約に「特定の順序は保証されていません」と書かれていて、「一度注文された順序」が特定の順序であるため、答えはノーであり、それに依存することはできません同じ方法で2回出てきます。
また、keySet()メソッドによって返されるSetインスタンスを保存し、同じ順序が必要なときにいつでもこのインスタンスを使用できます。