8일차(2) - 제네릭

은채의 성장통·2025년 6월 9일

KCC정보통신

목록 보기
12/30

노션정리

3. 그 외의 컬렉션


1. Vector

VectorList 인터페이스를 구현한 클래스이며,

내부 메서드들이 synchronized 되어 있어 Thread-safe(쓰레드 안전성)을 제공합니다.

특징

  • ArrayList와 비슷하지만, 멀티스레드 환경에서 안전
  • 요소 접근 방식: 인덱스 기반 (배열처럼 사용 가능)
  • addElement(), elementAt(), size(), remove() 등의 메서드 지원
  • Thread-safe가 필요 없으면 ArrayList를 사용하는 것이 더 효율적
  • 예제 코드: Vector 사용
import java.util.Vector;

public class VectorExample {
    public static void main(String[] args) {
        // Vector 생성
        Vector<Person> v = new Vector<>();

        // 요소 추가
        v.addElement(new Person("홍길동", 29));
        v.addElement(new Person("이순신", 30));
        v.addElement(new Person("강감찬", 65));

        System.out.println("Vector 요소: " + v); // 전체 출력

        // 특정 요소 가져오기 (인덱스 기반)
        Person p = v.elementAt(2);
        System.out.println("인덱스 2의 요소: " + p.getName());

        // 요소 제거
        v.remove(1);
        System.out.println("인덱스 1 제거 후: " + v);

        // 현재 크기 확인
        System.out.println("Vector 크기: " + v.size());
    }
}

  • 2. Stack

StackVector의 하위 클래스로, LIFO(Last-In-First-Out, 후입선출) 방식의 자료구조입니다.

특징

  • push(): 요소 추가
  • pop(): 마지막 요소 제거 및 반환
  • peek(): 마지막 요소 조회 (제거 X)
  • empty(): 스택이 비어있는지 확인
  • 예제 코드: Stack 사용
import java.util.Stack;

public class StackExample {
    public static void main(String[] args) {
        // Stack 생성
        Stack<Person> stack = new Stack<>();

        // 요소 추가 (push)
        stack.push(new Person("홍길동", 29));
        stack.push(new Person("이순신", 30));
        stack.push(new Person("강감찬", 65));

        System.out.println("Stack 전체: " + stack);

        // pop(): 마지막 요소 제거 및 반환
        Person p = stack.pop();
        System.out.println("pop() 호출: " + p.getName());
        System.out.println("Stack 상태: " + stack);

        // peek(): 마지막 요소 조회 (제거되지 않음)
        Person p2 = stack.peek();
        System.out.println("peek() 호출: " + p2.getName());
        System.out.println("Stack 상태: " + stack);

        // empty(): 스택이 비었는지 확인
        System.out.println("Stack이 비었는가? " + stack.empty());
    }
}

  • 3. Hashtable

HashtableMap 인터페이스를 구현한 클래스이며,

HashMap과 유사하지만 Thread-safe합니다.

특징

  • Map과 동일하게 Key-Value 저장 구조
  • put(), get(), remove() 메서드를 통해 데이터를 저장/삭제/조회 가능
  • 멀티스레드 환경에서 안전하지만, 성능이 느림
  • ConcurrentHashMap을 사용하면 보다 높은 성능 제공 가능
  • Hashtable vs HashMap
비교 항목HashtableHashMap
Thread-safeO (멀티스레드 환경에서 안전)X (싱글스레드 환경에서 빠름)
null 허용 여부X (null key, null value 허용 안됨)O (null key, null value 허용)
속도느림 (synchronized)빠름

  • 4. Enumeration

EnumerationVector, Stack, Hashtable 등에서 요소를 조회하는 클래스입니다.

특징

  • nextElement(): 다음 요소를 반환
  • hasMoreElements(): 더 많은 요소가 있는지 확인
  • Enumeration 예제 코드
import java.util.Enumeration;
import java.util.Vector;

