본문 바로가기
Backend/Java

Java 기초 문법 : 컬렉션(collection) - List (1)

by 코딩쥐 2024. 10. 10.

OpenJDK 21 / Collection — DevDocs

 

DevDocs

 

devdocs.io

 

Java에서 컬렉션이란 데이터의 집합을 의미한다. Collection 인터페이스는 모든 컬렉션의 최상위 인터페이스로 List, Set, Queue 등의 공통 기능을 정의한다. Map의 경우 구조상의 차이로 별도의 인터페이스로 정의되지만 Collection으로 분류된다.  모든 Collection은 저장될 객체의 타입을 지정할 수 있는 제네릭(Generic)타입을 지원한다.

 

  • Set : 순서가 없는 데이터의 집합으로 데이터의 중복을 허용하지 않는다.
    • 구현클래스 : HashSet, TreeSet
  • List : 순서가 있으며(인덱스 존재) 데이터의 중복을 허용한다. 
    • 구현클래스 : LinkedList, Vector, ArrayList
  • Queue : 순서가 있으며 요소는 큐의 앞쪽에서 제거되고, 새로운 요소는 뒤쪽에 추가된다.
    • 구현클래스 : LinkedList, PriorityQueue
  • Map : 키-값 쌍으로 데이터를 저장하며, 키는 중복을 허용하지 않는다. 키를 통해 매핑된 값을 가져올 수 있다.
    • 구현클래스 : HashMap, TreeMap

 

List

List 인터페이스는 순서가 있으며 데이터의 중복을 허용하는 데이터의 집합이다. 데이터의 삭제 및 추가를 할 때 데이터에 대한 전체적인 길이가 유동적으로 변경된다. JAVA의 배열의 경우 동적인 배열의 움직임이 불가능하지만, List를 사용하게 되면 유동적인 배열을 만들 수 있다는 장점이 있다. 

 

List를 사용하기 위해서는 List를 상속하고 있는 클래스의 인스턴스를 생성하여 사용해야한다. 가장 많이 사용되는 클래스의 경우 ArrayList, LinkedList, Stack 등이 있다.

 

List 메서드

메서드 설명
.add(); 배열에 요소를 추가한다.
.size() 배열의 길이를 구한다.
.get(인덱스) 배열의 해당 인덱스에 접근한다.
.remove(인덱스) 배열의 해당 인덱스를 삭제한다. 
.add(인덱스, 요소) 해당하는 인덱스에 요소를 삽입한다.
.set(인덱스, 요소) 해당하는 인덱스의 요소를 해당 요소로 변경한다. 

 

ArrayList

데이터 읽기 속도가 빠르며, 순회할 때 성능이 좋다. 크기가 정해져 있는 데이터를 다룰 때 효과적이다. 그러나 크기를 변경해야 하는 경우에는 크기를 변경할 때 새로운 배열을 생성하고 데이터를 복사해야 하므로 메모리 낭비가 발생할 수 있어 자주 변경되는 데이터에 대해서는 ArrayList 사용을 고려해봐야한다.

* ArrayList의 내부 구조는 배열을 기반으로 하고, 초기 메모리 공간 크기가 벗어났을 경우 다시 객체를 저장할 만한 heap을 할당받고 그 공간으로 옮겨담는 식으로 메모리 사용이 이루어 진다.

  • List<타입> 변수명 = new ArrayList<>();

 

ArrayList 사용하기

package com.example;

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

public class Example01 {
    public static void main(String[] args) {
    // 제네릭타입이기 때문에 배열에 들어갈 요소들의 타입을 설정
        List<Integer> list = new ArrayList<>();
        // 배열에 요소 추가
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
        System.out.println(list); // [1,2,3,4,5]

        // 배열의 길이 확인
        System.out.println(list.size()); // 5

        // 인덱스 3의 요소 접근 (인덱스는 0부터 시작)
        System.out.println(list.get(3)); // 4

        // 인덱스 3 삭제
        list.remove(3);
        System.out.println(list); // [1,2,3,5]
        System.out.println(list.size()); // 4 - 배열의 길이도 변경됨

        // 인덱스 2에 요소 10 추가
        list.add(2, 10);
        System.out.println(list); // [1,2,10,3,5]

        // 인덱스 4를 7로 변경
        list.set(4, 7);
        System.out.println(list); // [1,2,10,3,7]
    }
}

 

LinkedList

ArrayList의 경우 내부적으로 배열을 사용하여 데이터를 저장하고, LinkedList의 경우에는 각 요소가 노드로 구성되어 해당 노드에는 다음 노드에 대한 참조주소를 가지고 있다. 즉, LinkedList는 물리적으로 메모리에 연속적으로 이어져 있지 않고, 노드 간의 링크를 통해 연결된다. ArrayList와는 달리 요소 추가 혹은 삭제를 할 때 참조 주소만 변경하면 되기 때문에 내부 요소의 변경에 유리하다. 

  • List<타입> 변수명 = new LinkedList<>();

사용하는 방법은 위의 ArrayList와 동일하다. List의 메서드를 통해 다룰 수 있다. 


List의 정렬 (sort)

