자바 ArrayList size - jaba ArrayList size

이번 포스팅은 Java에서 ArrayList가 비어 있는지 확인하는 방법을 소개합니다.


size() 메서드

ArrayList 클래스의 size() 메서드를 사용하여 ArrayList가 비어 있는지 확인할 수 있습니다. 크기가 0보다 크면 ArrayList에 요소가 존재한다는 의미이므로 비어 있지 않고 크기가 0이면 요소가 없으므로 비어 있음을 의미합니다.

다음 예제는 ArrayList의 size() 메서드 사용 방법을 보여줍니다.

public class Main {
  public static void main(String args[]) {
    ArrayList<Integer> al1 = new ArrayList<Integer>(
            Arrays.asList(1, 2, 3, 4, 5)
    );

    ArrayList<Integer> al2 = new ArrayList<Integer>();

    if(al1.size() == 0) {
        System.out.println("al1 object is Empty");
    } else {
        System.out.println("al1 object is Not-Empty");
    }

    if(al2.size() == 0) {
        System.out.println("al2 object is Empty");
    } else {
        System.out.println("al2 object is Not-Empty");
    }
  }
}

실행 결과

al1 object is Not-Empty
al2 object is Empty

isEmpty() 메서드

ArrayList 클래스의 isEmpty() 메서드는 ArrayList의 size() 메서드를 내부적으로 호출합니다. isEmpty() 메서드는 ArrayList에 요소가 없으면 true를 반환하고 그렇지 않으면 false를 반환합니다.

다음 예제는 ArrayList의 isEmpty() 메서드 사용 방법을 보여줍니다.

public class Main {
  public static void main(String args[]) {
    ArrayList<Integer> al1 = new ArrayList<Integer>(
            Arrays.asList(1, 2, 3, 4, 5)
    );

    ArrayList<Integer> al2 = new ArrayList<Integer>();

    if(al1.isEmpty()) {
        System.out.println("al1 object is Empty");
    } else {
        System.out.println("al1 object is Not-Empty");
    }

    if(al2.isEmpty()) {
        System.out.println("al2 object is Empty");
    } else {
        System.out.println("al2 object is Not-Empty");
    }
  }
}

실행 결과

al1 object is Not-Empty
al2 object is Empty

null로 초기화

비어 있는 ArrayList는 객체는 존재하지만, 요소가 없는 경우를 말합니다. 하지만, null로 초기화하면 힙 메모리에 ArrayList 객체가 없다는 것을 의미합니다.

위에서 소개한 size(), isEmpty() 메서드를 사용하여 null로 초기화된 ArrayList를 체크하면 NullPointerException 예외가 발생합니다.

public class Main {
  public static void main(String args[]) {
    // ArrayList 객체를 null로 초기화합니다.
    ArrayList<Integer> al = null;

    // NullPointerException 예외가 발생합니다.
    // isEmpty() 메서드도 동일한 예외가 발생합니다.
    if(al.size() == 0) {
        System.out.println("al object is Empty");
    } else {
        System.out.println("al object is Not-Empty");
    }
  }
}

따라서, ArrayList가 비어 있는지 확인해기 위해서는 null을 반드시 확인해야 합니다.

다음 예제는 null 체크와 ArrayList가 비어 있는지 확인하는 방법을 보여줍니다.

public class Main {
  public static void main(String args[]) {
    ArrayList<Integer> al1 = null;
    ArrayList<Integer> al2 = null;

    if(al1 == null || al1.size() == 0) {
        System.out.println("al1 object is Empty");
    } else {
        System.out.println("al1 object is Not-Empty");
    }

    if(al2 == null || al2.isEmpty()) {
        System.out.println("al2 object is Empty");
    } else {
        System.out.println("al2 object is Not-Empty");
    }
  }
}

실행 결과

al1 object is Empty
al2 object is Empty

정리

  • ArrayList가 비어 있는지 확인하는 방법으로 ArrayList 클래스의 size() 메서드와 isEmpty() 메서드를 사용합니다.
  • ArrayList를 null로 초기화할 수 있으므로 null인지 체크할 필요가 있습니다.

A work-loving developer

프로그래밍 언어/Java

Java - ArrayList가 가변적일 수 있는 이유 (+ Size 지정하는 이유)

Jae Honey 2022. 5. 7. 19:22

ArrayList

자바에서 배열은 크기가 고정된 데이터 구조이다. 반면에, ArrayList는 가변적인 크기를 가지고 있다.

일반적인 배열은 우리가 알고 있는 형태로 Heap에 저장된다.