public class EnumerationExample {
    public static void main(String[] args) {
        // Vector 생성
        Vector<String> vector = new Vector<>();
        vector.add("Apple");
        vector.add("Banana");
        vector.add("Cherry");

        // Enumeration을 사용하여 요소 순회
        Enumeration<String> e = vector.elements();
        while (e.hasMoreElements()) {
            System.out.println("다음 요소: " + e.nextElement());
        }
    }
}

3.1. 수정할 수 없는 List

자바에서는 List.of()List.copyOf()를 사용해 수정할 수 없는(immutable) 리스트를 만들 수 있습니다.

이러한 리스트는 생성 후 추가, 삭제, 변경이 불가능하여, 데이터를 안전하게 관리할 수 있습니다.

1. List.of() 사용

  • 가변 인자를 받아 수정할 수 없는 리스트를 생성합니다.
  • 리스트를 만들면 추가, 삭제, 변경이 불가능하며 UnsupportedOperationException 예외가 발생합니다.
import java.util.List;

public class ImmutableListOf {
    public static void main(String[] args) {
        // 수정할 수 없는 리스트 생성
        List<String> immutableList = List.of("apple", "banana", "orange");

        // 생성된 리스트는 수정이 불가능 (실행 시 예외 발생)
        immutableList.add("grape"); // UnsupportedOperationException 발생

        // 리스트 조회는 가능
        System.out.println("Immutable List: " + immutableList);
    }
}

2. List.copyOf() 사용

  • 기존 컬렉션을 수정할 수 없는 리스트로 복사할 때 사용됩니다.
  • 원본 리스트가 변경되어도 새 리스트는 영향을 받지 않음.
import java.util.ArrayList;
import java.util.List;

public class ImmutableListCopyOf {
    public static void main(String[] args) {
        // 변경 가능한 리스트 생성
        List<String> mutableList = new ArrayList<>();
        mutableList.add("apple");
        mutableList.add("banana");
        mutableList.add("orange");

        // 기존 리스트를 복사하여 수정할 수 없는 리스트 생성
        List<String> immutableList = List.copyOf(mutableList);

        // 원본 리스트를 변경해도 immutableList는 영향을 받지 않음
        mutableList.add("grape");

        // immutableList는 변경되지 않음
        System.out.println("Immutable List: " + immutableList);
    }
}

3.2. Arrays 클래스

자바에서 배열을 다룰 때 유용한 기능을 제공하는 Arrays 클래스의 주요 메서드를 정리합니다.

1. asList()

  • 배열을 리스트로 변환할 때 사용됩니다.
import java.util.Arrays;

public class ArraysExample {
    public static void main(String[] args) {
        // 배열을 리스트로 변환
        String[] fruitsArray = {"apple", "banana", "orange"};
        System.out.println("Array to List: " + Arrays.asList(fruitsArray));
    }
}

2. compare()

  • 두 배열을 비교하여 크기를 판단합니다. (Java 9부터 사용 가능)
int[] array1 = {1, 2, 3};
int[] array2 = {1, 2, 3, 4};

int result = Arrays.compare(array1, array2);
if (result < 0) {
    System.out.println("Array 1 is smaller than Array 2");
} else if (result > 0) {
    System.out.println("Array 1 is larger than Array 2");
} else {
    System.out.println("Both arrays are equal");
}

3. sort()

  • 배열을 오름차순으로 정렬합니다.
int[] numbers = {5, 2, 9, 1, 3};
Arrays.sort(numbers);
System.out.println("Sorted Array: " + Arrays.toString(numbers));

4. toString()

  • 배열을 문자열로 변환하여 출력합니다.
char[] charArray = {'a', 'b', 'c', 'd', 'e'};
System.out.println("Array to String: " + Arrays.toString(charArray));

5. deepToString()

  • 다차원 배열을 문자열로 변환하여 출력합니다.
  • toString()을 사용하면 다차원 배열의 주소값이 출력되므로, deepToString()을 사용해야 합니다.
