Back-end/이것이 자바다[신용권 한빛미디어]

List 컬렉션

Ho's log 2022. 4. 24. 16:13

List 컬렉션은 객체를 일렬로 늘어놓은 구조를 가지고 있다.

객체를 인덱스로 관리하기 때문에 객체를 저장하면 자동 인덱스가 부여되고 인덱스로 객체를 검색, 삭제할 수 있는 기능을 제공한다.

List 컬렉션은 객체 자체를 저장하는 것이 아니라 다음 그림과 같이 객체의 번지를 참조한다. 

동일한 객체를 중복 저장할 수 있는데 이경우 동일한 번지가 참조된다,

null도 저장이 가능한데, 이 경우 해당 인덱스는 객체를 참조하지 않는다. 

List 컬렉션에는 ArrayList, Vector, LinkedList 등이 있는데, 다음은 List 컬렉션에서 공통적으로 사용 가능한 List 인터페이스의 메소드들이다.

인덱스로 객체를 관리하기 때문에 인덱스를 매개값으로 갖는 메소드가 많다 

기능 메소드 설명
객체추가 boolean add(E e) 주어진 객체를 맨끝에 추가
void add(int index, E element) 주어진 인덱스에 객체를 추가
E set(int index, E element) 주어진 인덱스에 저장된 객체를 주어진 객체로 바꿈
객체검색 boolean contains(Object o) 주어진 객체가 저장되어 있는지 여부
E get(int index) 주어진 인덱스에 저장된 객체를 리턴
boolean isEmpty() 컬렉션이 비어 있는지 조사
int size() 저장되어 있는 전체 객체수를 리턴
객체삭제 void clear() 저장된 모든 객체를 삭제
E remove(int index) 주어진 인덱스에 저장된 객체를 삭제
boolrsn remove(Object o) 주어진 객체를 삭제 

 

앞의 표에서 메소드의 매개 변수 타입과 리턴 타입에 E 라는 타입 파라미터가 있는데, 

이것은 List인터페이스가 제네릭 타입이기 때문이다, 구체적인 타입은 구현 객체를 생성할 때 결정된다.

객체 추가는 add()메소드를 사용하고, 객체를 찾아올 때에는 get() 메소드를 사용한다. 그리고 객체 삭제는 remove() 메소드를 사용한다. 다음은 List 컬렉션에 저장되는 구체적인 타입을 String 으로 정해놓고 추가, 삽입, 찾기, 그리고 삭제하는 방법을 보여준다. 

List<String> list = ...;

list.add("홍길동");
list.add(1, "tls"); 

String str = list.get(1);
list.remove(0);
list.remove("tls");

 

만약 전체 객체를 대상으로 하나씩 반복해서 저장된 객체를 얻고 싶다면 다음과 같이 for 문을 사용할수 있다.

List<String> list = ...;

for(int i=0; i<list.size(); i++){

	String str = list.get(i);
}

 

인덱스 번호가 필요 없다면 향샹된 for 문을 이용하는 것이 더욱 편리하다

for(Stirng str : list)

 

ArrayList


ArrayList 는 List 인터페이스의 구현 클래스로, ArrayList에 객체를 추가하면 객체가 인덱스로 관리된다.

일반 배열과 ArrayList는 인덱스로 객체를 관리한다는 점에서는 유사하지만, 큰 차이점을 가지고 있다.

배열은 생성할 때 크기가 고정되고 사용 중에 크기를 변경할 수없지만,

ArrayList는 저장 용량 (capacity)을 초과한 객체들이 들어오면 자동적으로 저장 용량 (capactiy)이 늘어난다는 것이다.

다음은 ArrayList 객체의 내부 구조를 보여 준다

ArrayList 를 생성하기 위해서는 저장할 객체 타입을 타입 파라미터로 표기하고 기본 생성자를 호출하면 된다.

예를 들어 String을 저장하는 ArrayList는 다음과 같이 생성 할 수있다.

List<String> list = new ArrayList<String>();

기본 생성자로 ArrayList 객체를 생성하면 내부에 10개의 객체를 저장할 수 있는 초기 용량(capacity)을 가지게 된다.

저장되는 객체 수가 늘어나면 용량이 자동으로 증가하지만, 처음부터 용량을 크게 잡고 싶다면 용량의 크기를 매개값으로 받는 생성자를 이용하면 된다.

 

