본문 바로가기
  • 개발공부 및 일상적인 내용을 작성하는 블로그 입니다.
부트캠프/후기 챌린지

[멋쟁이사자처럼부트캠프] 백엔드 자바 21기 (2026.01.26) #1

by 방구석 취준생 2026. 3. 25.

* 해당 글은 백엔드 자바 강의이후 회고 글입니다.

https://bootcamp.likelion.net/school/kdt-backendj-21th

 

백엔드 부트캠프 21기: Java : 멋사 부트캠프

실전 스킬 기반 백엔드 개발자 취업 완벽 대비 교육

bootcamp.likelion.net

 

컬렉션 프레임워크(Collection Framework)

프로그램을 개발할 때는 사용하는 자료를 어떤 구조로 관리할 것인지가 중요하다. 그래야 프로그램의 기능을 효과적으로 구현할 수 있기 때문이다.

이때 사용하는 것이 자료 구조(Data Structure) 이다.

자료 구조는 프로그램 실행 중 메모리에 자료를 유지, 관리하기 위해 사용한다.

 

자바에서는 필요한 자료 구조를 미리 구현하여 java.util 패키지에서 제공하고 있는데, 이를 컬렉션 프레임워크(Collection Framework) 라고 한다.

자료구조는 필요할 때 직접 만들어 사용할 수도 있으나 자바 컬렉션 프레임워크를 사용하면 직접 개발하는 수고를 덜 수 있을뿐만 아니라 잘 만들어진 자료 구조 클래스를 활용할 수 있다.

 

자바 컬렉션 프레임워크의 인터페이스

자바 컬렉션 프레임워크에는 여러 인터페이스가 정의되어 있고, 그 인터페이스를 구현한 클래스가 있다.

컬렉션 프레임워크의 전체 구조는 Collection 인터페이스와 Map 인터페이스 기반으로 이루어져 있다.

  • Collection 인터페이스 : 하나의 자료를 모아서 관리하는데 필요한 기능을 제공한다.
  • Map 인터페이스 : (key,value) 쌍으로 이루어진 자료들을 관리하는데 유용한 기능을 제공한다.

 

Collection 인터페이스

Collection 인터페이스 하위에는 List 인터페이스와 Set 인터페이스가 있다.

  • List 인터페이스 : 순차적인 자료를 관리하는데 사용한다. 데이터의 중복이 허용된다.
    • 구현 클래스 : ArrayList, Vector, LinkedList, Stack, Queue 등
  • Set 인터페이스 : 순서와 상관없이 중복을 허용하지 않는 데이터 집합으로서 활용된다. 데이터의 중복이 허용되지 않는다.
    • 구현 클래스 : HashSet, TreeSet 등

 

Map 인터페이스

Map 인터페이스는 하나가 아닌 쌍(pair) 으로 되어있는 자료를 관리하는 메서드들이 선언되어 있다.

이를 key-value 쌍이라고 표현하는데 이때 키 값은 중복될 수 없다.

  • 구현 클래스 : Hashtable, HashMap, TreeMap 등

Map 은 기본적으로 검색용 자료구조이다. 즉, 어떤 key 값을 알고있을 때 value 를 찾기위한 자료구조이다.

 

public class Member{
    private int memberId;
    private String memberName;
    
    public Member(int memberId, String memberName) {
        this.memberId = memberId;
        this.memberName = memberName;
    }
    
    public int getMemberId(){
        return memberId;
    }
    
    public void setMemberId(int memberId){
        this.memberId = memberId;
    }
    
    public String getMemberName(){
        return memberName;
    }
    
    public void setMemberName(String memberName){
        this.memberName = memberName;
    }
    
    @Override
    public String toString(){
        return memberName + " 회원님의 아이디는 " + memberId + "입니다.";
    }
}

 

  • 위의 코드는 컬렉션 프레임워크의 실습을 위해 사용될 Member 클래스를 만들어둔 것이다.
  • toString() 메서드는 Object 클래스로부터 재정의 받아왔다.

 

List 인터페이스

List 인터페이스에는 마치 배열처럼 객체를 순서에 따라 저장하고 유지하는데 필요한 메서드가 선언되어 있다.

자바에서 배열을 구현한 대표 클래스로는 ArrayList, Vector 가 있고, 배열과 구현 방식은 다르지만 순차 자료구조를 구현한 LinkedList 도 있다.

 