int[][] matrix = {{1, 2}, {3, 4}, {5, 6}};
System.out.println("Matrix to String: " + Arrays.deepToString(matrix));

4. 제네릭과 형 안정성(Generic & Type Safety)

  • 제네릭(Generic)은 컬렉션의 요소 타입을 명확하게 지정하여 타입 안정성을 보장하는 기능입니다.
  • 제네릭을 사용하면 컴파일 단계에서 타입 검사를 수행하므로, 잘못된 타입의 데이터를 넣을 수 없고,
  • 형변환 없이 원하는 타입을 직접 사용할 수 있습니다.

4.1. 제네릭 클래스(Generic Class)

제네릭 클래스는 유형 매개변수(Type Parameter)를 포함하는 클래스입니다.

클래스 선언 시 <T>를 사용하여 유동적인 타입을 적용할 수 있습니다.

package generic;

public class Container<T> {  // <T>: 제네릭 타입 매개변수
    private T data;  // 데이터 타입을 T로 지정 (어떤 타입이든 가능)

    public T getData() {  // 반환 타입도 T
        return data;
    }

    public void setData(T data) {  // 매개변수 타입도 T
        this.data = data;
    }
}
  • 제네릭 클래스 사용 예제
package generic;

public class GenericExample {
    public static void main(String[] args) {
        Container<String> cont1 = new Container<>();  // String 타입 지정
        cont1.setData("Hello");

        // cont1.setData(10); // 오류 발생 (타입 불일치)

        String s1 = cont1.getData(); // 형변환 없이 안전한 반환
        System.out.println(s1); // 출력: Hello
    }
}

💡 Container<String>처럼 타입을 지정하면 컴파일 시 타입 검사가 이루어짐

💡 setData(10); 처럼 잘못된 타입을 입력하면 컴파일 오류 발생


타입 매개변수 제한 (Bounded Type Parameter)

제네릭을 사용할 때 특정 클래스나 인터페이스의 하위 클래스만 허용하도록 제한할 수 있습니다.

1. 상위 바운드 (T extends SuperClass)

  • T는 반드시 Number하위 클래스여야 함 (Integer, Double, Float 등 가능)
  • String 같은 다른 타입은 사용할 수 없음
package generic.upperbound;

class Box<T extends Number> {  // T는 반드시 Number의 하위 클래스여야 함
    private T value;

    public Box(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }
}

public class UpperBoundExample {
    public static void main(String[] args) {
        Box<Integer> intBox = new Box<>(10); // 가능
        System.out.println("Integer Value: " + intBox.getValue());

        // Box<String> strBox = new Box<>("Hello"); // 컴파일 오류 발생
    }
}

2. 하위 바운드 (? super SubClass)

  • ? super IntegerInteger상위 클래스만 허용 (Number, Object 등 가능)
  • getValue()를 호출하면 반환 타입이 Object가 됨
package generic.lowerbound;

class Box<T> {
    private T value;

    public Box(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }
}

public class LowerBoundExample {
    public static void main(String[] args) {
        Box<? super Integer> intBox = new Box<>(10); // Integer의 상위 타입만 허용
        Object value = intBox.getValue(); // Object 타입으로 반환
        System.out.println("Value: " + value);

        // intBox.setValue("Hello"); // 컴파일 오류 발생 (String은 허용되지 않음)
    }
}

4.2. 제네릭 메서드(Generic Method)

제네릭 메서드는 메서드 내부에서만 제네릭 타입을 적용하는 방법입니다.

  • 1. 제네릭을 사용한 ArrayList 구현
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess {

    public boolean add(E o) {  // 요소의 타입을 E로 지정
        // ...
        return true;
    }
}
  • 2. 특정 타입의 리스트만 처리하는 제네릭 메서드
public <T extends Person> void doSomething(ArrayList<T> list) {
    // T는 반드시 Person의 하위 클래스여야 함
}

