▩ 목 차 ▩
1. 자바 컬렉션
2. List 인터페이스와 그 동생들
2-1. java.util 패키지
2-1-1. ArrayList와 Vector
2-1-2. Stack
2-1-3. LinkedList
3. ArrayList에 대해서 파헤쳐보자
3-1. ArrayList의 생성자는 3개다
3-2. ArrayList에 데이터를 담아보자.
3-2-1. add(E e)
3-2-2. add(int index, E e)
3-3. ArrayList에 데이터를 꺼내자 [ size(), get(), toArray() ]
3-4. ArrayList에 있는 데이터를 삭제하자. [ clear(), remove(), removeAll() ]
3-5. ArrayList에 있는 데이터를 변경하자. [ set() ]
4. Stack 클래스는 뭐가 다른데?
5. 정리
■ 1. 자바 컬렉션 ■
자바에서 컬렉션은 목록성 데이터를 처리하는 자료 구조를 통칭한다.
자료구조란 영어로 "Data Structure"라고 한다. 다시 말해서, 어떤 정보를 담는 것을 의미하는데, 하나의 데이터가 아닌 여러 데이터를 담을 때 사용한다.
우리가 배운 것 중에 배열이 가장 기본적인 자료 구조다. 어떻게 보면 DTO도 자료를 담는 한 방식이라고 볼 수 있다.
대부분 "배열에 담으면 되지 뭐" 라고 생각할 것이다. 배열에 담는 것은 문제가 되지 않는다. 성능상이나 메모리 효율면에서 가장 좋다.
하지만 배열은 그 크키가 정해져 있을 때 유용하다.
담으려는 데이터의 크기가 얼마나 되는지 모르는 경우에는 어떻게 할 것인가?
- int의 최대값에 해당하는 크기를 갖는 배열을 만든다.
- 배열의 크기가 부족하면, 필요한 개수만큼 더 큰 배열을 하나 더 만들어서 거기다 복사한다.
위의 두 방법이 있을 것이다.
첫번째 방법은 메모리 낭비가 엄청나게 발생하기 때문에 절대 취하면 안된다.
두번째 방법은 직접 개발해도 되지만 클래스가 이미 존재하기 때문에 쓰면 된다.
자바에서의 데이터를 담는 자료 구조는 크게 다음과 같이 구분할 수 있다.
- 순서가 있는 목록형(List) [ArrayList, LinkedList]
- 순서가 중요하지 않은 셋형(Set) [HashSet, TreeSet, LinkedHashSet]
- 먼저 들어온 것이 먼저 나가는 큐형(Queue) [LinkedList, PriorityQueue]
- 키-값(key-value)으로 저장되는 맵형(Map) [HashMap, TreeMap, LinkedHashMap]
자바에서는 목록(List), 셋(Set), 큐(Queue)는 Collection이라는 인터페이스를 구현하고 있다.
이 Collection 인터페이스는 java.util 패키지에 선언되어 있으며,여러 개의 객체를 하나의 객체에 담아 처리할 때 공통적으로 사용되는 여러 메소드들을 선언해 놓았다.
이 목록에서 유일한 맵(Map)만이 Collection과 관련 없는 별도의 인터페이스로 선언되어 있다.
이 장에서는 목록(List)부터 배워보자.
<자바의 컬렉션과 관련된 클래스들>
먼저 "목록"과 "셋", "큐"의 기본이 되는 Collection 인터페이스에 대해서 살펴보자.
Collection 인터페이스는 다음과 같이 선언되어 있다.
public interface Collection<E> extends Iterable<E>
Collection 인터페이스 선언문에서 특이한 것은 Iterable<E>이라는 인터페이스를 확장(extends) 했다는 점이다. 여기서 Iterable 인터페이스에 선언되어 있는 메소드는 단지 iterator() 메소드 하나다.
iterator()라는 메소드만 Iterable 인터페이스에 선언되어 있고, 이 메소드는 Iterator라는 인터페이스를 리턴한다.
[ Iterator라는 인터페이에는 추가 데이터가 있는 확인하는 hasNext()메소드, 현재 위치를 다음 요소로 넘기고 그 값을 리턴해주는 next()라는 메소드, 데이터를 삭제하는 remov() 메소드가 있다. ]
결론적으로, Collection 인터페이스가 Iterable 인터페이스를 확장했다는 의미는, Iterator 인터페이스를 사용하여 데이터를 순차적으로 가져올 수 있다는 의미이다.
Collection 인터페이스에 선언된 주요 메소드들의 목록을 살펴보자. [ 여기서 "요소"라는 것은 영어로 "Element"이며, 컬렉션에 저장되는 각각의 데이터를 말한다. ]
■ 2. List 인터페이스와 그 동생들 ■
배열과 비슷한 "목록"에 대해서 알아보자.
"목록"은 List 인터페이스로부터 시작되며, 이 List 인터페이스는 Collection 인터페이스를 확장하였다.
==> 따라서, 몇몇 추가된 메소드를 제외하고는 Collection에 선언된 메소드와 큰 차이는 없다.
Collection을 확장한 다른 인터페이스와 List 인터페이스의 가장 큰 차이점은 배열처럼 "순서"가 있다는 것이다.
List 인터페이스를 구현한 클래스들은 매우 많다. 그 많은 클래스들 중에서 java.util 패키지에서는 ArrayList, Vector, Stack, LinkedList를 많이 사용한다.
■ 2-1. java.util 패키지
■ 2-1-1. ArrayList와 Vector
이 중 ArrayList와 Vector 클래스의 사용법은 거의 동일하고 기능도 거의 비슷하다. 이 두 클래스는 "확장 가능한 배열" 이라고 생각하면 된다. 차이점으로는 ArrayList의 객체는 여러명이 값을 변경하려고 하면 문제가 발생할 수 있고, Vector는 그렇지 않다는 것이다.
즉, ArrayList는 Thread safe하지 않고, Vector는 Thread safe하다.
[ Vector에 대해서는 별도로 설명하지 않을 것이다. 대부분의 메소드가 ArrayList와 동일하고 보통 Vector보다 ArrayList를 많이 선호하기 때문이다. 따라서 ArrayList에 대해서 잘 알고 있으면, Vector도 쉽게 사용할 수 있다. ]
■ 2-1-2. Stack
Stack이라는 클래스는 Vector 클래스를 확장하여 만들었다. 이 클래스를 만든 가장 큰 이유는 LIFO를 지원하기 위함이다.
LIFO는 Last In First Out의 약자로, 가장 마지막에 추가한 값을 가장 처음 빼 내는것이다.
프로그래밍 언어에서 "스택"이라는 의미는 보통 메소드가 호출된 순서를 기억하는 장소를 말한다.
a() 메소드가 b()메소드를 호출하고, b()메소드는 다시 c() 메소드를 호출한다고 가정해보자.
위에서부터 메소드의 호출이 되고 사라지면 서서히 밑에만 남는 구조이다.
이러한 구조의 데이터를 다룰 필요가 있을 때 Stack 클래스를 잘 활용하면 된다.
■ 2-1-3. LinkedList
LinkedList는 "목록"(List) 에도 속하지만, "큐"(Queue)에도 속한다. 자세한 설명은 추후에 배우자.
■ 3. ArrayList에 대해서 파헤쳐보자 ■
ArrayList 클래스의 상속관계를 살펴보자.
ArrayList의 가장 상위 부모는 Object 클래스다. 그 다음에는 AbstractCollection, AbstractList의 순으로 확장했다.
따라서, AbstractCollection은 Collection 인터페이스 중 일부 공통적인 메소드를 구현해 놓은 것이며, AbstractList는 List 인터페이스 중 일부 공통적인 메소드를 구현해 놓은 것이라고 생각하면 된다.
ArrayList가 구현한 모든 인터페이스들은 다음과 같다.
Serializable, Clonealbe, Iterable<E>, Collection<E>, List<E>, RandomAccess
==> 이와 같은 인터페이스들을 ArrayList가 구현했다는 것은 각 인터페이스에서 선언한 기능을 ArrayList에서 사용할 수 있다는 말이다.
■ 3-1. ArrayList의 생성자는 3개다
ArrayList는 "확장 가능한 배열" 이다.
==> 따라서, 배열처럼 사용하지만 대괄호는 사용하지 않고, 메소드를 통해서 객체를 넣고, 빼고, 조회한다.
[EX] - ArrayList() 생성
package part22;
import java.util.ArrayList;
public class ListSample {
public static void main(String[] args) {
ListSample sample = new ListSample();
sample.checkArrayList1();
}
public void checkArrayList1() {
ArrayList list1 = new ArrayList();
}
}
위의 코드를 보자.
ArrayList 객체를 생성하기 위해선 java.lang 패키지가 아닌 java.util 패키지를 Import를 하여 사용해야 한다.
public void checkArrayList1() {
ArrayList list1 = new ArrayList();
list1.add(new Object());
list1.add("ArrayListSample");
list1.add(new Double(1));
}
위 코드를 보자.
ArrayList 클래스를 이용해 객체를 생성하면 이 ArrayList 객체에 어떤 객체도 넣을 수 있다. 넣을 때는 add()메소드를 사용한다.
그런데 보통 ArrayList는 위처럼 사용하지 않는다.
==> 대부분 서로 다른 종류의 객체를 하나의 배열에 넣지 않고, 한 가지 종류의 객체만 저장한다.
여러 종류를 하나의 객체를 담을 때에는 되도록이면 DTO라는 객체를 하나 만들어서 담는 것이 좋다.
==> 컬렉션 관련 클래스의 객체들을 선언할 때에는 제네릭을 사용하여 내가 어떤 객체의 클래스 타입을 명시해주어 사용하는것을 권장한다.
예를 들어, String만 담는(제네릭 꺽쇠를 이용해 String만 담으라고 명시) ArrayList를 생성할 때에는 다음과 같이 사용하면 된다.
ArrayList<String> list1=new ArrayList<String>();
[ JDK 7부터 생성자를 호출하는 부분(new 뒤에 있는 부분)에 따로 타입을 적지 않고 <>로만 사용해도 된다. ]
ArrayList<String> list1=new ArrayList<>();
public void checkArrayList1() {
ArrayList<String>> list1 = new ArrayList();
list1.add(new Object());
list1.add("ArrayListSample");
list1.add(new Double(1));
}
위의 메소드를 보게되면 컴파일 에러가 발생한다.
==> 제네릭을 이용하여 String만을 저장할 수 있도록 ArrayList의 객체를 생성했는데 이 객체에 String 문자열의 값을 넣지 않고 다른 클래스 타입을 넣었기 떄문이다. 이렇게 제네릭을 사용하면 컴파일 시점에 타입을 잘못 지정한 부분을 걸러 낼 수 있다.
ArrayList 객체를 선언할 때 매개변수를 넣지 않으면, 초기 크기는 10이다.
==> 따라서, 10개 이상의 데이터가 들어가면 크기를 늘이는 작업이 ArrayList 내부에서 자동으로 수행된다.
[ 이러한 작업이 수행되면, 애플리케이션 성능에 영향을 주게 된다. 만약 저장되는 크기가 어느정도 예측 가능하다면 다음과 같이 예측한 초기 크기를 지정할 것을 권장한다. ]
ArrayList<String> list1=new ArrayList<>(100);
■ 3-2. ArrayList에 데이터를 담아보자.
ArrayList에 데이터를 담는 메소드를 살펴보자.
add()와 addAll()메소드만 기억하자.
- add() : 하나의 데이터를 담을 때
- addAll() : Collection을 구현한 객체를 한꺼번에 담을 때
ArrayList는 확장된 배열 타입이기 때문에 배열처럼 순서가 매우 중요하다.
■ 3-2-1. add(E e)
이 메소드를 사용하여 데이터를 저장하면 배열의 가장 끝에 데이터를 담는다. 이 메소드를 사용하여 데이터를 추가했을 때 리턴되는 boolean 값은 제대로 추가 되었는지 여부를 말한다.
■ 3-2-2. add(int index, E e)
index와 함께 데이터를 넘겨주는 add()메소드는 지정된 위치에 데이터를 담는다.
==> 그렇기에 이 경우에는 지정된 위치에 있는 기존 데이터들은 위치가 하나씩 뒤로 밀려난다.
[EX] - add(E e), add(int index, E e)
public void checkArrayList2() {
ArrayList<String> list = new ArrayList();
list.add("A");
list.add("B");
list.add("C");
list.add("D");
list.add("E");
list.add(1,"A1");
for(String tempdata : list) {
System.out.println(tempdata);
}
}
A
A1
B
C
D
E
위의 코드를 보자.
- ArrayList 클래스로부터 제네릭을 이용한 String을 명시한 list 객체를 생성한다.
- add(E e), add(int index, E e)를 이용하여 데이터를 추가해준다.
[ add(int index, E e) 경우 원하는 위치에 데이터를 추가하는 메소드이다. 만약 데이터를 add 보다 큰 수를 입력하면 java.lang.IndexOutOfBoundsException 오류가 뜬다. 당연히 add의 수보다 큰 곳에 추가하려고 했기 때문이다. ] - list에 추가된 데이터의 결과를 출력하기 위해서 for(콜론)을 이용하여 출력해준다.
[ 주의해야 할 사항은 add(int index, E e) 메소드의 경우 index 값 부분에서 배열의 가장 첫 위치는 0부터 시작한다. ]
[EX] - addAll()
public void checkArrayList2() {
ArrayList<String> list = new ArrayList();
list.add("A");
list.add("B");
list.add("C");
list.add("D");
list.add("E");
list.add(1,"A1");
ArrayList<String> list2 = new ArrayList();
list2.add("0 ");
list2.addAll(list);
for(String tempdata : list2) {
System.out.println("List2 " + tempdata);
}
}
List2 0
List2 A
List2 A1
List2 B
List2 C
List2 D
List2 E
위의 코드를 보자.
list2라는 ArrayList의 객체를 새로 만들고 addAll() 메소드를 사용하여 값(Collection값:ArrayList객체)를 추가했다.
==> 그러면 비어있는 list2 객체에 list의 값들이 들어갈 것이고, addAll() 메소드를 호출하기 전에 추가한 "0" 이후에 저장된 것을 알 수 있다.
만약 list의 값을 list2에 복사해야 할 일이 생긴다면, 생성자를 사용하면 편리한다.
ArrayList<String> list2=new ArrayList<String>(list);
==> ArrayList에는 Coolection 인터페이스를 구현한 어떠한 클래스도 포함시킬 수 있는 생성자가 있기 때문에 가능하다.
[ 자바를 개발하다 보면 매우 다양한 타입의 객체를 저장할 필요가 있다. AraayList도 포함이다. ]
[EX] - ArrayList 치환
public void checkArrayList4() {
ArrayList<String> list = new ArrayList<String>();
list.add("A");
ArrayList<String> list2=list;
list.add("Ooops");
for(String tempdata : list2) {
System.out.println("List2 :"+ tempdata);
}
}
List2 :A
List2 :Ooops
위의 코드를 보자.
결과를 보게 되면 저장하지 않은 "Ooops" 가 저장되어 있다. 이렇게 처리되는 이유는 반드시 알고 있어야만 한다. 엄청 중요!
list2 = list;
위의 문장은 list2가 list의 값만 사용하겠다는 것이 아니다. list라는 객체(heap 영역)가 생성되어 참조 되고 있는 주소(stack 영역)까지도 사용하겠다는 말이다.
==> 따라서, 하나의 Collection 관련 객체를 복사할 일이 있을 떄에는 생성자를 이용하거나, addAll()메소드를 사용할 것을 권장한다.
왜냐하면 메모리 관점에서생성되어 있는 ArrayList 객체(list)를 "="대입 연산자를 이용하여 생성하여 다른 객체를 생성하게 되면 Stack영역에 다른 객체(list2)가 할당하게 된다. 이 Stack영역에 있는 list와 list2는 레퍼런스 변수로 실제값을 가지고 있지 않고 heap영역에 있는 실제값을 지닌 영역을 가르키는 변수다. 이때 heap 영역에 가르키는 영역이 일치하기 때문에 이 heap영역에 있는 실제값이 값이 바뀌면 list와 list2 둘 다 값이 바뀌게 되는것이다. [ 실제값을 바꾸는 방법은 list나 list2의 add()메소드를 이용하는 것이다. ]
아래 사진은 메모리 관점에서의 말을 그림으로 표현한 사진이다.
■ 3-3. ArrayList에 데이터를 꺼내자 [ size(), get(), toArray() ]
이제 ArrayList 객체에 있는 값을 꺼내고 삭제하는지 알아보자. 그 전에 알아야 하는 메소드가 있다.
==> 바로 ArrayList 객체에 들어가 있는 데이터의 개수를 가져오는 size()메소드다. 즉, Collection을 구현한 인터페이스는 size()메소드를 통하여 들어가 있는 데이터를 확인하다. size()메소드의 리턴 타입은 int다. [ 배열에 넣을 수 있는 공간의 개수를 가져올 때에는 배열.length를 사용한다. 그리고, String 문자열의 길이를 가져오는 것도 length() 메소드를 사용한다. ]
정리하자면 배열.length는 배열의 저장 공간 개수를 의미하지만, size()메소드는 ArrayList의 저장공간 개수를 말하는 것이 아닌 들어가 있는 데이터 개수를 의미한다.
[EX] - 인덱스를 사용하기위해 forloop방식을 사용하여 ArrayList의 객체안에 있는 데이터 출력
public void checkArrayList5() {
ArrayList<String> list = new ArrayList<String>();
list.add("A");
list.add("B");
int listSize=list.size();
System.out.println("data total count : "+listSize);
for (int loop=0; loop<listSize; loop++) {
System.out.println("list.get("+loop+")="+list.get(loop));
}
}
data total count : 2
list.get(0)=A
list.get(1)=B
위의 코드를 보자.
for 루프를 세미콜론 방식(forloop방식)을 사용하였다.[ 인덱스(위치)에 접근하기 위해 ]
이 for 방식(세미콜론)으로 ArrayList 객체에 있는 값을 가져올 때에는 get()메소드를 사용한다.
[ 한 건의 데이터를 꺼내는 메소드는 get()메소드 뿐이다. ]
위치로 데이터를 꺼내는 get() 메소드와 반대로 앞에서 부터 데이터로 위치를 찾아내는 indexOf()메소드와 뒤에서 부터 데이터로 위치를 찾아내는 lastIndexOf()라는 메소드도 있다.
간혹 ArrayList 객체에 있는 데이터들을 배열로 뽑아낼 필요가 있다. 그럴때에는 toArray() 메소드를 사용하면 된다.
여기서 중요한 것은 매개변수가 없는 toArray()메소드는 Object 타입의 배열로만 리턴을 한다는 것이다.
==> 즉, 제네릭을 사용하여 선언한 ArrayList 객체를 배열로 생성할 때에는 이 메소드를 사용하는 것은 옳지 않다.
두 번째에 있는 메소드[ toArray(T[] a) ]를 사용하는 것을 적극 추천한다.
[EX] - toArray(T[] a)
public void checkArrayList6() {
ArrayList<String> list = new ArrayList<String>();
list.add("A");
String[] strList = list.toArray(new String[0]);
System.out.println(strList[0]);
}
위의 코드를 살펴보자.
toArray() 메소드의 매개변수로 변환하려는 타입의 배열을 지정해주면 된다. 여기서 변수로 넘어가는 "new String[0]"을 자세히 보자.
매개변수로 넘기는 배열은 그냥 의미없이 타입만을 지정하기 위해서 사용할 수도 있다.
==> 그런데 실제로는 매개 변수로 넘긴 객체에 값을 담아준다. 하지만, ArrayList 객체의 데이터 크기가 매개변수로 넘어간 배열 객체의 크기보다 클 경우에는 매개 변수로 배열의 모든 값이 null로 채워진다. 예시로 한번 찾아보자
[EX] - ArrayList 객체의 크기가 매개변수로 넘어간 배열 객체의 크기보다 클 경우
public void checkArrayList7() {
ArrayList<String> list = new ArrayList<String>();
list.add("A");
list.add("B");
list.add("C");
String[] strList = list.toArray(new String[5]);
for(String tempdata:strList) {
System.out.println(tempdata);
}
}
A
B
C
null
null
위의 코드를 보자.
list.toArray(new String[5]); 구문을 이용하여 배열을 생성한 공간은 String 배열로 5개인데 list 객체에는 Add로 데이터가 추가된 것이 3개밖에 없기 때문에 5개중에 3개는 데이터가 넣어지지만, 나머지 2개는 null로 채워지게 된다.
==> 이것을 보면 알 수 있듯이 toArray()메소드를 사용할 때에는 크기가 0인 배열을 넘겨주는것이 좋다. 즉, [0] 말이다.
■ 3-4. ArrayList에 있는 데이터를 삭제하자. [ clear(), remove(), removeAll() ]
clear()메소드는 ArrayList의 데이터들을 다 지운다.
remove() 메소드는 get()메소드와 동일하게 지정된 위치의 데이터를 리턴하긴 하지만, 지정한 그 위치의 데이터를 지우고 리턴한다.
[ remove() 메소드는 많이 사용하므로 꼭 기억하자. ]
객체를 넘겨주는 remove()메소드와 컬렉션 객체를 넘겨주는 removeAll()을 보면 거의 동일한 기능을 하는것처럼 보일 수 있지만 약간 다르다. 객체를 넘겨주는 remove() 메소드는 매개 변수로 넘어온 객체와 동일한 첫번째 데이터만 삭제한다. 하지만, removeAll()메소드는 매개변수로 넘어온 컬렉션에 있는 데이터와 동일한 모든 데이터를 삭제한다.
[EX] - remove(int index)
public void checkArrayList8() {
ArrayList<String> list = new ArrayList<String>();
list.add("A");
list.add("B");
list.add("C");
list.add("A");
System.out.println("Removed "+ list.remove(0));
// System.out.println(list.remove("A"));
// ArrayList<String> temp = new ArrayList<String>();
// temp.add("A");
// list.addAll(temp);
for(int loop=0; loop<list.size(); loop++) {
System.out.println("list.get("+loop+")="+list.get(loop));
}
}
Removed A
list.get(0)=B
list.get(1)=C
list.get(2)=A
[EX] - remove(Object o)
public void checkArrayList8() {
ArrayList<String> list = new ArrayList<String>();
list.add("A");
list.add("B");
list.add("C");
list.add("A");
// System.out.println("Removed "+ list.remove(0));
System.out.println(list.remove("A"));
// ArrayList<String> temp = new ArrayList<String>();
// temp.add("A");
// list.addAll(temp);
for(int loop=0; loop<list.size(); loop++) {
System.out.println("list.get("+loop+")="+list.get(loop));
}
}
true
list.get(0)=B
list.get(1)=C
list.get(2)=A
위 코드를 보자.
list.remove("A"); 구문을 통해 "A" 값을 삭제하고 삭제가 성공적으로 되어 true값이 리턴되어 출력되는 것을 볼 수 있다.
하지만 제일 첫번째 "A"는 삭제되었지만 마지막에 있는 "A"는 삭제되지 않았다. [ "A"라는 문자열은 String Object이기 때문에 remove(Object o)의 메소드로 실행이 되어 매개변수로 넘어온 객체와 동일한 첫번째 데이터만 삭제한다. ]
[EX] - removeAll(Collection<?> c)
public void checkArrayList8() {
ArrayList<String> list = new ArrayList<String>();
list.add("A");
list.add("B");
list.add("C");
list.add("A");
//// System.out.println("Removed "+ list.remove(0));
// System.out.println(list.remove("A"));
ArrayList<String> temp = new ArrayList<String>();
temp.add("A");
list.removeAll(temp);
for(int loop=0; loop<list.size(); loop++) {
System.out.println("list.get("+loop+")="+list.get(loop));
}
}
list.get(0)=B
list.get(1)=C
위의 코드를 보자.
"A"라는 값을 갖는 모든 데이터가 사라진 것을 볼 수 있다. 즉, ArrayList의 객체인 temp 객체(Collecton)에 "A"라는 문자열 데이터를 가지고 있는데 list.removeAll(Collection<?> c)를 통해 매개변수로 temp 객체를 넘겨주어 temp 객체 안에 있는 데이터("A")와 동일한 list객체의 데이터 "A"를 삭제한다.
■ 3-5. ArrayList에 있는 데이터를 변경하자. [ set() ]
이 set(int index, E element)메소드를 통해 특정 위치에 있는 데이터를 내가 원하는 데이터값으로 변경할 수 있다.
또한 trimToSize()라는 메소드가 있는데, ArrayList의 객체 공간의 크기를 데이터의 개수만큼으로 변경한다.
[ 이 메소드는 일반적인 경우에는 사용할 일이 없는데 만약, ArrayList의 객체를 원격으로 전송하거나, 파일로 저장하는 일이 있을 때 이 메소드를 호출시켜 데이터의 크기를 줄일 수 있다. ]
앞에서 Vector라는 클래스도 있다고 말했다.
Vector는 쓰레드에 안전하고, ArrayList는 쓰레드에 안전하지 않다.
==> 따라서, ArrayList를 여러 쓰레드에서 덤벼도 안전하게 만들려면 다음과 같이 객체를 생성해야만 한다.
List list = Collections.sychoronizedList(new ArrayList(...));
■ 4. Stack 클래스는 뭐가 다른데? ■
List 인터페이스를 구현한 또 하나의 클래스인 Stack 클래스에 대해서 살펴보자.
일반적인 웹을 개발할 때에는 별로 많이 사용하지는 않지만, 마지막에 들어온 데이터를 가장 처음에 꺼내는 LIFO 기능을 구현하려고 할 때 필요한 클래스이다.
[ *LIFO(Last In First Out) : 후입선출 나중에 들어온 값을 먼저 처리하는 것을 의미 ]
사실 LIFO 후입선출 기능을 위해서 이 클래스를 사용하는 것은 권장하지 않는다.
==> 이 클래스보다 더 빠른 ArrayDeque라는 클래스가 존재하기 떄문이다. 하지만, ArrayDeque클래스는 쓰레드에 안전하지 못하다.
즉, 성능은 떨어지지만, 쓰레드에 안전한 LIFO 기능을 원한다면 Stack 클래스를 사용해라. 하지만 빠른 성능을 원한다면 ArrayDeque클래스를 사용해라.
먼저 간단하게 Stack 클래스의 상속관계를 살펴보자.
Stack 클래스의 부모 클래스는 Vector인 것을 볼 수 있다.
==> 즉, Vector 클래스에서 제공하는 모든 메소드를 사용할 수 있다.
나머지는 ArrayList와 동일한 AbstactCollection, AbstractList 이다.
또한 Stack 클래스에서 구현한 인터페이스는 Seralizable, Cloneable, Interable<E>, Collection<E>, List<E>, RandomAccess로 ArrayList 클래스에서 구현한 인터페이스와 모두 동일하다.
자바애서 Stack 클래스의 생성자는 단 하나다. [ Stack() : 아무 데이터도 없는 Stack 객체를 만든다 ]
Stack클래스에서 사용가능한 메소드들을 보자.
여기서 peek()와 pop() 메소드의 차이는 매우 중요하니 꼭 기억하기 바란다.
- peek() : 가장 위에 있는 데이터를 리턴
- pop() : 가장 위에 있는 데이터를 지우고 리턴
일반적인 Stack 클래스의 용도에는 pop()메소드가 적합하다.
[EX] - Stack 클래스의 peek()메소드
package part22;
import java.util.Stack;
public class StackSample {
public static void main(String[] args) {
StackSample sample = new StackSample();
sample.checkPeek();
}
public void checkPeek() {
Stack<Integer> intStack = new Stack<Integer>();
for(int loop=0; loop<5; loop++) {
intStack.push(loop); //1
System.out.println(intStack.peek()); //2
}
System.out.println("size="+intStack.size());
}
}
0
1
2
3
4
size=5
위의 코드에 달린 숫자 주석을 보자.
- loop 값을 push() 메소드를 사용하여 저장한다.
- peek()메소드를 사용하여 가장 위에 있는 값을 출력하고 있다.
그런데 만약 intStack이라는Stack에서 값을 하나씩 확인하면서 제거하도록 하려면 어떻게 해야 할까?
==> empty()메소드를 사용하여 비어있는지 확인하고, pop()메소드로 한 개 씩 제거하면 된다.
[EX] - Stack 클래스의 peek()메소드를 이용하여 데이터를 읽으면서 삭제하기
public void checkPop() {
Stack<Integer> intStack = new Stack<Integer>();
for(int loop=0; loop<5; loop++) {
intStack.push(loop); //1
System.out.println(intStack.pop()); //2
}
System.out.println("size="+intStack.size());
}
0
1
2
3
4
size=0
■ 5. 정리 ■
■ 자바는 자료 구조를 제공한다.
자료 구조는 배열과 같이 데이터를 담아 놓고 필요할 때 꺼내어 사용하기 위해서 존재하며 다음과 같이 분류할 수 있다.
- 순서가 있는 목록형(List) [ArrayList, LinkedList]
- 순서가 중요하지 않은 셋형(Set) [HashSet, TreeSet, LinkedHashSet]
- 먼저 들어온 것이 먼저 나가는 큐형(Queue) [LinkedList, PriorityQueue]
- 키-값(key-value)으로 저장되는 맵형(Map) [HashMap, TreeMap, LinkedHashMap]
■ java.util.Collection
- List, Set, Queue 타입 구현의 모태가 되는 인터페이스
- Iterable 인터페이스가 확장되어 있다.
- add(), addAll() : 데이터 담기용 메소드
- contains(), containsAll(), isempty(), equals(), size() : 데이터 확인용 메소드
- clear(), remove(), removeAll() : 데이터 삭제용 메소드
■ List 인터페이스는 목록을 저장할 때 사용한다. [크기가 변하는 배열이라고 생각하면 이해하기 쉬움 ]
List 인터페이스
- 각 데이터에 대한 위치가 있다.
- 위치에 있는 데이터를 꺼내거나 지우고, 원하는 위치에 데이터를 저장하는 작업을 할 수 있다.
- 특정 데이터의 중복을 허용한다.
- 순서대로 들어오는 데이터를 담아 둘 때 용이하다.
- Collection 인터페이스를 확장하였다.
- List 인터페이스를 구현한 클래스로는 AbstractList, AbstractSequentialList, ArrayList, AttributeList, CopyOnwriteArrayList, LinkedList, RoleList, RoleUnresolvedList, Stack, Vector가 있으며, 이 중에서 ArrayList, LinkedList가 많이 사용된다.
목록형 데이터을 대표하는 Collection이라는 인터페이스에 대해서 알아보았다.
Collection 인터페이스를 확장하는 인터페이스는 List, Queue, Set이 있다.
그 중 이번글은 List 인터페이스와 그 인터페이스를 구현한 ArrayList 클래스에 대해서 자세히 알아보았다.
ArrayList는 아주 많이 사용되기 때문에, 자유 자재로 사용할 수 있도록 필요한 메소드들은 꼭 기억해두자.
그리고 또한 List 인터페이스와 그 인터페이스를 구현하고 Vector 클래스를 상속받은 Stack 클래스는 마지막에 추가한 데이터를 먼저 빼는 LIFO(후입선출) 구조가 필요할 때 사용하면 편하니 기억해두자. [ ArrayDeque는 성능 빠르고 쓰레드에는 안전하지 못하는 LIFO 기능제공 / Stack은 성능 느리고 쓰레드에는 안전한 LIFO 기능 ]
'JAVA > 자바의신 2' 카테고리의 다른 글
24장 자바랭 다음으로 많이 쓰는 애들은 컬렉션 - Part3(Map) (1) | 2022.09.19 |
---|---|
23장 자바랭 다음으로 많이 쓰는 애들은 컬렉션 - Part2(Set과 Queue) (0) | 2022.09.18 |
21장 실수를 방지하기 위한 제네릭 (0) | 2022.09.17 |
20장 가장 많이 쓰는 패키지는 자바랭 (0) | 2022.09.17 |
19장 자바에 대해서 더 알아보자 (1) | 2022.09.15 |