ArrayList  클래스

  • ArrayList 는 객체 배열을 구현한 클래스이며 배열처럼 메모리에서 시작주소를 기준으로 연속적인 공간을 할당받는다.
  • 객체 순서를 기반으로 순차적으로 자료를 관리하는 프로그램을 구현할 때 사용한다.

아래의 코드를 보자.

public class MemberArrayList {
    private ArrayList<Member> arrayList;
    
    public MemberArrayList(){ // Member 형으로 선언한 ArrayList 객체 생성
        arrayList = new ArrayList<Member>;
    }
    
    // ArrayList 에 회원 추가
    public void addMember(Member member){
        arrayList.add(member);
    }
    
    public boolean removeMember(int memberId){
        for(int i = 0l i < arrayList.size(); i++){
            Member member = arrayList.get(i); // Member 객체 순차적으로 가져옴
            int tempId = member.getMemberId();
            
            // 회원 아이디가 매개변수와 일치하면 해당 회원을 삭제
            if(tempId == memberId){
                arrayList.remove(i);
                return true;
            }
        }
        
        // 반복문이 끝날때까지 해당 아이디를 찾지 못한경우
        System.out.println(memberId + "가 존재하지 않습니다.");
        return false;
    }
    
    // 전체 회원을 출력하는 메서드
    public void showAllMember(){
        for(Member member : arrayList){
            System.out.println(member);
        }
        System.out.println();
    }
}

 

  • addMember(Member member) : 매개변수로 전달된 회원을 ArrayList 의 맨 뒤에 추가한다.
  • removeMember(int memberId) : 매개변수로 전달받은 아이디(memberId) 를 가지고 있는 회원을 ArrayList 에서 찾아서 제거한다.
  • showAllMember() : ArrayList 에 저장되어 있는 모든 회원을 출력한다. Member 클래스에 재정의한 toString() 이 호출되며 회원정보가 출력된다.

위의 클래스의 기능들을 테스트 하기위한 코드는 아래와 같다.

public class MemberArrayListTest {
    public static void main(String[] args){
        MemberArrayList memberArrayList = new MemberArrayList();
        
        Member memberLee = new Member(1001, "이지원");
        Member memberSon = new Member(1002, "손민국");
        Member memberPark = new Member(1003, "박서훤");
        Member memberHong = new Member(1004, "홍길동");
        
        memberArrayList.addMember(memberLee);
        memberArrayList.addMember(memberSon);
        memberArrayList.addMember(memberPark);
        memberArrayList.addMember(memberHong);
        
        memberArrayList.showAllMember();
        
        // 홍길동 회원 삭제
        memberArrayList.removeMember(memberHong.getMemberId());
        memberArrayList.showAllMember();
    }
}

 

 

* ArrayList 의 용량에 대하여...

ArrayList.java 파일을 열어 내부를 확인해보면 아래와 같은 코드가 있는것을 확인해볼 수 있다.

transient Object[] elementData;
// ....

/**
  * Default initial capacity
  */
 private static final int DEFAULT_CAPACITY = 10;

 

  • ArrayList() 디폴트 생성자를 호출하여 배열 크기를 지정하지 않으면 크기가 10개짜리인 배열이 기본적으로 만들어진다.
  • 이를 배열의 용량(capacity) 이라고 한다.
  • ArrayList(int) 생성자를 사용하면 초기에 생성할 배열의 용량을 지정할 수도 있다.
  • 배열에 요소를 추가하여 3개의 항목이 있다고 할 때, size() 메서드를 호출하면 유효한 값이 저장된 갯수 3이 반환된다.
  • 이는 배열의 용량과는 다른 의미이다.
  • ArrayList 에 요소를 추가하다보면 처음 생성한 용량이 부족해질 수 있다.
  • 기본으로 10개가 만들어진 경우 11번째 요소를 추가하면 어떻게 될까?
  • add() 나 insert() 등의 메서드는 용량이 부족해지면 큰 용량의 배열을 새로 만들고 기존 항목을 복사한다.

 

* ArrayList 와 Vector 클래스

  • Vector 는 ArrayList 처럼 배열을 구현한 클래스이다.
  • ArrayList 와 Vector 의 가장 큰 차이는 동기화 지원여부이다.
  • 동기화(synchronization) 란 두 개 이상의 스레드가 동시에 Vector 를 사용할 때 오류가 나지 않도록 실행 순서를 보장하는 것이다.