💡 T extends PersonPerson 또는 그 하위 클래스만 허용

  • 3. 와일드카드(?)를 사용한 제네릭 메서드
public void doSomething(ArrayList<? extends Person> list) {
    // Person의 하위 타입을 포함한 리스트 허용
}

💡 ? extends PersonPerson을 포함한 그 하위 클래스도 허용

💡 ? super PersonPerson의 상위 클래스만 허용


5. enum (열거형)

enum고정된 상수들의 집합을 정의하는 자료형으로, 기존 public static final int 방식보다 더 타입 안정성(type-safe)을 갖습니다.

또한, enum메서드, 생성자, 필드 등을 포함할 수 있어 클래스처럼 동작합니다.


5.1. enum 기본 사용

1. 열거형 선언

package enums;

public enum Coin {
    PENNY,    // 1센트
    NICKEL,   // 5센트
    DIME,     // 10센트
    QUARTER;  // 25센트
}
  • 열거형의 각 요소는 자동으로 static final 상수처럼 동작
  • enum은 특정 타입을 명확히 지정하여 형 안정성을 제공
  • 전통적인 int 상수보다 가독성이 좋고 유지보수가 쉬움

2. 열거형 사용 예제

package enums;

public class EnumBasicExample {
    public static void main(String[] args) {
        Coin coin = Coin.DIME; // 열거형 값 사용

        switch (coin) {
            case PENNY:
                System.out.println("1센트 동전입니다.");
                break;
            case NICKEL:
                System.out.println("5센트 동전입니다.");
                break;
            case DIME:
                System.out.println("10센트 동전입니다.");
                break;
            case QUARTER:
                System.out.println("25센트 동전입니다.");
                break;
            default:
                break;
        }
    }
}
  • switch 문에서 enum을 사용할 수 있음

  • 각 enum 값은 EnumClass.VALUE 형식으로 접근 가능 (Coin.DIME)

    전통적인 방식과의 비교

public static final int PENNY = 1;
public static final int NICKEL = 5;
  • 위 방식은 타입 안정성이 부족하여 잘못된 값 전달 위험이 존재
  • enum을 사용하면 컴파일 단계에서 오류를 방지 가능

5.2. "Hidden" 정적 메서드

enum에는 자동으로 제공되는 정적 메서드가 있습니다.

1. values()

  • 열거형의 모든 값을 배열 형태로 반환합니다.

2. valueOf(String name)

  • 주어진 문자열이 열거형과 일치하면 해당 값 반환, 그렇지 않으면 오류 발생.

예제 코드

public class EnumHiddenMethodExample {
    public static void main(String[] args) {
        // 모든 enum 값을 배열로 가져오기
        Coin[] coins = Coin.values();
        System.out.println("열거형 값 목록:");
        for (Coin c : coins) {
            System.out.println(c);
        }

        // 문자열을 이용해 특정 열거형 값 가져오기
        Coin c1 = Coin.valueOf("QUARTER"); // "QUARTER"와 완벽히 일치해야 함
        System.out.println("c1의 값: " + c1);
        System.out.printf("c1의 값: %s\n", c1);
    }
}
  • Coin.values()를 사용하면 열거형의 모든 값을 배열로 가져올 수 있음
  • Coin.valueOf("QUARTER")를 사용하면 해당 enum 값을 반환
  • Coin.valueOf("quarter")처럼 소문자로 입력하면 오류 발생

5.3. 메서드를 갖는 enum

enum은 일반적인 상수 집합을 넘어 메서드를 포함할 수 있는 객체 지향적인 구조입니다.

이를 통해 열거형 값을 특정 값으로 변환하거나 로직을 추가할 수 있습니다.


1. 메서드를 포함한 enum

아래 코드에서 getCent() 메서드는 각 열거형 값에 대응하는 센트 값을 반환합니다.

public enum Coin {
    PENNY, NICKEL, DIME, QUARTER;