자바 ArrayList size - jaba ArrayList size

ArrayList는 물리적으로 원소들이 연속되어야 한다.

만약 여기서 Heap에 새로운 Object가 들어왔다고 가정하자. 그러면 List에 새로운 원소를 생성할 수 없게 된다.

자바 ArrayList size - jaba ArrayList size

"ArrayList는 어떻게 가변적인 길이를 가질 수 있을까?"에 대해 알아보자.

add()

ArrayList 클래스의 add() 메서드를 살펴보자.

자바 ArrayList size - jaba ArrayList size

add()를 사용하면 내부적으로 오버로딩된 add 메서드를 호출한다.

메서드 내부를 살펴보면 흥미로운 사실을 알 수 있다.

  • ArrayList는 내부적으로 elementData라는 배열을 가지고 있다. 원소는 해당 배열에 저장된다.
  • ArrayList는 size라는 필드를 가지고 있다.
    • 원소의 개수가 size 필드에 저장되고 실제로 할당된 크기는 이와 다르다.
  • 요소의 개수가 size와 일치하면 grow() 메서드를 호출한다.

여기서 가변적인 사이즈의 비밀은 grow() 함수가 가지고 있다!

grow()

add()함수를 실행했을 때 내부 배열에 원소가 가득차면 grow() 메서드를 실행한다. grow는 직역하면 "자라다, 성장하다"의 뜻을 가진다.

자바 ArrayList size - jaba ArrayList size

grow()도 오버로딩된 grow() 메서드를 호출하게 된다.

그러면 Arrays.copyOf() 메서드를 사용해서 현재 있던 배열을 그대로 복사해서 새로운 배열을 만든다. 이 때 인자로 size+1을 넘기는데, 이는 size가 1만큼 증가된 배열을 추가한다는 뜻은 아니다.

Arrays.copyOf는 첫 번째 파라미터로 복사할 배열, 두 번째 파라미터로 복사할 배열의 길이를 받는다.

Arrays.copyOf(array, size);

그런데, size+1을 그대로 Arrays.copeOf의 두 번째 인자로 넣지 않고 newCapacity(size+1)이 두 번째 인자로 들어간다.

newCapacity()

newCapacity()의 인자로 size+1을 넘겨주는 이유는 배열 요소가 비어있거나, 너무 커서 Overflow가 발생할 때 Size를 처리해주기 위해서이다.

자바 ArrayList size - jaba ArrayList size

실제로는 현재 size + (size >> 1) 만큼의 사이즈를 반환한다. size를 우측 시프트 연산하면 절반이 남으므로 반환 값은 size의 1.5배가 된다.

정리하면 ArrayList는 내부적으로 배열을 가지고 있고, 배열 요소가 가득차면 1.5배 크기의 새로운 배열로 만들어서 복사한다.

참고

newCapacity() 메서드에서 봤듯이 배열에 요소를 삽입할 때 배열이 비어있으면 배열의 크기로 DEFAULT_CAPACITY 상수를 사용한다.

자바 ArrayList size - jaba ArrayList size

해당 상수의 값은 10이므로 기본 ArrayList의 기본생성자를 사용해서 생성한 후 새로운 요소를 하나 삽입하면 size가 10이 된다.

그 후 1.5배씩 확장하면서 배열을 복사하게 된다.

배열을 복사할 때 자원이 많이 사용되므로 ArrayList의 Size가 예상가능하다면 Size를 명시해서 생성하는 것이 바람직하다.

자바 ArrayList size - jaba ArrayList size

ArrayList에 1000만개의 데이터를 삽입할 때 기본 생성자로 ArrayList로 생성할 때랑 미리 Size를 1000만으로 명시해서 생성했을 때랑 처리 시간이 5배 가량 차이가 난다고 한다.

정리

그래서 어떻게 연속된 데이터를 저장해나가면서 확장할 수 있냐는 질문에 대한 대답을 정리하면 아래와 같다.

ArrayList는 미리 특정한 크기를 점유한 후 요소를 하나씩 추가한다. 그리고 크기가 부족할 때마다 힙에 더 큰 공간을 점유하는 새로운 List를 생성한다. 그 후, 기존의 ArrayList의 데이터를 복사한 후 소멸한다.


Reference

  • https://junghyungil.tistory.com/96
  • https://kjhoon0330.tistory.com/m/entry/Java-ArrayList%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%ED%81%AC%EA%B8%B0%EA%B0%80-%EA%B0%80%EB%B3%80%EC%A0%81%EC%9D%BC%EA%B9%8C