+) 스레드와 멀티스레드 프로그래밍

  • 스레드란 간단히 말하면 작업 단위이다. 프로그램이 메모리에서 수행되려면 스레드 작업이 생성되어야 한다.
  • 이때 하나의 스레드만 수행되면 단일 스레드(single thread), 두 개 이상의 스레드가 동시에 실행되면 멀티 스레드(multi-thread) 라고한다.
  • 두 개 이상의 스레드가 동시에 실행되면 같은 메모리 공간(리소스) 에 접근하기 때문에 변수 값이나 메모리 상태에 오류가 생길 수 있다.
  • 이때 메모리에 동시에 접근하지 못하도록 순서를 맞추는 것이 동기화이다.
  • 두 작업이 동시에 실행되는 멀티 스레드 환경이 아닌 경우에는 ArrayList 를 사용하도록 권장한다. 왜냐하면 동기화를 구현하기 위해서는 동시에 작업이 이루어지는 자원에 대해 잠금(lock) 을 수행하기 때문이다.
  • 즉, 메서드를 호출할 때 배열 객체에 잠금을 하고, 메서드 수행이 끝나면 잠금을 해제한다는 뜻이다.
  • 이렇게 Vector 의 모든 메서드는 호출될 때마다 잠금과 해제가 일어나므로 ArrayList 보다 수행 속도가 느리다. 
  • ArrayList 를 사용해서 구현했는데 나중에 프로그램에서 동기화가 필요하다면 Vector 로 바꾸지 않고 다음과 같이 ArrayList 생성 코드를 쓰면 된다.
Collections.synchronized(new ArrayList<String>());

 

 

LinkedList 클래스

배열은 처음 배열을 생성할 때 정적 크기로 선언하고, 물리적 순서와 논리적 순서가 동일하다.

배열은 중간에 자료를 삽입하거나 삭제할 때 나머지 자료를 이동시켜 빈 공간을 만들지 않고 연속된 자료 구조를 구현한다.

또한 처음 선언한 배열 크기 이상으로 요소가 추가되는 경우 크기가 더 큰 배열을 새로 생성하여 각 요소를 복사해야 하는 번거로움이 있다.

이런 점을 개선한 자료구조를 연결리스트(linkedlist) 라고 한다. 자바의 LinkedList 클래스가 이를 구현하고 있다.

 

  • 연결리스트의 각 요소는 다음 요소를 가리키는 주소값을 가지고 있다. 따라서 물리적인 메모리는 떨어져 있어도 논리적으로는 앞뒤 순서가 있다.
  • 같은 List 인터페이스를 구현한 ArrayList 에 비해 중간에 자료를 넣고 제거하는데 시간이 적게 걸린다는 것과 크기를 동적으로 증가시킬 수 있다는 장점이 있다.
  • 연결리스트의 각 요소는 요소의 자료와 다음 요소의 주소를 저장하는 부분으로 구현된다.
자료(data) 다음 요소의 주소

 

  • 연결리스트의 각 요소는 물리적으로 제각각의 위치에 있는 메모리에 생성되어 있지만 다음 요소를 가리키는 순서에 따라 순차적인 특징을 가지고 있다.
  • 연결리스트의 마지막 요소는 자신의 다음으로 가리키는 요소가 없기 때문에 다음 요소를 가리키는 주소로 null 값이나 0을 저장한다.
  • 연결리스트에 A -> B -> C 의 순서로 데이터가 저장되어 있을 때 B 와 C 사이에 D 라는 요소를 추가하는 경우 기존에 B가 가지고 있는 C의 위치를 가리키는 주소를 D 에게 저장해준 다음, B 의 다음 요소를 가리키는 주소로 기존의 C 가 아닌 D 의 주소를 저장해주면된다. 
  • 연결리스트에 A -> B -> C 의 순서로 데이터가 저장되어 있을 때 중간에 있는 B를 제거하고 싶다면 기존에 A 가 가지고 있던 B의 주소를 B가 가지고 있는 C 가 있는 주소로 덮어씌워 주면 된다. 이때 제거된 B의 메모리는 추후에 GB 에 의해 메모리에서 제거된다. 