    // 각 열거형 값에 대응하는 센트 값을 반환하는 메서드
    public int getCent() {
        switch (this) {
            case PENNY: return 1;
            case NICKEL: return 5;
            case DIME: return 10;
            case QUARTER: return 25;
            default: return 0;
        }
    }
}
  • this → 현재 열거형 값 (PENNY, NICKEL 등)
  • switch(this) → 현재 객체(열거형 값)에 맞는 값을 반환
  • 각 열거형 값에 맞는 특정 값을 반환하는 메서드를 포함할 수 있음

2. enum 메서드 활용 예제

열거형의 모든 값을 순회하며 getCent()를 호출하여 해당 값을 출력하는 코드입니다.

public class EnumMethodExample {
    public static void main(String[] args) {
        for (Coin coin : Coin.values()) {
            System.out.println(coin + "의 값은 " + coin.getCent());
        }
    }
}

출력 결과

PENNY의 값은 1
NICKEL의 값은 5
DIME의 값은 10
QUARTER의 값은 25
  • coin.values() → 모든 열거형 값을 가져옴
  • coin.getCent() → 각 열거형 값에 맞는 센트 값을 반환

5.4. enum 추상 메서드

열거형에서 추상 메서드를 선언하고, 각 열거형 값에서 다르게 구현할 수 있습니다.

각 열거형 항목이 추상 메서드를 개별적으로 정의할 수 있기 때문에 더 유연한 코드 작성이 가능합니다.


1. 추상 메서드를 포함한 enum

public enum Coin2 {
    PENNY {
        int getCent() { return 1; }
    },
    NICKEL {
        int getCent() { return 5; }
    },
    DIME {
        int getCent() { return 10; }
    },
    QUARTER {
        int getCent() { return 25; }
    };

    // 각 열거형이 반드시 구현해야 하는 추상 메서드
    abstract int getCent();
}
  • abstract int getCent(); → 각 열거형에서 반드시 구현해야 하는 추상 메서드
  • 각 열거형 값에서 getCent()각기 다른 방식으로 구현 가능

2. 추상 메서드 활용 예제

열거형의 모든 값을 순회하며 getCent()를 호출하여 해당 값을 출력하는 코드입니다.

public class EnumAbstractMethodExample {
    public static void main(String[] args) {
        for (Coin2 coin : Coin2.values()) {
            System.out.println(coin + "의 값은 " + coin.getCent());
        }
    }
}

출력 결과

PENNY의 값은 1
NICKEL의 값은 5
DIME의 값은 10
QUARTER의 값은 25
  • 각 열거형 값이 추상 메서드를 개별적으로 구현
  • coin.getCent()를 호출하여 각 열거형 값에 맞는 결과를 반환

정리

  • enum일반 클래스처럼 메서드를 포함할 수 있음
  • switch를 활용해 열거형 값을 특정 값으로 변환할 수 있음
  • enum에서 추상 메서드를 선언하고, 각 열거형 값에서 별도로 구현 가능
  • 코드 유지보수가 쉬워지고, 객체 지향적인 설계를 지원

5.5. 생성자를 갖는 enum

확장이 가능하기 떄문데 센트 값을 필드로 넣고 생성자를 추가할 수 있습니다。

 package	enums;
 2 
3 public	enum	Coin3	{
 4 PENNY(1),
 5 NICKEL(5),
 6 DIME(10),
 7 QUARTER(25);
 8 
9 private	final	int	cent;
 10 
11 Coin3(int	cent)	{
 12 this.cent	=	cent;
 13 }
 14 
15 public	int	getCent()	{
 16 return	cent;
 17 }
 18 }
 package	enums;
 2 
3 public	class	EnumConstructorExample	{
 4 public	static	void	main(String[]	args)	{
 5 for(Coin3	coin	:	Coin3.values())	{
 6 System.out.println(coin	+	"의	값은	"	+	coin.getCent());
 7 }
 8 }
 9 }

6절. ArrayList를 이용한 고객관리 프로그램

profile
인생 별거 없어

0개의 댓글