ホームページ >Java >&#&チュートリアル >Java コレクションのインタビューの質問と回答の概要
すべての プログラミング言語 にはコレクションがあり、Java の初期バージョンには、Vector、Stack、HashTable、Array などのいくつかのコレクション クラスが含まれていました。コレクションの普及に伴い、Java 1.2 では、すべてのコレクション インターフェイス、実装、アルゴリズムを含むコレクション フレームワークが提案されました。 Java は長い間、スレッドの安全性を確保しながらジェネリックスと同時コレクション クラスを使用するプロセスを経てきました。また、ブロッキング インターフェイスと Java 同時実行パッケージでのその実装も含まれます。コレクション フレームワークの利点のいくつかは次のとおりです:
(1) 独自のコレクション クラスを実装する代わりにコア コレクション クラスを使用することで、開発コストを削減します。
(2) 厳密にテストされたコレクション フレームワーク クラスを使用することで、コードの品質が向上します。
(3) JDK に付属のコレクション クラスを使用することで、コードのメンテナンス コストを削減できます。
(4) 再利用性と操作性。
Java1.5 ではジェネリックスが導入され、すべてのコレクション インターフェイスと実装でジェネリックスが広範囲に使用されています。ジェネリックを使用すると、保持できる Object 型をコレクションに提供できるため、他の型の要素を追加すると、コンパイル時にエラーが発生します。これにより、コンパイル時にエラー メッセージが表示されるため、実行時の ClassCastException が回避されます。ジェネリックスはコードをよりクリーンにするため、明示的な変換や instanceOfoperators を使用する必要がありません。また、型チェックされたバイトコード命令が生成されないため、ランタイムにも利点がもたらされます。
Collection は、コレクション レベルのルート インターフェイスです。コレクションは、その要素であるオブジェクトのセットを表します。 Java プラットフォームは、このインターフェイスの直接実装を提供しません。
Set は、重複した要素を含めることができないセットです。このインターフェイスは数学的なセットの抽象化をモデル化し、トランプのようなセットを表すために使用されます。
List は、繰り返しの要素を含むことができる順序付けされたコレクションです。インデックスを使用して任意の要素にアクセスできます。 List は、長さが動的に変化する配列に似ています。
Map は、キーを値にマッピングするオブジェクトです。Map には重複したキーを含めることはできません。各キーは最大 1 つの値のみをマッピングできます。
他のインターフェイスには、Queue、Dequeue、SortedSet、SortedMap、ListIterator などがあります。
Collection インターフェースはオブジェクトのセットを指定し、オブジェクトはその要素です。これらの要素がどのように維持されるかは、コレクションの特定の実装によって決まります。たとえば、List などの一部の Collection 実装では要素の重複が許可されますが、Set などの他の実装では許可されません。多くの Collection 実装にはパブリック クローン メソッドがあります。ただし、コレクションのすべての実装にこれを含めるのは意味がありません。これは、Collection が抽象表現であるためです。重要なのは実装です。
クローン作成またはシリアル化のセマンティクスと影響は、具体的な実装を扱うときに影響します。したがって、実装は、それをどのように複製またはシリアル化するか、あるいはそもそも複製またはシリアル化できるかどうかを決定する必要があります。
すべての実装でクローン作成とシリアル化を強化すると、最終的には柔軟性が低下し、制限が増加します。特定の実装は、クローンを作成してシリアル化できるかどうかを決定する必要があります。
Map インターフェイスとその実装もコレクション フレームワークの一部ですが、Map はコレクションではなく、コレクションは Map ではありません。したがって、Map が Collection を継承することは意味がありません。また、その逆も同様です。
Map が Collection インターフェースを継承する場合、要素はどこに行くのでしょうか? Map にはキーと値のペアが含まれており、キーまたは値のリスト コレクションを抽出するメソッドを提供しますが、「オブジェクトのセット」仕様には適合しません。
Iterator インターフェース は、コレクションを走査するためのインターフェースを提供します。 iterator メソッドを使用して、コレクションからイテレータ インスタンスを取得できます。イテレーターは、Java コレクション フレームワークの列挙を置き換えます。イテレータを使用すると、呼び出し元は反復プロセス中に要素を削除できます。
列挙はイテレーターの 2 倍高速で、使用するメモリが少なくなります。列挙は非常に基本的なものであり、基本的なニーズを満たします。ただし、Enumeration と比較すると、Iterator はコレクションの走査中に他のスレッドがコレクションを変更するのを防ぐため、より安全です。
Iterator は Java Collections Framework の列挙を置き換えます。イテレーターでは呼び出し元がコレクションから要素を削除できますが、Enumeration ではそれができません。イテレータのメソッド名が改良され、その機能がより明確になりました。
セマンティクスは不明です。分かっていることは、Iterator プロトコルは反復の順序を保証できないということです。ただし、ListIterator は反復順序を保証する追加操作を提供しないことに注意してください。
現在のイテレーターのトップレベルで実装できますが、インターフェイスに追加されると、すべての継承でそれを実装する必要があり、意味がありません。
(1) Iterator を使用して Set コレクションと List コレクションを走査できますが、ListIterator は List のみを走査できます。
(2) Iterator は前方のみにトラバースできますが、LIstIterator は両方向にトラバースできます。
(3) ListIterator は Iterator インターフェイスを継承し、要素の追加、要素の置換、前後の要素のインデックス位置の取得などの追加関数を追加します。
List<String> strList = new ArrayList<>(); //使用for-each循环 for(String obj : strList){ System.out.println(obj); } //using iterator Iterator<String> it = strList.iterator(); while(it.hasNext()){ String obj = it.next(); System.out.println(obj); }
イテレーターを使用すると、現在トラバースされているコレクション要素が変更されると確実に ConcurrentModificationException がスローされるため、よりスレッドセーフになります。
次の要素を取得しようとするたびに、Iterator フェイルファスト プロパティは現在のコレクション構造に変更がないかチェックします。変更が見つかった場合は、ConcurrentModificationException がスローされます。 Collection 内のすべての Iterator 実装は、フェイルファストになるように設計されています (ConcurrentHashMap や CopyOnWriteArrayList などの同時コレクション クラスを除く)。
Iterator のフェイルファスト プロパティは現在のコレクションで動作するため、コレクション内の変更の影響を受けません。 Java.util パッケージ内のすべてのコレクション クラスはフェイルファストになるように設計されていますが、java.util.concurrent のコレクション クラスはフェイルセーフです。フェイルファスト反復子は ConcurrentModificationException をスローしますが、フェイルセーフ反復子は ConcurrentModificationException をスローしません。
コレクションを走査するとき、ArrayList の代わりに CopyOnWriteArrayList を使用するなど、同時コレクション クラスを使用して ConcurrentModificationException を回避できます。
Iterator インターフェースはコレクションを走査するためのメソッドを定義しますが、その実装はコレクション実装クラスの責任です。トラバーサル用の Iterator を返す各コレクション クラスには、独自の Iterator 実装内部クラスがあります。
これにより、コレクション クラスはイテレーターがフェイルファストであるかフェイルセーフであるかを選択できるようになります。たとえば、ArrayList イテレータはフェイルファストであり、CopyOnWriteArrayList イテレータはフェイルセーフです。
UnsupportedOperationException は、操作がサポートされていないことを示すために使用される例外です。これは、JDK クラスで広く使用されており、コレクション フレームワークの java.util.Collections.UnmodifiableCollection は、すべての追加操作と削除操作でこの例外をスローします。
HashMap は、Map.Entrystatic内部クラスの実装にキーと値のペアを格納します。 HashMap はハッシュ アルゴリズムを使用し、put メソッドと get メソッドでは hashCode() メソッドとquals() メソッドを使用します。キーと値のペアを渡して put メソッドを呼び出すと、HashMap は Key hashCode() とハッシュ アルゴリズムを使用して、キーと値のペアが格納されているインデックスを見つけます。エントリは LinkedList に保存されるため、エントリが存在する場合は、equals() メソッドを使用して渡されたキーがすでに存在するかどうかを確認し、存在する場合は値を上書きし、存在しない場合は新しいエントリを作成します。それを保存します。キーを渡して get メソッドを呼び出すと、再度 hashCode() を使用して配列内のインデックスを検索し、次に、equals() メソッドを使用して正しいエントリを検索し、その値を返します。以下の写真で詳細を説明します。
HashMap に関するその他の重要な問題は、容量、負荷率、しきい値の調整です。 HashMap のデフォルトの初期容量は 32、負荷率は 0.75 です。しきい値は負荷係数に容量を乗算したものです。マップ サイズがしきい値よりも大きい場合、HashMap はマップの内容を再ハッシュして、より大きな容量を使用します。容量は常に 2 の累乗であるため、データベースから取得したキャッシュデータなど、多数のキーと値のペアを保存する必要があることがわかっている場合は、正しい容量で HashMap を初期化し、負荷率。
HashMap は、Key オブジェクトの hashCode() メソッドと equals() メソッドを使用して、キーと値のペアのインデックスを決定します。これらのメソッドは、HashMap から値を取得しようとするときにも使用されます。これらのメソッドが正しく実装されていない場合、その場合、2 つの異なるキーが同じ hashCode() とquals() の出力を生成する可能性があり、HashMap はそれらを同じであると見なし、それらを別の場所に置き換えるのではなく上書きします。同様に、重複データの保存を許可しないすべてのコレクション クラスは、重複を見つけるために hashCode() とquals() を使用するため、これらを正しく実装することが非常に重要です。 equals() と hashCode() の実装は、次の規則に従う必要があります:
(1) o1.equals(o2) の場合、o1.hashCode() == o2.hashCode() は常に true です。
(2)如果o1.hashCode() == o2.hashCode(),并不意味着o1.equals(o2)会为true。
我们可以使用任何类作为Map的key,然而在使用它们之前,需要考虑以下几点:
(1)如果类重写了equals()方法,它也应该重写hashCode()方法。
(2)类的所有实例需要遵循与equals()和hashCode()相关的规则。请参考之前提到的这些规则。
(3)如果一个类没有使用equals(),你不应该在hashCode()中使用它。
(4)用户自定义key类的最佳实践是使之为不可变的,这样,hashCode()值可以被缓存起来,拥有更好的性能。不可变的类也可以确保hashCode()和equals()在未来不会改变,这样就会解决与可变相关的问题了。
比如,我有一个类MyKey,在HashMap中使用它。
//传递给MyKey的name参数被用于equals()和hashCode()中 MyKey key = new MyKey('Pankaj'); //assume hashCode=1234 myHashMap.put(key, 'Value'); // 以下的代码会改变key的hashCode()和equals()值 key.setName('Amit'); //assume new hashCode=7890 //下面会返回null,因为HashMap会尝试查找存储同样索引的key,而key已被改变了,匹配失败,返回null myHashMap.get(new MyKey('Pankaj'));
那就是为何String和Integer被作为HashMap的key大量使用。
Map接口提供三个集合视图:
(1)Set keyset():返回map中包含的所有key的一个Set视图。集合是受map支持的,map的变化会在集合中反映出来,反之亦然。当一个迭代器正在遍历一个集合时,若map被修改了(除迭代器自身的移除操作以外),迭代器的结果会变为未定义。集合支持通过Iterator的Remove、Set.remove、removeAll、retainAll和clear操作进行元素移除,从map中移除对应的映射。它不支持add和addAll操作。
(2)Collection values():返回一个map中包含的所有value的一个Collection视图。这个collection受map支持的,map的变化会在collection中反映出来,反之亦然。当一个迭代器正在遍历一个collection时,若map被修改了(除迭代器自身的移除操作以外),迭代器的结果会变为未定义。集合支持通过Iterator的Remove、Set.remove、removeAll、retainAll和clear操作进行元素移除,从map中移除对应的映射。它不支持add和addAll操作。
(3)Set44bf986331d5d9c3931140ad55669b0c> entrySet():返回一个map钟包含的所有映射的一个集合视图。这个集合受map支持的,map的变化会在collection中反映出来,反之亦然。当一个迭代器正在遍历一个集合时,若map被修改了(除迭代器自身的移除操作,以及对迭代器返回的entry进行setValue外),迭代器的结果会变为未定义。集合支持通过Iterator的Remove、Set.remove、removeAll、retainAll和clear操作进行元素移除,从map中移除对应的映射。它不支持add和addAll操作。
(1)HashMap允许key和value为null,而HashTable不允许。
(2)HashTable是同步的,而HashMap不是。所以HashMap适合单线程环境,HashTable适合多线程环境。
(3)在Java1.4中引入了LinkedHashMap,HashMap的一个子类,假如你想要遍历顺序,你很容易从HashMap转向LinkedHashMap,但是HashTable不是这样的,它的顺序是不可预知的。
(4)HashMap提供对key的Set进行遍历,因此它是fail-fast的,但HashTable提供对key的Enumeration进行遍历,它不支持fail-fast。
(5)HashTable被认为是个遗留的类,如果你寻求在迭代的时候修改Map,你应该使用CocurrentHashMap。
对于在Map中插入、删除和定位元素这类操作,HashMap是最好的选择。然而,假如你需要对一个有序的key集合进行遍历,TreeMap是更好的选择。基于你的collection的大小,也许向HashMap中添加元素会更快,将map换为TreeMap进行有序key的遍历。
ArrayList和Vector在很多时候都很类似。
(1)两者都是基于索引的,内部由一个数组支持。
(2)两者维护插入的顺序,我们可以根据插入顺序来获取元素。
(3)ArrayList和Vector的迭代器实现都是fail-fast的。
(4)ArrayList和Vector两者允许null值,也可以使用索引值对元素进行随机访问。
以下是ArrayList和Vector的不同点。
(1)Vector是同步的,而ArrayList不是。然而,如果你寻求在迭代的时候对列表进行改变,你应该使用CopyOnWriteArrayList。
(2)ArrayList比Vector快,它因为有同步,不会过载。
(3)ArrayList更加通用,因为我们可以使用Collections工具类轻易地获取同步列表和只读列表。
Array可以容纳基本类型和对象,而ArrayList只能容纳对象。
Array是指定大小的,而ArrayList大小是固定的。
Array は、addAll、removeAll、iterator などの ArrayList ほど多くの関数を提供しません。 ArrayList の方が明らかに優れた選択肢ですが、場合によっては Array の方が優れている場合もあります。
(1) リストのサイズが指定されている場合、ほとんどの場合、リストを保存して走査します。
(2) 基本的な データ型 を走査する場合、コレクションはコーディング作業を容易にするためにオートボクシングを使用しますが、指定されたサイズの基本型のリストの処理も遅くなる可能性があります。
(3) 多次元配列を使いたい場合は、List658a6c8c8956eca63df9b922e1300853>よりも[][]を使う方が簡単です。
ArrayList と LinkedList は両方とも List インターフェースを実装していますが、それらの間にはいくつかの違いがあります。
(1) ArrayList は Array でサポートされるインデックスに基づくデータ構造であるため、複雑度 O(1) の要素へのランダム アクセスを提供しますが、LinkedList は一連のノード データを格納し、各ノードは前のノードに関連付けられます。そして次のノードが接続されます。したがって、インデックスを使用して要素を取得する方法もありますが、内部実装は開始点からトラバースしてインデックス付きノードまでトラバースしてから要素を返すことになり、時間計算量は O(n) となり、ArrayList よりも遅くなります。
(2) ArrayList と比較すると、LinkedList の要素の挿入、追加、削除が高速になります。これは、要素を途中に挿入するときに、配列のサイズの変更やインデックスの更新が必要ないためです。
(3) LinkedList の各ノードは前後のノードへの参照を保存するため、LinkedList は ArrayList よりも多くのメモリを消費します。
ArrayList、HashMap、TreeMap、および HashTable クラスは、要素へのランダム アクセスを提供します。
java.util.EnumSet は列挙型を使用したコレクション実装です。コレクションが作成されるとき、列挙コレクション内のすべての要素は、明示的または暗黙的に、指定された単一の列挙型から取得される必要があります。 EnumSet は非同期であり、null 要素は許可されません。また、copyOf(Collection c)、of(E first,E...rest)、complementOf(EnumSet s) などのいくつかの便利なメソッドも提供します。
Vector、HashTable、Properties、Stack は同期クラスであるため、スレッドセーフであり、マルチスレッド環境で使用できます。 Java 1.5 同時実行 API には、反復中の変更を許可するいくつかのコレクション クラスが含まれています。これらはすべてコレクションのクローンで動作するため、マルチスレッド環境でも安全です。
Java 1.5 同時実行パッケージ (java.util.concurrent) には、反復中にコレクションを変更できるスレッドセーフなコレクション クラスが含まれています。イテレータはフェイルファストになるように設計されており、ConcurrentModificationException をスローします。クラスとしては、CopyOnWriteArrayList、ConcurrentHashMap、CopyOnWriteArraySet があります。
Java.util.concurrent.BlockingQueue はキューです。要素を取得または削除する場合は、キューが空でなくなるまで待機します。要素を追加する場合は、キューに空き領域ができるまで待機します。 BlockingQueue インターフェイスは Java コレクション フレームワークの一部であり、主にプロデューサー/コンシューマー パターンを実装するために使用されます。プロデューサが使用可能なスペースを確保するか、コンシューマが使用可能なオブジェクトを確保するかについて心配する必要はありません。これらはすべて BlockingQueue 実装クラスで処理されるためです。 Java は、ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue、SynchronousQueue などの一元的な BlockingQueue 実装を提供します。
スタックとキューの両方がデータの事前保存に使用されます。 java.util.Queue はインターフェイスであり、その実装クラスは Java 同時実行パッケージに含まれています。キューを使用すると、要素を先入れ先出し (FIFO) ベースで取得できますが、常にそうとは限りません。 Deque インターフェイスを使用すると、両端から要素を取得できます。
スタックはキューに似ていますが、要素の後入れ先出し (LIFO) 取得が可能です。
StackはVectorを拡張したクラスであり、Queueはインターフェースです。
Java.util.Collections は、コレクションを操作したりコレクションを返したりする静的メソッドのみを含むユーティリティ クラスです。これには、コレクションを操作し、指定されたコレクションに基づく新しいコレクションを返す、その他いくつかの機能を備えた多態性アルゴリズムが含まれています。このクラスには、二分検索、並べ替え、シャッフル、逆順などのコレクション フレームワーク アルゴリズムのメソッドが含まれています。
配列またはコレクションのソートメソッドを使用したい場合は、Java が提供する Comparable インターフェースをカスタムクラスに実装する必要があります。 Comparable インターフェイスには、並べ替えメソッドで使用される CompareTo(T OBJ) メソッドがあります。このメソッドをオーバーライドして、「この」オブジェクトが渡されたオブジェクト引数より小さい、等しい、または大きい場合に負の整数、0、または正の整数を返すようにする必要があります。ただし、実際のほとんどの場合は、さまざまなパラメータに基づいて並べ替えることが必要になります。たとえば、CEO として従業員を給与に基づいて並べ替えたいと考えており、人事担当者は年齢に基づいて従業員を並べ替えたいと考えています。 Comparable.compareTo(Object o) メソッドの実装では 1 つのフィールドに基づいてのみ並べ替えることができ、オブジェクトの並べ替えのニーズに応じてフィールドを選択できないため、これは Comparator インターフェイスを使用する必要がある状況です。 Comparator インターフェイスの Compare(Object o1, Object o2) メソッドの実装では、2 つのオブジェクト パラメーターを渡す必要があります。最初のパラメーターが 2 番目のパラメーターより小さい場合、最初のパラメーターが 2 番目のパラメーターと等しい場合は、負の整数が返されます。最初のパラメータが 2 番目のパラメータより小さい場合は、負の整数が返され、1 が 2 番目のパラメータより大きい場合は、正の整数が返されます。
Comparable インターフェイスと Comparator インターフェイスは、オブジェクトのコレクションまたは配列を並べ替えるのに使用されます。 Comparable インターフェイスはオブジェクトの自然な順序付けを提供するために使用され、これを使用して単一のロジックに基づいた順序付けを提供できます。
Comparator インターフェイスは、さまざまな並べ替えアルゴリズムを提供するために使用され、特定のオブジェクト コレクションを並べ替えるのに必要な Comparator を選択できます。
オブジェクトの配列を並べ替える必要がある場合は、Arrays.sort() メソッドを使用できます。オブジェクトのリストを並べ替える必要がある場合は、Collection.sort() メソッドを使用できます。どちらのクラスにも、自然な並べ替え (Comparable を使用) または基準ベースの並べ替え (Comparator を使用) を行うためのメソッド sort() がオーバーロードされています。 Collections は内部で arraysorting メソッドを使用しているため、両方のパフォーマンスは同じですが、Collections はリストを配列に変換するのに時間がかかるだけです。
Collections.unmodifiableCollection(Collection c) メソッドを使用して読み取り専用コレクションを作成してからパラメーターとして渡すことができます。これにより、コレクションを変更するすべての操作で UnsupportedOperationException がスローされます。
Collections.synchronizedCollection(Collection c) を使用して、指定されたコレクションに基づいて同期された (スレッドセーフな) コレクションを取得できます。
Java コレクション フレームワークは、並べ替えや検索などの一般的なアルゴリズム実装を提供します。 Collections クラスには、これらのメソッドの実装が含まれています。ほとんどのアルゴリズムはリストで動作しますが、一部のアルゴリズムはすべての種類のコレクションで使用できます。一部のアルゴリズムには、並べ替え、検索、混合、最大値と最小値が含まれます。
大文字の O は、データ構造内の一連の要素の観点からアルゴリズムのパフォーマンスを表します。 Collection クラスは実際のデータ構造であり、通常は時間、メモリ、パフォーマンスに基づいて、大文字の O を付けてコレクションの実装を選択します。例: 例 1: ArrayList の get(index i) は定数時間の操作であり、リスト内の要素の数に依存しません。したがって、そのパフォーマンスは O(1) です。例 2: 必要な要素を見つけるためにすべての要素を走査する必要があるため、配列またはリストでの線形検索のパフォーマンスは O(n) です。
(1) ニーズに応じて適切なコレクション タイプを選択します。たとえば、サイズが指定されている場合は、ArrayList の代わりに Array を使用します。挿入順序に従って Map をトラバースしたい場合は、TreeMap を使用する必要があります。繰り返したくない場合は、Set を使用する必要があります。
(2) 一部のコレクション クラスでは初期容量を指定できるため、格納される要素の数を見積もることができれば、それを使用して再ハッシュやサイズ変更を回避できます。
(3) 実装プログラミングではなくインターフェースプログラミングに基づいているため、後から実装を簡単に変更できます。
(4) 実行時に ClassCastException が発生しないように、常にタイプセーフなジェネリックスを使用してください。
(5) hashCode()やequals()を自分で実装することを避けるために、JDKが提供する不変クラスをMapのキーとして使用します。
(6) 可能な限りコレクション ツール クラスを使用するか、独自の実装を作成する代わりに、読み取り専用、同期されたコレクション、または空のコレクションを取得します。コードの再利用性が向上し、安定性と保守性が向上します。
以上がJava コレクションのインタビューの質問と回答の概要の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。