hashCode()
, equals()
구현해야 한다.ArrayList
, LinkedList
new ArrayList<>(1000000);
ensureCapacity()
메서드로 용량을 늘려 크기 조정 작업을 건너뛸 수 있다.add()
작업은 항상 O(1)이다.ArrayList
는 다른 원소를 모두 한 칸씩 밀어야한다.LinkedList
는 특정 인덱스에 추가해도 레퍼런스만 변경하면 된다.ArrayList
는 랜덤 엑세스 하는 경우 모든 원소를 O(1)만에 가져올 수 있다.LinkedList
는 인덱스 카운트만큼 원소를 방문해야 한다.LinkedList
고유 기능이 필요한 경우가 아니고 랜덤 엑세스가 필요한 알고리즘에선 ArrayList
를 권장
java.util.Map<K, V>
인터페이스를 제공하며 키/값 모두 참조형이어야 한다.HashMap
축소 버전 핵심 메서드
public Object get(Object key) {
// 편의상 널 키는 지원하지 않음
if (key == null) return null;
int hash = key.hashCode();
int i = indexFor(hash, table.length);
for (Entry e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
return null;
}
private int indexFor(int h, int length) {
return h & (length - 1);
}
static class Node implements Map.Entry {
final int hash;
final Object key;
Object value;
Node next;
Node(int h, Object k, Object v, Entry n) {
hash = h;
key = k;
value = v;
next = n;
}
}
HashMap.Node
클래스는 java.util
패키지에서만 접근할 수 있다. (정적 클래스 사용하는 전형적인 유스케이스)HashMap 배치
equals()
메서드로 리스트에서 키를 찾는다. (중복 허용 x)indexFor()
메서드를 키 객체의 hashCode()
메서드를 사용하는 코드로 교체
HashMap
생성자에 전달하는 두 매개변수는 성능에 큰 영향을 미친다.
initialCapacity
: 현재 생성된 버킷 개 수(디폴트 16)loadFactor
: 버킷 용량 자동 증가시키는 한계치 (디폴트 2배)HashMap
의 get()
, put()
은 일정 시간이 소요되지만 순회를 하면 비용이 증가할 수 있다.LinkedList
로 구현하면 리스트를 훑어보는 작업이 커질수록 비용이 더 든다.HashMap
은 한 버킷에 TREEIFY_THRESHOLD
값만큼 키/값 쌍이 모이면 버킷을 TreeNode
로 비꾸어 마치 TreeMap
처럼 동작한다.TreeNode
는 리스트 노드에 비해 2배 더 공간을 차지하기에 처음부터 쓰지는 않는다.LinkedHashMap
HashMap
의 서브클래스로 이중 연결 리스트를 사용해 원소의 삽입 순서를 관리한다.TreeMap
처럼 비용이 많이 들지 않는다.TreeMap
은 레드-블랙 트래를 구현한 Map
레드-블랙 트리: 기본 이진 트리 구조에 메타데이터를 부가(노드 컬러링)해서 트리 균형이 치우치는 현상을 방지한 트리
TreeMap
은 다양한 키가 필요할 때 아주 유용하다.
TreeMap
이 제공하는 get()
, put()
, containsKey()
, remove()
메서드는 log(n) 작업 성능을 보장Map
일부를 처리해야할 때 TreeMap
을 쓰는편이 바람직하다.MultiMap
(하나의 키에 여러 값을 묶은 맵) 구현체를 제공하지 않는다.Map<K, List<V>>
형태로도 충분하다.Set
이 존재하며 고려해야할 사항은 Map
과 비슷하다.
HashSet
은 HashMap
으로 구현되어 있다.Set
은 중복을 허용하지 않기에 Map
의 키 원소와 같다.
HashSet
의 add()
메서드가 내부적으로는 HashMap
의 키에 대응되고, 값은 PRESENT
라는 더미 객체로 구성HashSet
의 삽입/삭제, contains
작업의 복잡도는 O(1)
initialCapacity
, loadFactor
에 따라 달라진다.TreeSet
역시 TreeMap
을 활용
Order
, OrderItem
, DeliverySchedule
등Order
에는 여러 OrderItem
인스턴스가 매핑됨jamp -histo
명령이나 VisualVM 같은 GUI 툴을 통해 자바 힙 상태를 살펴볼 수 있는데 도메인 객체 메모리 누수 현상을 효과적으로 진단할 수 있다.누수를 일으키는 도메인 객체는 종종 GC 마킹 시간을 증가시키는 주범이다. 단명 객체가 긴 전체 객체 체인에 걸쳐 살아남기 때문
finalize()
메서드는 자동으로 리소스를 관리하려고 만든 장치close()
호출은 정말 깜빡 잊고 놓치기 쉽다.
Object
의 Finalize()
메서드는 자바 태동기부터 존재했다.
어떤 객체가 더 이상 자신을 참조하지 않는다고 가비지 수집기가 판단하면 그 객체에 있는 finalize() 메서드를 호출한다. 서브 클래스는 finalize() 메서드를 오버라이드해서 시스템 리소스를 처분하는 등 기타 정리 작업을 수행한다.
Object
생성자 바디에서 성공 반환되는 시점에 해당 객체를 등록하는 식으로 JVM에 구현돼 있다.
Finalize()
실행Finalize()
문제점
Finalize()
를 실행해야 한다. (스레드 생성 오버헤드 감수)Object.Finalize()
는 디프리케이트 됐다.자바 7 이전까진 개발자가 책임지고 리소스를 닫아야 했다.
public void readFirstLineOld(File file) throws IOException {
BufferReader reader = null;
try {
reader = new BufferedReader(new FileReader(file));
// ...
} finally {
if (reader != null) {
reader.close();
}
}
}
자바 7부터 try-with-resources 생성자를 통해 AutoCloseable
인터페이스를 구현한 객체를 자동으로 닫을 수 있다.
public void readFirstLineNew(File file) throws IOException {
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
String firstLine = reader.readLine();
// ...
}
}
런타임 GC에 의존하는 종료화와 달리 try-with-resources는 순수한 컴파일 타임 기능이다.
invokedynamic
명령어 덕분에 호출부에서 실행할 메서드를 유연하게 결정할 수 있게 되었다.
invokedynamic
호출부에 의해 호출되는 메서드를 나타낸 객체자바 7부터 일부 클래스, 패키지(MethodHandle
)가 추가되어 실행 가능한 메서드의 레퍼런스를 직접 반영할 수 있게 됐다.
MethodType mt = MethodType.methodType(int.class);
MethodHandles.Lookup l = MethodHandles.lookup();
MethodHandle mh = l.FindVirtual(String.class, "hashCode", mt);
String reveiver = "b";
int ret = (int) mh.invoke(receiver);
System.out.println(ret);
MethodHandles.lookup()
메서드로 컨텍스트 객체를 생성setAccessible()
말고는 방법이 없어 자바의 안전한 접근 제어 체계에 허점을 노출시킨다.대부분 개발자에게 메서드 핸들이란 코어 리플렉션과 기능은 비슷하나 최대한 정적 타입을 안전하게 지키는 요즘 방식의 리플렉션이라고 생각하면 된다.