배열과 연결리스트의 차이점

  • 배열은 생성할 때 용량을 지정하고, 용량보다 더 많은 요소가 추가된 경우엔 기존보다 더 큰 용량을 가진 배열을 만들어가며 더 많은 요소를 저장한다.
  • 그러나 연결리스트는 요소를 추가할 때 새로운 연결리스트를 만들어줄 필요없이 동적으로 요소의 메모리를 생성하기 때문에 배열처럼 더 큰 용량을 가진 배열을 새로 만들고 기존 요소의 값을 복사하는 번거로움이 없다.
  • 또한 연결리스트는 자료를 중간에 추가하거나 삭제할 때 자료의 이동이 배열보다 적다. 이런면에서는 연결리스트가 배열에 비해 더 편리한 자료구조라고 생각할 수도 있다.
  • 그러나 배열이 연결리스트보다 효율적인 경우 또한 존재한다. 어떤 요소의 위치를 찾고자 할 경우 연결리스트는 처음부터 순차적으로 찾고자 하는 요소와 현재 탐색중인 연결리스트의 요소가 같은지 아닌지를 비교해가며 순차 탐색을 해야하는데 반해, 배열의 경우 인덱스의 위치를 알고 있으면 순차 탐색할 필요없이 곧장 해당 요소를 메모리에서 찾아올 수 있다.
  • 배열에서 인덱스는 배열의 시작주소에서 몇번째 위치에 떨어져 있는 메모리인지를 정의하고 있기 때문에 곧장 해당되는 주소의 메모리로 찾아갈 수 있는것이다.
  • 따라서 사용하는 자료의 변동(삽입, 삭제) 가 많은 경우엔 연결리스트를, 자료 변동이 거의 없는 경우엔 배열을 사용하는 것이 좋다. 

 

연결리스트를 사용해보자.

자바의 LinkedList 클래스에서는 ArrayList 보다 더 다양한 메서드를 제공한다.

아래의 코드는 LinkedList 클래스에서만 제공하는 메서드를 사용해보자.

public class LinkedList {
    public static void main(String[] args){
        LinkedList<String> myList = new LinkedList<String>();
        
        myList.add("A");
        myList.add("B");
        myList.add("C");
        
        System.out.println("myList");
        
        myList.add(1, "D"); // 연결리스트의 첫번째 위치에 D 추가
        System.out.println("myList");
        
        myList.addFirst("0");
        System.out.println("myList");
        
        // 연결리스트의 맨 뒤 요소를 삭제 후 해당 요소를 출력
        System.out.println(myList.removeLast());
        System.out.println("myList");
    }
}

 

  • LinkedList 클래스에서는 연결리스트의 맨 앞, 또는 맨 뒤에 있는 요소를 추가, 삭제하는 addFirst(), addLast(), removeFirst(), removeLast() 등의 메서드를 지원한다.
  • 이 메서드들은 스택, 큐 등의 자료 구조에서 다양하게 활용할 수 있다.

ArrayList 로 스택과 큐 구현하기

스택은 가장 나중에 추가된 데이터를 먼저 꺼내는 LIFO(Last In First Out) 방식의 자료 구조이다.

큐는 가장 먼저 추가된 데이터를 먼저 꺼내는 선입선출인 FIFO(First In First Out) 방식의 자료 구조이다.

  • Stack 클래스는 자바 1 부터 제공해왔고 Queue 는 인터페이스로 정의되어 있으며 PriorityQueue(우선순위 큐) 등이 구현되어 있다.
  • 하지만 ArrayList 와 LinkedList 를 사용하는 경우도 종종 있다. 위의 클래스들을 사용하는 것보다 직접 구현하여 사용하는게 더 편할 때도 있기 때문이다.

스택은 가장 최근에 추가된 자료부터 반환해준다. 그러므로 가장 최근에 검색한 단어를 찾거나 장기, 체스 같은 게임에서 두었던 수를 무를때도 응용할 수 있다.

스택에 자료를 추가하는것은 push() 라고 하고 자료를 꺼내는 것은 pop() 이라고 한다.

그리고 스택에 가장 최근에 추가된 자료의 위치를 top 이라고 한다.

 

아래의 코드는 ArrayList 를 이용하여 push() 와 pop() 을 간단하게 구현하여 스택을 만든 코드이다.

class MyStack {
    private ArrayList<String> arrayStack = new ArrayList<String>();
    
    // 스택의 맨 뒤에 요소를 추가
    public void push(String data){
        arrayStack.add(data);
    }
    
    // 스택의 맨 뒤에서 요소를 꺼냄
    public String pop(){
        int len = arrayStack.size();
        if(len == 0){
            System.out.println("스택이 비어있습니다.");
            return null;
        }
        
        // 맨 뒤에 있는 자료를 반환하고 배열에서 제거
        return(arrayStack.remove(len - 1));
    }
}