List<String> list = new ArrayList<String>(30); // 30개를 저장할수 있는 용량을 가짐

 

자바 4 이전 까지는 타입 파라미터가 없엇기 때문에 다음과 같이 ArrayList 객체를 생성하였다. 

이렇게 생성된 ArrayList는 모든 종류의 객체를 저장 할 수 있다. 

그 이유는 객체가 저장될 때 Object타입으로 변환되어 저장되기 때문이다.

List ist = new ArrayList();

모든 종류의 객체를 저장할 수 있다는 장점은 있지만, 저장 할 대 Object 로 변환 하고, 찾아올때 원래 타입으로 변환 해야 하므로 실행 성능에 좋지 못한 영향을 미친다.

일반적으로 컬렉션에는 단인 종류의 객체들만 저장된다, 그래서 자바 5 부터 제네릭을 도입하여 ArrayList 객체를 생성할 때 타입 파라미터로 저장할 객체의 타입을 지정함으로써 불필요한 타입 변환을 하지 않도록 했다.

 

이후에 소개되는 모든 컬렉션 객체들도 마찬가지 이유로 타입 파라미터를 이용해서 저장할 객체의 타입을 지정할 수 있다. 

다음 코드는 자바 4 이전과 자비 5 이후의 차이점을 잘 보여준다. 

 

//자바 4이전
List list = new ArrayList(); // 컬렉션 생성
list.add("홍길동"); // 컬렉션을 객체에 추가

Object obj = list.get(0); // 컬렉션에서 객체 검색
String name = (String) obj; // 타입 변환 후 홍길동을 얻을 수 있음.
//자바 5 이후
List<String> list = new ArrayList<String>(); // 컬렉션 생성
list.add("홍길동"); // 컬렉션 추가
String name = list.get(0); // 컬렉션 객체 검색, 홍길동을 바로 얻음

 

ArrayList에 객체를 추가하면 인덱스 0 부터 차례대로 저장된다.

ArrayList 에서 특정 인덱스의 객체를 제거하면 바로 뒤 인덱스부터 마지막 인덱스 까지 모두 앞으로 1씩 당겨진다. 

마찬가지로 특정 인덱스에 객체를 삽입하면 해댕 인덱스부터 마지막 인덱스까지 모두 1씩 밀려난다.

다음은 4번 인덱스가 제거되었을 때 5번 인덱스부터 모두 앞으로 1씩 당겨지는 모습을 보여준다. 

따라서 빈번한 객체 삭제와 삽입이 일어나는 곳에서는 ArrayList 를 사용하는 것이 바람직하지 않다.

이런 경우라면 LinkedList를 사용하는 것이 좋다.

그러나 인덱스 검색이나, 맨 마지막에 객체를 추가하는 경우에는 ArrayList 가 더 좋은 성능 발휘한다.

 

다음 예제는 ArrayList에 String 객체를 추가, 검색, 삭제하는 방법을 보여준다 .

 

package CollectionFrameWork;

import java.util.ArrayList;
import java.util.List;

public class ArrayListExmaple {

    public static void main(String[] args) {
        List<String> list = new ArrayList<>();

        list.add("A");
        list.add("B");
        list.add("C");
        list.add("D");
        list.add("E");
        list.add("F");

        int size = list.size();
        System.out.println("Size of list is : " + size);
        System.out.println();

        String skip = list.get(2);
        System.out.println("Element at index 2 is : " + skip);
        System.out.println();

        for (int i = 0; i < list.size(); i++) {
            String str = list.get(i);
            System.out.println("Element at index " + i + " is : " + str);
        }
        System.out.println();

        list.remove(2);
        System.out.println("After removing element at index 2 : ");
        for (int i = 0; i < list.size(); i++) {
            String str = list.get(i);
        }
        list.remove("A");
        System.out.println("After removing element A : ");
        for (int i = 0; i < list.size(); i++) {
            String str = list.get(i);
        }
    }
}

 

Vector


Vector는 ArrayList 와 동일한 내부 구조를 가지고 있다. Vector를 생성하기 위해서는 저장할 객체 타입을 타입 파라미터로 표기하고 기본 생성자를 호출하면 된다.

