컬렉션

컨테이너·2025년 11월 2일

JAVA

목록 보기
5/9
post-thumbnail

컬렉션 프레임워크

자바에서 컬렉션 프레임워크는 여러 개의 다양한 데이터들을 쉽고 효과적으로 처리할 수 있도록 표준화된 방법을 제공하는 클래스들의 집합을 의미한다. 즉, 데이터는 효율적으로 저장하는 자료구조와 데이터를 처리하는 알고리즘을 미리 구현해 놓은 클래스를 말한다.

ArrayList 예제

ArrayList list = new ArrayList();    // ArrayList를 ArrayList로 가져옴
List list = new ArrayList();         //ArrayList 형식을 [List 타입]으로 가져온 것
// ↳ List (상위)타입에서 가져오므로, ArrayList 에서 LinkedList로 변환될 수 있다.
Collection clist = new ArrayList();  // 다형성 -> 클래스 바꿀 때 용이함

ArrayList는 최상위 클래스 collection 인터페이스로부터 List 를 통해 메소드들을 상속받아 온다.상위에서 상속받아 온 메소드들을 각각의 구현 클래스에서 오버라이딩하여 사용한다.

↳ 다형성을 이용해서 상위 레퍼런스로 ArrayList객체를 참조할 수 있다. List 인터페이스 하위의 다양한 구현체들로 타입변경이 가능하므로 보다 유연한 코드를 작성할 수 있다.

위 코드를 하나씩 뜯어보자

ArrayList list = new ArrayList();

ArrayList 클래스의 객체를 기본생성자를 통해 list로 가져온다. 가장 기본적인 클래스 사용이다.

다음은 아래와 같다.

List list = new ArrayList();

List 라는 상위 인터페이스의 타입에 ArrayList() 를 담아 다형성을 적용시킨다. List 타입의 list를 생성했기 때문에, list는 ArrayList에서 LinkedList로 타입 변환을 시킬 수 있다.

List list = new ArrayList();
list = new LinkedList(); -> 가능!

Arraylist 메소드

list.size();           => 요소의 갯수 반환
list.add();          => 제일 뒤에 값 삽입
list.add(인덱스,);  => 해당 인덱스에 값 삽입
list.get(인덱스);      => 값 반환
list.remove(인덱스);   => 인덱스에 있는 값 삭제
list.set(인덱스,);  => 인덱스에 있는 값 수정

Generics 없는 ArrayList 예제

아래 코드는 위 ArrayList list = new ArrayList(); 의 제네릭없는 코드의 예시이다.

alist.add("hello");
System.out.println(alist);
alist.add(1);
System.out.println(alist);

[hello]
[hello, 1]

위 내용은 제네릭이 없으므로 객체 타입 구분없이 들어온다. 배열과 동일한 기능이므로 이 방법은 ArrayList로 사용하지 않는다.

Generics 있는 ArrayList 예제

String 타입의 Element 로 ArrayList 객체 생성

List<String> stringList = new ArrayList();

이제 해당 List에는 반드시 String 타입의 값만 들어올 수 있다. 이는 컴파일 시점에 체크해주는 요소이다.

Collections 메소드 팁

Collections.sort(stringList); //매개변수로 List<T> 를 받아온다.
System.out.println("stringList = " + stringList);

Collections 의 sort에는 ComparableComparator 인터페이스를 가지고 오는 부분이 있다. 해당 Collection.sort() 메소드는 List<T> 를 매개변수로 받아온다. List 를 정렬해주는 메소드로 아래와 같은 결과가 나온다.

stringList = [apple, orange, banana, mango, grape]
=> (기본) 오름차순 정렬
stringList = [apple, banana, grape, mango, orange]

내림차순으로 만드는 방법.

Iterator

Iterator란?
Collection 인터페이스의 iterator() 메소드를 이용해서 인스턴스를 생성할 수 있다. 컬렉션에서 값을 읽어오는 방식을 통일된 방식으로 제공하기 위해 사용한다. 반복자라고 불리우며, 반복문을 이용해서 목록을 하나씩 꺼내는 방식으로 사용하기 위함이다.

