https://www.javaguides.net/p/java-collections-tutorial.html
자바의 자료구조에서 List, Set, Queue 형의 자료구조는 Collection 인터페이스를 구현한다.
여러 객체를 하나의 객체에 담아 처리할 때 공통적으로 사용되는 여러 메서드들을 선언해 놓았다.
기능
Collection (Java Platform SE 8 )
Collection 인터페이스 기능은 위 문서에 명시되어 있다.
public interface List<E> extends Collection<E> { // 생략 }
List 는 원소가 삽입되는 위치를 제어할 수 있다. 검색이나 읽기 연산에서 index(목록 내 위치) 를 사용한다.An ordered collection (also known as a sequence). The user of this interface has precise control over where in the list each element is inserted. The user can access elements by their integer index (position in the list), and search for elements in the list.
add(*E* e)
Collection 인터페이스의Ensures that this collection contains the specified element (optional operation). Returns true if this collection changed as a result of the call. (Returns false if this collection does not permit duplicates and already contains the specified element.)
add(E e)
는 목록에 매개변수로 받은 원소가 추가가 되면 true를 리턴하도록 구현할 것을 명시했다.List 인터페이스에 선언된Appends the specified element to the end of this list (optional operation).
add(E e)
은 목록의 맨 끝에 요소를 추가할 것을 명시하고 있다. remove(*Object* o)
List 는 중복이 허용되기 때문에 요소 삭제는 목록에서 첫번째로 검색된 요소를 삭제한다.Removes the first occurrence of the specified element from this list, if it is present (optional operation)
equals(Object o)
List 구현 클래스 객체의 equals() 재정의는 두 List 객체가 같은 순서의 같은 객체를 가졌을 경우 동등하다고 판단하도록 명시되어있다.Compares the specified object with this list for equality. Returns true if and only if the specified object is also a list, both lists have the same size, and all corresponding pairs of elements in the two lists are equal. (Two elements e1 and e2 are equal if Objects.equals(e1, e2).) In other words, two lists are defined to be equal if they contain the same elements in the same order. This definition ensures that the equals method works properly across different implementations of the List interface.
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
/**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* Shared empty array instance used for empty instances.
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access
배열의 크기가 아닌 요소의 개수를 저장하는 변수다/**
* The size of the ArrayList (the number of elements it contains).
*
* @serial
*/
private int size;
객체가 관리하는 배열의 크기가 변경된 횟수를 저장한다. protected transient int modCount = 0;
초기화 시 배열의 크기를 지정할 수 있는 생성자
/**
* Constructs an empty list with the specified initial capacity.
*
* @param initialCapacity the initial capacity of the list
* @throws IllegalArgumentException if the specified initial capacity
* is negative
*/
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
기본 생성자 : 디폴트 용량으로 배열의 크기가 초기화 된다.
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
Collection 타입을 매개변수로 받는 생성자
/**
* Constructs a list containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
*
* @param c the collection whose elements are to be placed into this list
* @throws NullPointerException if the specified collection is null
*/
public ArrayList(Collection<? extends E> c) {
Object[] a = c.toArray();
if ((size = a.length) != 0) {
if (c.getClass() == ArrayList.class) {
elementData = a;
} else {
elementData = Arrays.copyOf(a, size, Object[].class);
}
} else {
// replace with empty array.
elementData = EMPTY_ELEMENTDATA;
}
}
원소가 추가될 때마다 modCount를 1 증가한다.
add 내부에서 호출되는 add 헬퍼 메서드에서 용량 증가를 처리한다.
ArrayList<Integer> intList = new ArrayList<>();
intList.add(1);
public boolean add(E e) {
modCount++;
add(e, elementData, size);
return true;
}
인스턴스 필드 elementData 배열의 길이가 배열의 길이를 저장하는 size 변수와 같을 때 배열의 크기를 증가시킨다.
Arrays.copyOf()
을 사용해 증가된 크기의 배열을 생성한 후 기존 배열의 원소를 복사한다.
/**
* This helper method split out from add(E) to keep method
* bytecode size under 35 (the -XX:MaxInlineSize default value),
* which helps when add(E) is called in a C1-compiled loop.
*/
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length)
elementData = grow();
elementData[s] = e;
size = s + 1;
}
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
* @throws OutOfMemoryError if minCapacity is less than zero
*/
private Object[] grow(int minCapacity) {
return elementData = Arrays.copyOf(elementData,
newCapacity(minCapacity));
}
private Object[] grow() {
return grow(size + 1);
}
증가시킬 배열의 크기를 계산하는 방법
grow factor 는 기존 배열 사이즈의 1.5 다.
ArrayList 에 요소를 하나씩 추가하면 grow() 가 더 많이 호출된다고 한다. 아직 이해 못했음.
/**
* Returns a capacity at least as large as the given minimum capacity.
* Returns the current capacity increased by 50% if that suffices.
* Will not return a capacity greater than MAX_ARRAY_SIZE unless
* the given minimum capacity is greater than MAX_ARRAY_SIZE.
*
* @param minCapacity the desired minimum capacity
* @throws OutOfMemoryError if minCapacity is less than zero
*/
private int newCapacity(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity <= 0) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
return Math.max(DEFAULT_CAPACITY, minCapacity);
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return minCapacity;
}
return (newCapacity - MAX_ARRAY_SIZE <= 0)
? newCapacity
: hugeCapacity(minCapacity);
}
ArrayList 는 마커 인터페이스 Cloneable 을 구현함
ArrayList 클래스에 재정의된 clone
은 shallow copy 를 한다.
복사 시 배열의 원소는 복제된 객체가 아닌 단순히 참조값을 복사한다.
/**
* Returns a shallow copy of this {@code ArrayList} instance. (The
* elements themselves are not copied.)
*
* @return a clone of this {@code ArrayList} instance
*/
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
ArrayList<Mock> strList = new ArrayList<>();
Mock bmw = new Mock("BMW");
strList.add(bmw);
ArrayList<Mock> copyList = (ArrayList<Mock>) strList.clone();
bmw.setName("BMW -> Porsche");
System.out.println("strList = " + strList);
System.out.println("copyList = " + copyList);
-----------출력 결과-----------
strList = [Mock{name='BMW -> Porsche'}]
copyList = [Mock{name='BMW -> Porsche'}]
그래서 복재된 리스트 요소 객체들에 변경을 해도 영향을 받지 않으려면 Deep Copy 를 해야 하는데,
자바의 신에서는 생성자를 사용하거나 addAll() 메서드를 사용할 것을 권장하고 있다.
RandomAccess marker interface
![](https://velog.velcdn.com/images/gutenlee/post/7d3a330a-6a2e-4454-986c-624459d5c84d/image.png)
Marker interface used by List implementations to indicate that they support fast (generally constant time) random access. The primary purpose of this interface is to allow generic algorithms to alter their behavior to provide good performance when applied to either random or sequential access lists.
RandomAccess 마커 인터페이스를 구현하면 random access 가 빠르게 동작할 수 있도록 최적의 알고리즘을 적용해야 한다.
Vector 클래스는 ArrayList 와 거의 유사하지만 차이점은 주요 기능 메서드들에 synchronized
키워드가 선언되어있다. ArrayList 와 달리 thread-safe 를 보장한다.
Stack 은 Vector 를 상속한다. Vector 가 LIFO (last-in-first-out) 로 구조로 동작하도록 구현되어 있다.
또한 synchronized
선언되어 있어 thread-safe 를 보장한다.
public class Stack<E> extends Vector<E> {}
Stack 은 상속을 잘못 받은 사례다.
LIFO 구조로 동작하는 의도와는 달리 Vector 를 상속하면서 Vector 의 set()
, remove()
메서드 호출이 가능해 중간 index 위치에 데이터 삽입, 삭제가 가능하다.
Stack<String> stacks = new Stack<>();
stacks.push("A");
stacks.push("B");
stacks.set(1, "C"); // Vector 의 메서드 호출
Stack 클래스 주석에서도 완전한 LIFO 스택 연산을 사용하려면 ArrayDeque 클래스 사용을 권장하고 있다.