이전에는 정렬에 관한 기능을 Collection 인터페이스에서 제공했으나, JDK 8 버전 이후 List 에서 자체적으로 정렬에 대한 기능을 제공한다. Comparable 인터페이스 또는 Comparator 인터페이스를 사용하여 객체의 정렬 기준을 정의할 수 있으며, 아래의 메서드를 통해 정렬이 가능하다.

  • list.sort(Comparator.naturalOrder());
    Comparable / Comparator 를 사용하여 정의한 정렬기준 기반으로 정렬

  • list.sort(Comparator.reverseOrder());
    Comparable / Comparator 를 사용하여 정의한 정렬 기준을 반대로(내림차순) 정렬

  • list.sort(String.CASE_INSENSITIVE.ORDER);
    대소문자를 구분하지 않고 정렬

  • list.sort(Collections.reverseOrder(String.CASE_INSENSITIVE.ORDER));
    대소문자를 구분하지 않고 반대로(내림차순) 정렬

 

Comparable 

별도의 클래스 없이 현재 인터페이스를 상속 받아서 구현하는 정렬 방식이다. List 내의 각 인스턴스 내에서 비교 기능을 호출해서 사용한다. 리턴 값이 음수, 0, 양수이며 리턴 값에 따라 정렬 위치가 달라진다. 리턴 값이 양수면 현재 객체가 비교 대상보다 나중에 위치하게 된다.

  • int compareTo(클래스 변수명)
package com.example;

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

//Comparable 인터페이스를 상속받는다.
public class Example01 implements Comparable<Example01>{
    //멤버 변수 생성
    private String name;
    private int num;

    // 생성자 생성
    public Example01(String name, int num) {
        this.name = name;
        this.num = num;
    }

    // compareTo 메서드를 오버라이드
    @Override
    public int compareTo(Example01 person) {
        // 해당 인스턴스의 num의 숫자가 작으면 1을 반환
        if(person.num < num){
            return 1; // 현재 객체가 더 크면 나중에 정렬
        } else if (person.num > num) {
            return -1; // 현재 객체가 더 작으면 먼저 정렬
        }
        return 0;  // 같으면 순서 유지
    }

    @Override
    public String toString() {
        return "Example01{" +
                "name='" + name + '\'' +
                ", num=" + num +
                '}';
    }

    public static void main(String[] args) {
        // 배열 생성
        List<Example01> list = Arrays.asList(
                new Example01("아무개", 2),
                new Example01("김콩쥐", 1),
                new Example01("서호랑", 5)
        );

        // compareTo에서 설정한 정렬 사용
        list.sort(Comparator.naturalOrder());
        System.out.println(list); 
        // [Example01{name='김콩쥐', num=1}, Example01{name='아무개', num=2}, Example01{name='서호랑', num=5}]

        // compareTo에서 설정한 정렬 반대로 사용
        list.sort(Comparator.reverseOrder());
        System.out.println(list); 
        //[Example01{name='서호랑', num=5}, Example01{name='아무개', num=2}, Example01{name='김콩쥐', num=1}]
    }
}

 

Comparator 

Comperator는 두 객체를 비교하는 데 사용되는 인터페이스로, 해당 인터페이스를 구현한 클래스를 만들어 사용자 정의 정렬 기준을 설정한다. 여러 가지 비교 기준을 동시에 적용할 수 있다. 

  • int compare(클래스 변수1, 클래스 변수2)

 

Name으로 비교하는 클래스 생성 (Comparator 인터페이스 구현)

package com.example;

import java.util.Comparator;

public class NameComparator implements Comparator<Example01> {

    @Override
    public int compare(Example01 o1, Example01 o2) {
        return o1.getName().compareTo(o2.getName());
    }
}

 

Num으로 비교하는 클래스 생성 (Comparator 인터페이스 구현)

package com.example;

import java.util.Comparator;

public class NumComparator implements Comparator<Example01> {

    @Override
    public int compare(Example01 o1, Example01 o2) {
        return o1.getNum().compareTo(o2.getNum());
    }
}

 

해당하는 기준을 사용하여 정렬

package com.example;

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

//Comparable 인터페이스를 상속받는다.
public class Example01{
    //멤버 변수 생성
    private String name;
    //Comparable 인터페이스가 compareTo 메서드를 정의하기 때문에, 객체 타입만 가능하다 (int -> Integer)
    private Integer num;

    // 생성자 생성
    public Example01(String name, Integer num) {
        this.name = name;
        this.num = num;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getNum() {
        return num;
    }

    public void setNum(Integer num) {
        this.num = num;
    }

    @Override
    public String toString() {
        return "Example01{" +
                "name='" + name + '\'' +
                ", num=" + num +
                '}';
    }

    public static void main(String[] args) {
        // 배열 생성
        List<Example01> list = Arrays.asList(
                new Example01("아무개", 2),
                new Example01("김콩쥐", 1),
                new Example01("서호랑", 5)
        );

        //NameComparator을 통한 정렬
        list.sort(new NameComparator());
        System.out.println(list);
        //[Example01{name='김콩쥐', num=1}, Example01{name='서호랑', num=5}, Example01{name='아무개', num=2}]

        //NameComparator 반대로 정렬
        list.sort(new NameComparator().reversed());
        System.out.println(list);
        // [Example01{name='아무개', num=2}, Example01{name='서호랑', num=5}, Example01{name='김콩쥐', num=1}]

        //NumComparator을 통한 정렬
        list.sort(new NumComparator());
        System.out.println(list);
        //[Example01{name='김콩쥐', num=1}, Example01{name='아무개', num=2}, Example01{name='서호랑', num=5}]

        //NumComparator 반대로 정렬
        list.sort(new NumComparator().reversed());
        System.out.println(list);
        //[Example01{name='서호랑', num=5}, Example01{name='아무개', num=2}, Example01{name='김콩쥐', num=1}]
    }
}