Computer >> 컴퓨터 >  >> 프로그래밍 >> Java

Java ArrayList 출력 완벽 가이드: for 루프부터 toString()까지 3가지 방법

ArrayList는 Java 개발에서 꾸준히 주목받는 주제입니다. 일반 배열보다 훨씬 많은 장점을 제공하는데요. ArrayList를 사용하면 객체를 순서대로 효율적으로 저장하고, 필요 없어진 데이터는 손쉽게 제거할 수 있습니다. 정렬이나 검색 같은 리스트의 표준 연산도 간편하게 수행할 수 있죠. 만약 일반 배열을 사용한다면 이러한 연산 로직을 모두 직접 작성해야 하기 때문에 추가적인 시간과 주의가 필요합니다.

이처럼 다양한 장점 덕분에 ArrayList는 폭넓은 용도로 활용됩니다. 하지만 ArrayList에 기본으로 내장되어 있지 않은 중요한 기능이 하나 있는데, 바로 출력(printing)입니다. ArrayList에는 정수나 문자열 같은 기본(primitive) 데이터 타입이 들어갈 수도 있고, 커스텀 클래스로 생성한 객체 같은 고급 데이터 타입이 들어갈 수도 있습니다. 그리고 리스트의 구성에 따라 출력 방법도 달라집니다.

이 글에서는 두 가지 유형의 ArrayList를 모두 다루며, Java에서 ArrayList를 출력하는 대표적인 세 가지 방법을 살펴보겠습니다.

Java ArrayList 출력: 세 가지 방법

ArrayList에는 크게 두 가지 유형이 있습니다. 하나는 정수나 실수처럼 단순한 기본 데이터 타입의 객체를 담는 리스트이고, 다른 하나는 커스텀 클래스로 인스턴스화된 객체를 담는 리스트입니다. 이 두 유형을 출력하는 방법은 여러 가지가 있으며, 그중 대표적인 세 가지는 다음과 같습니다.

  • for 루프 사용
  • println 명령어 사용
  • toString() 구현 활용

방법 1: for 루프 사용하기

for 루프는 리스트나 배열을 순회하는 가장 간단한 방법 중 하나입니다. 모든 요소를 하나씩 차례대로 접근하면서 각각을 개별적으로 처리할 수 있기 때문에, 단순 배열과 ArrayList 모두에서 잘 동작합니다.

특히 이 방법은 각 요소에 대해 조건별로 다른 처리를 정의할 수 있다는 장점이 있습니다. for 루프의 기본 문법은 다음과 같습니다.

for (int i = 0; i < arr.size(); i++) {
    // arr이 ArrayList 객체라고 가정
    int current = arr.get(i);
    // 여기서 현재 요소로 원하는 작업 수행
}

그럼 for 루프를 활용해 객체 리스트를 순회하는 실제 예시를 살펴보겠습니다.

for 루프 예제

가장 기본적인 형태로, ArrayList의 모든 요소를 그대로 출력할 수 있습니다.

ArrayList<Integer> arr = new ArrayList<>();
arr.add(3);
arr.add(4);
arr.add(5);
arr.add(6);

for (int i = 0; i < arr.size(); i++) {
    int curr = arr.get(i);
    System.out.println(curr);
}

한 단계 더 나아가, instanceof 연산자를 활용하면 요소의 타입에 따라 서로 다른 처리를 정의할 수도 있습니다.

ArrayList arr = new ArrayList<>();
arr.add(3);
arr.add(5l);
arr.add("Hello World!");

for (int i = 0; i < arr.size(); i++) {
    if (arr.get(i) instanceof Integer)
        System.out.println(arr.get(i) + " is a number");
    else if (arr.get(i) instanceof String)
        System.out.println(arr.get(i) + " is a string");
    else if (arr.get(i) instanceof Long)
        System.out.println(arr.get(i) + " is a long");
}

이 방법이 적합한 경우

for 루프는 요소 수가 비교적 적고, 타입이 크게 다양하지 않은 상황에 가장 적합합니다. 예를 들어 정수(int)와 문자열로만 구성된 20개 정도의 항목이 있다면 for 루프가 좋은 선택입니다. 반면 문자열, 정수, 불리언 등 여러 타입이 뒤섞인 100개 이상의 목록을 다룰 때는 for 루프가 지나치게 번거로워질 수 있습니다.

방법 2: println 명령어 사용하기

println 명령어는 기본(primitive) 데이터 타입으로 구성된 ArrayList를 직접 출력할 때 사용할 수 있습니다. Java의 기본 데이터 타입에는 String, Integer, Float, Long, Double, Boolean이 해당됩니다. 만약 목록이 이러한 타입에 맞지 않는 데이터로 구성되어 있다면 이 방법으로는 출력할 수 없습니다.

문법은 아주 간단합니다.

System.out.println(arr);
// arr이 ArrayList 객체라고 가정