public class StackTest {
    public static void main(String[] args){
        MyStack stack = new MyStack();
        stack.push("A");
        stack.push("B");
        stack.push("C");
        
        System.out.println(stack.pop());
        System.out.println(stack.pop());
        System.out.println(stack.pop());
    }
}

 

* 중요! 반드시 기억할 것!

  • 함수를 호출하면 JVM 의 스택 메모리에 함수 호출 스택과 함께 함수 내부의 지역 변수가 생성된다.
  • 이때 함수를 호출하면 호출된 함수가 끝날때까지 해당 함수의 메모리 공간은 계속 남아있게 된다.
  • 이렇게 가장 나중에 호출된 함수와 그 함수의 지역 변수가 사용하는 메모리는 스택 자료 구조와 같은 방식으로 운영된다.

 

아래의 코드는 ArrayList 로 큐를 만든 코드이다.

class MyQueue {
    private ArrayList<String> arrayQueue = new ArrayList<String>();
    
    // 큐의 맨 뒤에 요소 추가
    public void enQueue(String data){
        arrayQueue.add(data);
    }
    
    // 큐의 맨 앞에서 꺼냄
    public String deQueue(){
        int len = arrayQueue.size();
        if(len == 0){
            System.out.println("큐가 비어있습니다.");
            return null;
        }
        
        // 맨 앞의 자료 반환하고 배열에서 제거
        return(arrayQueue.remove(0));
    }
}

public class QueueTest {
    public static void main(String[] args){
        MyQueue queue = new MyQueue();
        queue.enQueue("A");
        queue.enQueue("B");
        queue.enQueue("C");
        
        System.out.println(queue.deQueue());
        System.out.println(queue.deQueue());
        System.out.println(queue.deQueue());
    }
}

 

 

Collection 요소를 순회하는 Iterator

앞서 구현한 MemberArrayList 클래스의 removeMember() 메서드를 보면 for 문과 get(i) 메서드를 사용하여 회원을 순차적으로 하나씩 꺼내면서 매개변수와 같은 아이디를 찾는다.

그런데 순서가 없는 Set 인터페이스를 구현한 경우에는 get(i) 메서드를 사용할 수 없다.

이때는 Iterator 를 사용한다. Iterator 는 Collection 인터페이스를 구현한 객체에서 미리 정의되어 있는 iterator() 메서드를 호출하여 참조한다.

예를 들어 Collection 을 구현한 ArrayList 에 iterator() 메서드를 호출하면 Iterator 클래스가 반환되므로 다음처럼 Iterator 형 변수에 대입해 사용한다.

Iterator ir = memberArrayList.iterator();

 

Iterator 를 사용하여 모든 요소를 순회할 때 아래의 두 가지 메서드를 사용한다.

  • boolean hasNext() : 이후에 요소가 더 있는지를 체크하는 메서드이며, 요소가 있다면 true 를 반환한다.
  • E next() : 다음에 있는 요소를 반환한다.

위의 두 메서드로 MemberArrayList 클래스의 removeMember() 메서드를 수정해보자.

public boolean removeMember(int memberId) {
    // Iterator 반환
    Iterator<Member> ir = arrayList.iterator();
    
    while(ir.hasNext()) { // 요소가 있는 동안 다음 회원을 반환받는다.
        Member member = ir.next();
        int tempId = member.getMemberId();
        
        // 회원 아이디가 매개변수와 일치하면 해당 회원을 삭제하고 true 반환
        if(tempId == memberId) {
            arrayList.remove(member);
            return true;
        }
    }
    
    // 끝날때까지 삭제하려는 값을 찾지 못한경우
    System.out.println(memberId + "가 존재하지 않습니다.");
    return false;
}

 

  • arrayList.iterator() 메서드를 호출하여 Iterator 를 가져온다. 
  • Iterator<Member> 와 같이 제네릭 자료형으로 Iterator 가 순회하기 때문에 hasNext() 의 결과가 true 이면 다음 요소를 가져오는 next() 메서드를 호출한다.
  • 나머지 비교 부분은 for 문과 get(i) 메서드를 사용하는 경우와 같다. 
  • 이와 같은 방식으로 구현하면 순서가 없는 클래스도 Iterator 를 사용하여 요소를 순회할 수 있다.