모든 프로그래밍 언어에는 컬렉션이 있으며, Java의 초기 버전에는 Vector, Stack, HashTable 및 Array와 같은 여러 컬렉션 클래스가 포함되어 있습니다. 컬렉션이 널리 사용되면서 Java 1.2는 모든 컬렉션 인터페이스, 구현 및 알고리즘을 포함하는 컬렉션 프레임워크를 제안했습니다. Java는 오랫동안 스레드 안전성을 보장하면서 제네릭 및 동시 컬렉션 클래스를 사용하는 과정을 거쳐 왔습니다. 또한 Java 동시성 패키지의 차단 인터페이스와 해당 구현도 포함됩니다. 컬렉션 프레임워크의 장점 중 일부는 다음과 같습니다.
(1) 자체 컬렉션 클래스를 구현하는 대신 핵심 컬렉션 클래스를 사용하여 개발 비용을 줄입니다.
(2) 엄격하게 테스트된 컬렉션 프레임워크 클래스를 사용하면 코드 품질이 향상됩니다.
(3) JDK에 포함된 컬렉션 클래스를 사용하면 코드 유지 비용을 줄일 수 있습니다.
(4) 재사용성과 운용성.
Java 1.5에는 제네릭이 도입되었으며 모든 컬렉션 인터페이스와 구현에서는 제네릭을 광범위하게 사용합니다. Generics를 사용하면 보유할 수 있는 Object 유형의 컬렉션을 제공할 수 있으므로 다른 유형의 요소를 추가하면 오류로 컴파일됩니다. 이렇게 하면 컴파일 타임에 오류 메시지가 표시되므로 런타임 시 ClassCastException이 발생하지 않습니다. Generics는 또한 코드를 더 깔끔하게 만들어주므로 명시적인 변환과 instanceOf 연산자를 사용할 필요가 없습니다. 또한 유형 확인 바이트코드 명령어가 생성되지 않기 때문에 런타임에 이점을 제공합니다.
컬렉션은 컬렉션 수준의 루트 인터페이스입니다. 컬렉션은 해당 요소인 개체 집합을 나타냅니다. Java 플랫폼은 이 인터페이스의 직접적인 구현을 제공하지 않습니다.
세트는 중복된 요소를 포함할 수 없는 세트입니다. 이 인터페이스는 수학적 집합 추상화를 모델로 하며 카드 한 벌과 같은 집합을 나타내는 데 사용됩니다.
목록은 반복되는 요소를 포함할 수 있는 순서가 지정된 컬렉션입니다. 인덱스를 통해 모든 요소에 액세스할 수 있습니다. 목록은 길이가 동적으로 변경되는 배열과 비슷합니다.
맵은 키를 값에 매핑하는 개체입니다. 맵에는 중복 키가 포함될 수 없습니다. 각 키는 최대 하나의 값만 매핑할 수 있습니다.
다른 인터페이스로는 Queue, Dequeue, SortedSet, SortedMap 및 ListIterator가 있습니다.
컬렉션 인터페이스는 개체 집합을 지정하며 개체는 해당 요소입니다. 이러한 요소가 유지되는 방식은 컬렉션의 특정 구현에 따라 결정됩니다. 예를 들어 List와 같은 일부 Collection 구현은 중복 요소를 허용하지만 Set과 같은 다른 구현은 허용하지 않습니다. 많은 컬렉션 구현에는 공개 복제 메서드가 있습니다. 그러나 모든 컬렉션 구현에 이를 넣는 것은 의미가 없습니다. 컬렉션은 추상적인 표현이기 때문입니다. 중요한 것은 구현입니다.
복제 또는 직렬화의 의미와 의미는 특정 구현을 다룰 때 중요합니다. 따라서 구현에서는 이를 복제 또는 직렬화하는 방법이나 복제 또는 직렬화할 수 있는지 여부를 결정해야 합니다.
모든 구현에서 복제 및 직렬화를 강화하여 궁극적으로 유연성이 떨어지고 제한이 더 커집니다. 특정 구현에서는 복제 및 직렬화 가능 여부를 결정해야 합니다.
Map 인터페이스와 그 구현도 컬렉션 프레임워크의 일부이지만 Map은 컬렉션이 아니며 컬렉션도 Map이 아닙니다. 따라서 Map이 Collection을 상속하고 그 반대의 경우도 마찬가지입니다.
Map이 Collection 인터페이스를 상속받는 경우 요소는 어디로 가나요? 맵에는 키 또는 값 목록 컬렉션을 추출하는 방법을 제공하는 키-값 쌍이 포함되어 있지만 "객체 집합" 사양에 맞지 않습니다.
반복자 인터페이스는 컬렉션을 탐색하기 위한 인터페이스를 제공합니다. 반복자 메서드를 사용하여 컬렉션에서 반복자 인스턴스를 가져올 수 있습니다. 반복자는 Java 컬렉션 프레임워크의 열거를 대체합니다. 반복자를 사용하면 호출자가 반복 프로세스 중에 요소를 제거할 수 있습니다.
열거는 Iterator보다 두 배 빠르며 메모리를 덜 사용합니다. 열거는 매우 기본적이며 기본 요구 사항을 충족합니다. 그러나 Enumeration과 비교할 때 Iterator는 컬렉션을 탐색하는 동안 다른 스레드가 컬렉션을 수정하는 것을 방지하므로 더 안전합니다.
Iterator는 Java Collections Framework의 열거를 대체합니다. 반복자를 사용하면 호출자가 컬렉션에서 요소를 제거할 수 있지만 열거형은 그렇지 않습니다. 반복기 메서드 이름이 개선되어 기능이 더욱 명확해졌습니다.
의미가 불분명합니다. Iterator 프로토콜은 반복 순서를 보장하지 않는 것으로 알려져 있습니다. 그러나 ListIterator는 반복 순서를 보장하는 추가 작업을 제공하지 않습니다.
현재 Iterator의 최상위 수준에서 구현할 수 있지만 거의 사용되지 않습니다. 인터페이스에 추가되면 모든 상속에서 이를 구현해야 하는데 이는 의미가 없습니다.
(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.UnmodifyingCollection은 모든 추가 및 제거 작업에서 이 예외를 발생시킵니다.
HashMap은 Map.Entry정적내부 클래스 구현에 키-값 쌍을 저장합니다. HashMap은 해시 알고리즘을 사용하며, put 및 get 메소드에서는 hashCode() 및 equals() 메소드를 사용합니다. 키-값 쌍을 전달하여 put 메소드를 호출하면 HashMap은 Key hashCode()와 해싱 알고리즘을 사용하여 키-값 쌍이 저장된 인덱스를 찾습니다. 항목은 LinkedList에 저장되므로 항목이 있으면 equals() 메서드를 사용하여 전달된 키가 이미 있는지 확인하고, 있으면 값을 덮어쓰고, 없으면 새 항목을 생성하고 그런 다음 저장합니다. 키를 전달하여 get 메소드를 호출하면 다시 hashCode()를 사용하여 배열에서 인덱스를 찾은 다음 equals() 메소드를 사용하여 올바른 항목을 찾은 다음 해당 값을 반환합니다. 아래 이미지는 자세한 내용을 설명합니다.
HashMap과 관련된 다른 중요한 문제는 용량, 부하율 및 임계값 조정입니다. HashMap의 기본 초기 용량은 32이고 부하율은 0.75입니다. 임계값은 로드 요소에 용량을 곱한 값입니다. 항목을 추가하려고 할 때마다 맵 크기가 임계값보다 크면 HashMap은 맵 콘텐츠를 다시 해시하고 더 큰 용량을 사용합니다. 용량은 항상 2의 거듭제곱이므로 데이터베이스에서 가져온 캐싱 데이터와 같이 많은 수의 키-값 쌍을 저장해야 한다는 것을 알고 있는 경우 다음을 사용하여 HashMap을 초기화하는 것이 좋습니다. 올바른 용량 및 부하율 관행.
HashMap은 Key 객체의 hashCode() 및 equals() 메서드를 사용하여 키-값 쌍의 인덱스를 결정합니다. 이 메소드는 HashMap에서 값을 얻으려고 할 때도 사용됩니다. 이러한 메서드가 올바르게 구현되지 않으면 두 개의 서로 다른 키가 동일한 hashCode() 및 equals() 출력을 생성할 수 있으며 HashMap은 이를 동일한 것으로 간주하여 다른 위치에 저장하는 대신 덮어씁니다. 마찬가지로 중복된 데이터의 저장을 허용하지 않는 모든 컬렉션 클래스는 중복된 데이터를 찾기 위해 hashCode()와 equals()를 사용하므로 올바르게 구현하는 것이 매우 중요합니다. 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)Set
(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는 ArrayList만큼 addAll, RemoveAll, iterator 등의 기능을 제공하지 않습니다. 분명히 ArrayList가 더 나은 선택이지만 Array가 더 나은 경우도 있습니다.
(1) 목록의 크기를 지정한 경우 대부분 저장하고 순회합니다.
(2) 기본 데이터 유형을 탐색하는 경우 컬렉션은 코딩 작업을 쉽게 하기 위해 오토박싱을 사용하지만 지정된 크기의 기본 유형 목록 작업은 느려질 수 있습니다.
(3) 다차원 배열을 사용하려면 List보다 [][]를 사용하는 것이 더 쉽습니다.
ArrayList와 LinkedList는 모두 List 인터페이스를 구현하지만 둘 사이에는 몇 가지 차이점이 있습니다.
(1) ArrayList는 Array에서 지원하는 인덱스 기반의 데이터 구조이므로 복잡도가 O(1)인 요소에 대한 임의 액세스를 제공하지만 LinkedList는 일련의 노드 데이터를 저장하며 각 노드는 이전 노드와 다음 노드에 연결됩니다. 따라서 인덱스를 사용하여 요소를 얻는 방법이 있지만 내부 구현은 시작점에서 순회하여 인덱스된 노드까지 순회한 후 요소를 반환하는 방식으로 시간 복잡도가 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) 및 compleOf(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는 컬렉션에 대해 작동하거나 컬렉션을 반환하는 정적 메서드만 포함하는 유틸리티 클래스입니다. 여기에는 컬렉션에서 작동하고 지정된 컬렉션이 지원하는 새 컬렉션을 반환하는 다형성 알고리즘과 기타 몇 가지 기능이 포함되어 있습니다. 이 클래스에는 이진 검색, 정렬, 셔플링 및 역순 정렬과 같은 컬렉션 프레임워크 알고리즘에 대한 메서드가 포함되어 있습니다.
Array나 Collection의 정렬 방식을 사용하려면 Java에서 제공하는 Comparable 인터페이스를 사용자 정의 클래스에 구현해야 합니다. Comparable 인터페이스에는 정렬 메서드에서 사용하는 CompareTo(T OBJ) 메서드가 있습니다. "this" 개체가 전달된 개체 인수보다 작거나 같거나 큰 경우 음의 정수, 0 또는 양의 정수를 반환하도록 이 메서드를 재정의해야 합니다. 그러나 대부분의 실제 사례에서는 다양한 매개변수를 기준으로 정렬하려고 합니다. 예를 들어, 저는 CEO로서 급여를 기준으로 직원을 정렬하고 HR은 연령을 기준으로 직원을 정렬하려고 합니다. Comparable.compareTo(Object o) 메소드 구현은 하나의 필드를 기준으로만 정렬할 수 있고 객체 정렬 요구 사항에 따라 필드를 선택할 수 없기 때문에 Comparator 인터페이스를 사용해야 하는 상황입니다. Comparator 인터페이스의 Compare(Object o1, Object o2) 메서드를 구현하려면 두 개의 개체 매개변수를 전달해야 합니다. 첫 번째 매개변수가 두 번째 매개변수보다 작을 경우 첫 번째 매개변수가 두 번째 매개변수인 0과 같으면 음의 정수가 반환됩니다. 첫 번째 매개변수가 두 번째 매개변수보다 작으면 음의 정수가 반환됩니다. 하나가 두 번째 매개변수보다 크면 양의 정수가 반환됩니다.
Comparable 및 Comparator 인터페이스는 객체 컬렉션이나 배열을 정렬하는 데 사용됩니다. Comparable 인터페이스는 객체의 자연스러운 순서를 제공하는 데 사용되며 단일 논리를 기반으로 순서를 제공하는 데 사용할 수 있습니다.
Comparator 인터페이스는 다양한 정렬 알고리즘을 제공하는 데 사용됩니다. 주어진 개체 컬렉션을 정렬하는 데 사용해야 하는 비교기를 선택할 수 있습니다.
객체 배열을 정렬해야 하는 경우 Arrays.sort() 메서드를 사용할 수 있습니다. 객체 목록을 정렬해야 하는 경우 Collection.sort() 메서드를 사용할 수 있습니다. 두 클래스 모두 자연 정렬(Comparable 사용) 또는 기준 기반 정렬(Comparator 사용)을 위해 오버로드된 sort() 메서드를 가지고 있습니다. 컬렉션은 내부적으로 배열 정렬 방법을 사용하므로 둘 다 동일한 성능을 갖습니다. 단, 컬렉션은 목록을 배열로 변환하는 데 시간이 걸립니다.
37. 주어진 컬렉션에서 동기화된 컬렉션을 어떻게 생성하나요?
38. 컬렉션 프레임워크에 구현되는 일반적인 알고리즘은 무엇인가요?
39. 대문자 O는 무엇인가요? 몇 가지 예를 들어볼까요?
40. Java Collections Framework와 관련된 모범 사례는 무엇입니까?
(2) 일부 컬렉션 클래스에서는 초기 용량을 지정할 수 있으므로 저장된 요소 수를 추정할 수 있으면 이를 사용하고 재해싱이나 크기 조정을 피할 수 있습니다.
(3) 구현 기반 프로그래밍이 아닌 인터페이스 프로그래밍 기반으로 나중에 구현을 쉽게 변경할 수 있습니다.
(4) 런타임 시 ClassCastException을 방지하려면 항상 유형이 안전한 제네릭을 사용하세요.
(5) hashCode() 및 equals()를 직접 구현하는 것을 피하기 위해 JDK에서 제공하는 불변 클래스를 Map의 키로 사용합니다.
(6) 가능한 한 컬렉션 도구 클래스를 사용하거나, 직접 구현을 작성하는 대신 읽기 전용, 동기화 또는 빈 컬렉션을 가져옵니다. 코드 재사용성을 제공하고 안정성과 유지 관리성이 향상됩니다.
위 내용은 Java 컬렉션 인터뷰 질문 및 답변 요약의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!