인덱스로 관리되는 컬렉션이 아닌 경우에는 반복문을 사용해서 요소에 하나씩 접근할 수 없기 때문에 인덱스를 사용하지 않고도 반복문을 사용하기 위한 목록을 만들어주는 역할이라고 보면된다.

  • hasNext() : 다음 요소를 가지고 있는 경우 true, 더이상 요소가 없는 경우 false를 반환
  • next(): 다음 요소를 반환

Comparator 에서 compare 오버라이딩(정렬에 필요한 객체)

클래스를 만들고 해당 클래스 타입의 객체를 만들어서 sort, reverseSort하고 싶다면, 해주어야 하는 작업이 있다. Comparator 인터페이스를 구현하는 새로운 정렬 클래스를 만들어주는 것이다.

인터페이스에서 구현을 요구하는 메소드가 있는데, 그 중, compare 라고 하는 메소드를 오버라이딩 한다.

  • 오버라이딩 내용 - 정렬 클래스 타입 만들기 비교 대상 두 인스턴스의 price가 오름차순 정렬이 되기 위해서는 o1 의 가격이 더 작은 가격이어야 한다. 만약 o2의 가격이 더 작으면 두 인스턴스의 순서를 변경해야 한다. 두 인스턴스를 교환하라는 신호로 양수를 반환하면 정렬 시 순서를 변경하는 조건으로 사용한다.

BookDto 클래스 → ArrayList 에 객체 추가하여 정렬하기

클래스를 ArrayList에 넣어 정렬하는 예시이다.

public class AscendingPrice implements Comparator<BookDto> {
    // 가격오름차순
    @Override
    public int compare(BookDto o1, BookDto o2) {
        int result = 0;
        if(o1.getPrice() > o2.getPrice()) {
            result = 1; //o1 기준 양수이니 자리를 바꾼다.
        } else if (o1.getPrice() < o2.getPrice()) {
            result = -1; //자리를 안바꾼다.
        } else {// 같으면
            result = 0;
        }
        return result;
    }
    // Comparator 혹은
    // Comparable 기능을 붙이는 것.
}

이렇게 AscendingPrice, 즉 가격순으로 오름차순 정렬 클래스를 만들어주면, 아래와 같이 책 정보를 정렬시킬 수 있다.

bookList.sort(new AscendingPrice());//가격 오름차순 정렬
System.out.println("가격 오름차순 정렬");
for  (BookDto book : bookList) {
    System.out.println(book);
}    

결과
가격 오름차순 정렬
[number=5, title='홍길동전', author='일연', price=1000]

[number=2, title='목민심서', author='정약욕', price=30000]

[number=1, title='홍길동전', author='허균', price=50000]

[number=3, title='동의보감', author='허준', price=54000]

[number=4, title='삼국사기', author='김부식', price=62000]

익명 클래스

익명 클래스는 딱 한 번 사용하고 말 클래스이다. 내림차순 정렬을 다시 구현해보자.

// 익명 클래스 ( 한번 사용하고 말 것임 ) > 내림차순정렬
bookList.sort(new Comparator<BookDto>() { //클래스와 같은 개념임.
    @Override
    public int compare(BookDto o1, BookDto o2) {
        return o1.getPrice() >= o2.getPrice()? -1 : 1;
    }
});

System.out.println("가격내림차순 정렬 ====");
for  (BookDto book : bookList) {
    System.out.println(book);
}

이를 lambda 형식으로 작성할 수도 있다.