List<E> list = new Vector<E>();

 

ArrayList와 다른 점은 Vector는 동기화된(synchronized) 메소드로 구성되어 있기 때문에 멀티 스레드가 동시에 이 메소드들을 실행할 수 없고,

하나의 스레드가 실행을 완료해야만 다른 스레드를 실행할 수 있다.

그래서 멀티 스레드 환경에서 안전하게 객체를 추가, 삭제 할 수 있다. 이것을 스레드가 안전(Thread Safe)하다라고 말한다. 

 

다음은 Vector를 이용해서 Board 객체를 추가, 삭제, 검색하는 예제이다. 

package CollectionFrameWork;

import java.util.Vector;

public class VectorExample {

    public static void main(String[] args) {

        Vector<Board> ve = new Vector<>();

        ve.add(new Board("r", "A", "B"));
        ve.add(new Board("d", "B", "C"));
        ve.add(new Board("s", "C", "D"));
        ve.add(new Board("f3", "D", "E"));

        ve.remove(2);
        ve.remove(1);

        for (Board b : ve) {
            System.out.println(b.getContent() + " " + b.getSubject() + " " + b.getWriter());
        }
    }
}

 

LinkedList


LinkendList는 List 구현 클래스이므로 ArrayList와 사용 방법은 똑같지만 내부 구조는 완전 다르다.

ArrayList 는 내부 배열에 객체를 저장해서 인덱스로 관리하지만, LinkedList는 인접 참조를 링크해서 체인처럼 관리한다.

LinkedList 에서 특정 인덱스의 객체를 제거하면 앞 뒤 링크만 변경되고 나머지 링크는 변경되지 않는다.

특정 인덱스에 객체를 삽입 할 때에도 마찬가지다.

ArrayList는 중간 인덱스의 객체를 제거하면 뒤의 객체는 인덱스가 1씩 앞으로 당겨진다고 했다.

그렇기 때문에 빈번한 객체삭제와 삽입이 일어나는 곳에서는 ArrayList 보다 LinkedList 가 좋은 성능을 발휘한다.

 

다음은 중간에 객체를 제거할 경우 앞뒤 링크의 수정이 일어나는 모습을 보여주고 있다.

 

LinkedList를 생성하기 위해서는 저장할 객체 타입을 타입 파라미터 (E)에 표기하고 기본 생성자를 호출하면 된다.

LinkedList가 처음 생설될때에는 어떠한 링크도 만들어 지지 않기 때문에 내부는 비어있다고 보면 된다.

 

다음 예제는 ArrayList와 LinkedList 에 객체를 삽입하는 걸린 시간을 측정한다.

실행 결과를 보면 LinkedList가 훨씬 빠른 성능을 낸다. 

 

package CollectionFrameWork;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

public class LikedListExample {
    public static void main(String[] args) {
        List list = new LinkedList();
        List arrayList = new ArrayList();

        long startTime = System.currentTimeMillis();


        for (int i = 0; i < 1000000; i++) {
            list.add(i);
        }

        long endTime = System.currentTimeMillis();
        System.out.println(startTime - endTime);
        startTime = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            arrayList.add(i);
        }
        long endTime1 = System.currentTimeMillis();
        System.out.println(endTime1 - endTime);
    }
}

 

끝에서 부터 (순차적으로) 추가/삭제 하는 것은 ArrayList 가 빠르지만,

중간에 추가 또는 삭제할 경우 앞뒤 링크 정보만 변경하면 되는 LinkedList가 더 빠르다.

ArrayList 는 뒤쪽 인덱스들을 모두 1씩 증가 또는 감소시키는 시간이 필요하므로 처리 속도가 느리다.

구분 순차적으로 추가/삭제 중간에 추가/삭제 검색
ArrayList 빠르다 느리다 빠르다
LinkedList 느리다 빠르다 느리다

'Back-end > 이것이 자바다[신용권 한빛미디어]' 카테고리의 다른 글

Map 컬렉션  (0) 2022.05.15
Set 컬렉션  (0) 2022.05.01
컬렉션 프레임 워크  (0) 2022.04.24
람다식 표준 API의 함수적 인터페이스, 메소드 참조  (0) 2022.04.10
람다식  (0) 2022.04.06