이 방법이 동작하는 이유는 모든 기본 데이터 타입이 표준 출력 장치로 곧바로 출력될 수 있기 때문입니다. 별도의 변환 과정 없이도 출력 가능한 형태로 해석되는 것이죠. 반면 커스텀 클래스의 인스턴스에 저장된 데이터는 println 명령어가 직접 해석할 수 없기 때문에, 이런 경우에는 이 방법이 동작하지 않습니다.

println 예제

단순한 정수 ArrayList를 출력하는 코드는 다음과 같습니다.

ArrayList<Integer> arr = new ArrayList<>();
arr.add(5);
arr.add(4);
arr.add(6);
System.out.println(arr);
// => [5, 4, 6]

타입이 섞인(heterogeneous) ArrayList 역시 문제없이 출력됩니다.

ArrayList<Object> arr = new ArrayList<>();
arr.add(7);
arr.add("Foo");
arr.add(12l);
System.out.println(arr);
// => [7, Foo, 12]

이 방법이 적합한 경우

이 방법은 짧고 단순한 목록을 디버깅 목적으로 빠르게 확인할 때 가장 유용합니다. 다만 첫 번째 방법만큼 유연하지 않아, 출력 내용을 원하는 형식으로 가공할 수 없다는 점은 감안해야 합니다.

또 한 가지 주의할 점은, 목록에 기본 타입이 아닌(non-primitive) 데이터가 포함되어 있으면 값 자체가 출력되지 않고 메모리상의 위치를 가리키는 참조값이 출력된다는 것입니다.

class Foo {
    int bar;
    public Foo(int bar){ this.bar = bar; };
}

ArrayList<Object> arr = new ArrayList<>();
arr.add(4);
arr.add(new Foo(40));
System.out.println(arr);
// => [4, Foo@2a139a55]

방법 3: toString() 메서드 구현하기

이 방법은 앞선 방법의 한계를 보완합니다. 기본 타입이 아닌 객체를 직접 출력할 수 없는 근본적인 이유는, 컴파일러가 해당 객체를 어떤 형태로 출력해야 할지 알지 못하기 때문입니다. toString() 메서드를 오버라이드하면 클래스의 객체를 콘솔에 출력하는 방식을 개발자가 직접 정의해 줄 수 있습니다.

앞선 예제의 Foo 클래스를 다시 살펴보겠습니다.

class Foo {
    int bar;
    public Foo(int bar){ this.bar = bar; };
}

이 클래스를 출력 친화적으로 만들려면 클래스 정의 안에 toString() 메서드를 오버라이드해서 정의해야 합니다.

class Foo {
    int bar;
    public Foo(int bar){ this.bar = bar; };
    public String toString() {
        return "Bar: " + String.valueOf(bar);
    }
}

이제 Foo의 인스턴스를 생성하고 출력하면, 미리 정의해 둔 형식 그대로 결과가 나옵니다. 이 간단한 수정만으로 println과 함께 어떤 종류의 ArrayList든 쉽고 빠르게 출력할 수 있습니다.

toString() 예제

방법 2의 마지막 예제를 println으로 출력 가능한 형태로 바꿔 보겠습니다. 먼저 toString()이 정의되어 있지 않은 상태의 출력 결과는 다음과 같습니다.

class Foo {
    int bar;
    public Foo(int bar){ this.bar = bar; };
}

ArrayList<Object> arr = new ArrayList<>();
arr.add(4);
arr.add(new Foo(40));
System.out.println(arr);
// => [4, Foo@2a139a55]

여기서 필요한 변경은 단 하나, Foo 클래스에 toString() 정의를 추가하는 것입니다.

class Foo {
    int bar;
    public Foo(int bar){ this.bar = bar; };
    public String toString(){
        return "Value: " + String.valueOf(bar);
    }
}

// 이제 println은 의도한 출력을 반환합니다:
// => [4, Value: 40]

이 방법이 적합한 경우

이 방법은 타입이 섞인 ArrayList를 출력하거나, 커스텀 클래스로 구성된 리스트를 다뤄야 할 때 적합합니다. 클래스에 메서드 정의를 단 하나만 추가하면 for 루프를 일일이 작성할 때의 번거로움을 피할 수 있습니다.

다만 상황이 단순할 때는 오히려 과한 방법일 수 있습니다. 소규모 목록이라면 논리가 단순한 for 루프 방식을 선택하는 편이 나을 수도 있습니다.

결론

Java ArrayList를 출력하는 일은 생각보다 단순하지 않습니다. ArrayList의 내용이 동적일수록 표준 출력으로 목록을 깔끔하게 출력하기가 어려워집니다. println 명령어만으로 대부분의 경우를 처리할 수 있지만, 상황에 따라서는 toString() 오버라이드가 반드시 필요할 수 있습니다.

이 글에서는 ArrayList를 출력하는 세 가지 방법을 살펴보았습니다. 각 방법의 문법과 예제, 그리고 가장 적합한 사용 시나리오까지 함께 확인했는데요. 이를 바탕으로 자신의 상황에 맞는 최적의 방법을 선택해 보시기 바랍니다. 더 심화된 Java 학습 자료가 필요하다면 Java 학습 가이드도 함께 참고해 보세요.