bookList.sort(new C

LinkedList

LinkedList 는 ArrayList와 사용 방식이 거의 유사하다. 다만, 구현과 작동 방식이 달라서 자료 변경이 많은 환경에서는 ArrayList 보다 훨씬 유리하다. 사용 방법은 ArrayList와 동일하다.

LinkedList에만 제공하는 메소드

LinkedList에는 ArrayList와 내부적인 구현 방식이 다르므로 수정, 삭제가 빈번한 상황에서는 LinkedList 를 사용하는 것이 효율적이다. ~first, ~last 접미어를 붙여 순차적 기능을 강조한 함수들이 LinkedList에는 추가적으로 정의되어 있다.

LinkedList<String> linkedList = new LinkedList<>();

linkedList.addFirst();    => 제일 앞에 값넣음
linkedList.removeFirst();   => 제일 앞 값 삭제
linkedList.removeLast();    => 제일 마지막 값 삭제
linkedList.clear();         => 갖고 있는 모든 요소 삭제
linkedList.isEmpty();       => 비어있는 여부 판별

Stack이란

후입선출(LIFO)의 자료구조로 push() , pop() , peek() 등의 메소드를 활용하여 자료처리한다.

Stack<Integer> stack = new Stack<Integer>();

Stack가 상속하는 클래스

public class Stack<E> extends Vector<E> {

Vector를 상속받으며, Vector는 인터페이스 List를 구현하는 클래스이다.

Stack 명령어

stack.push(자료형); //stack에 값 삽입

System.out.println("stack = " + stack);// toString() 오버라이딩됨

stack.peek(); //값 위치 확인

stack.search(3); //값 찾기

stack.pop(); // stack pop

Stack에서 더 이상 값이 없는데 pop()할 경우

EmptyStackException 예외가 발생한다.

Queue

Queue 란 사전적 의미로 ‘줄’ 을 의미한다. 스택과는 다르게 FIFO(선입선출) 로 구현되어 있다.

Queue 구현

Queue는 인터페이스이고, 주로 LinkedList() 구현 클래스로 선언한다. LinkedList로 구현하는 queue는 deque 에 해당되어 offerFirst() , offerList() 같은 메소드도 제공한다.

Queue 명령어

Queue<String> queue = new LinkedList<>()

queue.offer("first"); //값 삽입
 
queue.peek(); //해당 큐의 가장 앞에 있는 요소(먼저 들어온 요소) 반환
 
queue.poll(); // 해당 큐의 가장 앞에 있는 요소(먼저 들어온 요소)를 반환하고 제거

Queue에서 반환할 값 없을 경우

Queue에서 반환할 값이 없을 때 poll() 을 할경우 null 을 반환한다.

Priority Queue

선입 선출의 개념과 정렬의 개념을 모두 가져갈 수 있다.

PriorityQueue<String> queue = new PriorityQueue<>(); // String기준 오름차순
PriorityQueue<String> queue = new PriorityQueue<>(Collections.reverseOrder());// 내림차순

Set

HashSet

HashSet<String> hset = new HashSet<String>(); //선언

hset.add(자료 객체); //set에 저장된 자료는 순서가 보장되지 않는다.

<공통메소드>
hset.size(); 
hset.contains();
hset.isEmpty(); 
  • set에 저장된 자료는 순서가 보장되지 않는다.
  • 출력을 하기 위해 다른 방법이 필요하다.

Set의 add 중복

hset.add(new String("java"));
hset.add(new String("mariaDB"));
hset.add(new String("html"));
hset.add(new String("servlet"));
hset.add(new String("spring"));

hset.add(new String("java"));
hset.add(new String("mariaDB"));
hset.add(new String("html"));
System.out.println("hset : " + hset);

결과

hset : [mariaDB, spring, java, servlet, html]
  • 위와 같이 주소값을 새로 부여한 String 객체로 같은 값을 넣어도, 중복값을 허용 하지 않는다. → Hashcode() 오버라이딩

Set 출력

1. 배열로 변경

Object[] arr = hset.toArray(); //다형성 이용해서 오브젝트 타입의 배열 선언
for (int i = 0; i< arr.length; i++) {
		System.out.println("arr[i] = " + arr[i]);
}

결과

arr[i] = mariaDB
arr[i] = spring
arr[i] = java
arr[i] = servlet
arr[i] = html

2. Iterable iterator 메소드

Iterator<String> iter = hset.iterator();
while(iter.hasNext()) {
    System.out.println("iter.next() = " + iter.next());
}

iter.remove(); // 값 제거 메소드

결과

iter.next() = mariaDB
iter.next() = spring
iter.next() = java
iter.next() = servlet
iter.next() = html

3. 향상된 for문

for (String str : hset) {
    System.out.println("str = " + str);
    // hset.remove(str); ❌ 사용불가
}

결과

str = mariaDB
str = spring
str = java
str = servlet
str = html
  • 반복 순회 도중 hset.remove(str) 메소드를 사용하면 java.util.ConcurrentModificationException 이 발생한다.

LinkedHashSet

저장 당시의 순서를 유지하는 특성을 가지고 있다.

  • 중복 제거
  • 순서 유지
Set<String> lset = new LinkedHashSet<>(); //선언

lset.add("kimchi");
lset.add("friedEgg");

lset.add("kimchi");
lset.add("friedEgg");
// 중복되도 값이 하나만 들어간다.

LinkedHashSet의 출력

HashSet과 동일하게 iterator이나 반복문을 통해 가져와 출력한다.

1. Iterator

Iterator<String> iterator = lset.iterator();
while (iterator.hasNext()) {
    System.out.println(iterator.next());
}

TreeSet

이진트리를 사용하여 정렬을 사용할 수있다. String클래스에 implements Comparable을 통해 compareTo메소드가 오버라이딩 되어 있는 기준으로 정렬된다.

  • 중복제거
  • 오름차순 정렬
TreeSet<String> tset = new TreeSet<String>(); //선언

tset.add("kimchi");
tset.add("friedEgg");
tset.add("rice");
tset.add("ramen");
tset.add("pork");
tset.add("kimchi");

결과

tset = [friedEgg, kimchi, pork, ramen, rice]

TreeSet으로 lotto번호 재구현해보기

Set<Integer> lotto = new TreeSet<Integer>();
while(lotto.size() < 6) {
    lotto.add((int)(Math.random() * 45)+1);
}
System.out.println("lotto = " + lotto);

Map

HashMap

  • Key 값 중복 불가 (set의 성질)
    • 동일 키 값에 값을 넣으면 값이 수정된다.
  • value 값 중복 가능 (Link의 성질)
# map의 공통 메소드
HashMap hmap = new HashMap();

hmap.put(Key, Value);     // map에 key와 value를 넣는다.
hmap.put(수정key값, Value); //수정
hmap.get(key값);              //값을 가져온다.
hmap.remove(key값);           // 값 삭제

1. 기본생성자, 제네릭없이 사용

  • 제네릭 없이 타입의 구분없이 사용도 가능하다.

hmap.put("one", new Date());
hmap.put(12, "red apple");
hmap.put(33, 123);
System.out.println("hmap = " + hmap);

결과 

hmap = {33=123, one=Mon Nov 03 10:27:33 KST 2025, 12=red apple}

2. 값 수정

  • 동일 키 값에 값을 넣으면 값이 수정된다.
hmap.put(12, "red apple");
->
hmap.put(12, "yellow apple");

결과
hmap = {33=123, one=Mon Nov 03 10:27:33 KST 2025, 12=yellow apple}

3. 없는 값

없는 값을 hmap.get(10000) 하면 null을 리턴한다.

HashMap<K, V>

HashMap<String, String> hmap2 = new HashMap<>();
hmap2.put("one", "red apple");
hmap2.put("two", "yellow apple");

결과 

hmap2 = {one=red apple, two=yellow apple}

0. 자주 사용되는 세트

<String, Object>
<Integer, Object> 
  • 위 조합을 실사용에 많이 쓴다.

1. 문자열:key , Value

HashMap<String, String> hmap2 = new HashMap<>();
  • Key와 Value 값에 래퍼크래스 자료형이 들어간다.

Map 순회 방법

1. keySet() 메소드를 사용하여 순회

  • Key 값만 set로 가져온다는 것이다.
hmap2.keyset();

-> [one, two]
  • Iterator 과 반복문으로 순회하기
Iterator setIt = hmap2.keySet().iterator();
while (setIt.hasNext()) {
    String key = setIt.next().toString();
    String value = hmap2.get(key);
    System.out.println("key = " + key + ", value = " + value);
}
// 향상된 for문
for (String key : hmap2.keySet()) {
    System.out.println(key + " = " + hmap2.get(key));
}

2. Value() 값만 가져오기

3. EntrySet() 사용

Set<Map.Entry<String, String>> entries = hmap2.entrySet();
Iterator<Map.Entry<String, String>> iterator = entries.iterator(
while (iterator.hasNext()) {
    Map.Entry<String, String> entry = iterator.next();
    System.out.println("entry = " + entry.getValue());
}
for  (Map.Entry<String, String> entry : entries) {
    System.out.println("key = " + entry.getKey() + ", value = " + entry.getValue());
}

Map의 기본값 설정해주기

.getOrDefault()

System.out.println(map.getOrDefault(78901, "홍길동"));
해당 키 값이 존재하지 않으면 defualt값을 출력한다.

Map의 키 값이 없을 때 설정해주기

.computeIfAbsent()

map.computeIfAbsent(10, (k)->"james");
System.out.println("map = " + map);

Properties

  • HashMap과 거의 유사하지만 key와 value모두를 String으로만 사용할 수 있는 자료구조이다.(설정파일에 넣어서 사용하는 부분)
  • 설정 파일의 값을 읽어서 애플리케이션에 적용할 때 사용한다.
Properties prop = new Properties();
/*DB 접속 정보를 갖고 있는 객체를 의미로 설정한 것*/
prop.setProperty("driver", "com.maria.jdbc.Driver");
prop.setProperty("url", "jdbc:maria://localhost:3306/test");
prop.setProperty("user", "student");
prop.setProperty("password", "student");

결과

prop = {password=student, 
				driver=com.maria.jdbc.Driver, 
				user=student, 
				url=jdbc:maria://localhost:3306/test}

property 정보를 FileOutputStream을 통해 출력(Write) 하는 예제

  • 각각 .dat , .properties , .xml 파일을 생성시킨다.
try {
    prop.store( new FileOutputStream("src/main/java/com/lhw/section03/map/run/driver.dat"),
				            "jdbc driver(comment)");
    prop.store(new FileWriter("src/main/java/com/lhw/section03/map/run/driver.properties"),
                    "jdbc driver(comment-properties)");
    prop.store(new FileWriter("src/main/java/com/lhw/section03/map/run/driver.xml"),
                    "jdbc driver(comment-xml)");
} catch (FileNotFoundException e) {
    throw new RuntimeException(e);
} catch (IOException e) {
    throw new RuntimeException(e);
}
  • 결과

image.png

Property 정보를 FileInputStream을 통해 읽어(read)오는 예제

Properties prop2 = new Properties();
System.out.println("prop2 = " + prop2); //비어있음
//비어 있는 prop2에 위에 내용을 하나씩 뽑아낸다.
try {
    prop2.load(new FileInputStream("src/main/java/com/lhw/section03/map/run/driver.dat"));
    prop2.load(new FileReader("src/main/java/com/lhw/section03/map/run/driver.properties"));
    prop2.loadFromXML(new FileInputStream("src/main/java/com/lhw/section03/map/run/driver.xml"));
} catch (IOException e) {
    throw new RuntimeException(e);
}
System.out.println("prop2 = " + prop2);

→ 위 try문 내에서 각각 prop의 .dat , .properties, .xml파일을 읽어온다.

Property 객체에 값 추가/읽어오기

setProperty()

getProperty()

profile
백엔드

0개의 댓글