상세 컨텐츠

본문 제목

제네릭

백엔드/Java

by Ryuzy 2025. 5. 23. 11:21

본문

728x90
반응형

1. 제네릭

제네릭(Generic)은 자바에서 클래스나 메서드를 선언할 때 사용할 데이터 타입을 나중에 지정할 수 있도록 하는 기능입니다. 이를 통해 코드의 재사용성과 타입 안정성을 높일 수 있으며, 컴파일 시 타입 검사를 가능하게 해줍니다. 예를 들어 List<String>처럼 사용할 경우, 리스트에 문자열만 담을 수 있도록 제한되어 런타임 오류를 줄일 수 있습니다. 제네릭은 다양한 타입을 처리해야 하는 클래스나 메서드에서 중복 없이 하나의 코드로 여러 타입을 다룰 수 있도록 도와줍니다.

 

1. 제네릭을 사용하는 이유

  • 타입 안정성
    → 컴파일 시점에 타입을 검사하여 오류를 줄일 수 있습니다.
  • 형변환 제거
    → 제네릭을 사용하면 Object로 저장 후 꺼낼 때 형변환을 하지 않아도 됩니다.
  • 코드 재사용성 증가
    → 다양한 타입에 대해 같은 로직을 사용할 수 있습니다.
class 클래스이름<T> {
    // T는 타입 파라미터 (예: String, Integer 등)
    private T 변수;

    public void set(T 변수) {
        this.변수 = 변수;
    }

    public T get() {
        return 변수;
    }
}

 

T는 타입 변수(Type Parameter)이며, 어떤 타입이 들어올지 모를 때 사용합니다. 보통 T, E, K, V 등 대문자로 표현합니다.

 

제네릭을 사용하지 않은 경우

Object를 사용하면 다양한 타입을 넣을 수 있지만, 꺼낼 때 형변환(casting)이 필요하며, 실행 중(ClassCastException) 오류가 날 수 있습니다.

class ObjectBox {
    private Object item;

    public void set(Object item) {
        this.item = item;
    }

    public Object get() {
        return item;
    }
}

public class NoGenericExample {
    public static void main(String[] args) {
        ObjectBox box = new ObjectBox();

        box.set("사과");  // 문자열 저장
        Object obj = box.get();

        // 강제 형변환 필요
        String fruit = (String) obj;
        System.out.println("과일: " + fruit);

        box.set(123);  // 정수도 저장 가능 (타입 제한 없음)
        // 잘못된 형변환 시 에러 발생
        // String error = (String) box.get();  // 런타임 오류 발생
    }
}

 

제네릭을 사용한 경우

제네릭을 사용하면 저장할 때 타입을 제한할 수 있고, 꺼낼 때 형변환이 필요 없습니다.

// 제네릭 Box 클래스
class Box<T> {
    private T item;

    public void set(T item) {
        this.item = item;
    }

    public T get() {
        return item;
    }
}

public class GenericExample {
    public static void main(String[] args) {
        Box<String> stringBox = new Box<>();
        stringBox.set("바나나");  // String 타입만 저장 가능
        String fruit = stringBox.get();  // 형변환 없이 꺼냄
        System.out.println("과일: " + fruit);

        Box<Integer> intBox = new Box<>();
        intBox.set(100);
        int number = intBox.get();  // 형변환 없이 꺼냄
        System.out.println("숫자: " + number);
    }
}

 

2. 제네릭 클래스

class Person<T, U> {
    private T name;
    private U age;

    public Person(T name, U age) {
        this.name = name;
        this.age = age;
    }

    public void printInfo() {
        System.out.println("이름: " + name + ", 나이: " + age);
    }
}

public class Main {
    public static void main(String[] args) {
        Person<String, Integer> p1 = new Person<>("김사과", 20);
        p1.printInfo();

        Person<String, Double> p2 = new Person<>("반하나", 23.5);
        p2.printInfo();
    }
}

 

3. 제네릭 메서드

class Printer {
    public <T> void print(T item) {
        System.out.println("출력: " + item);
    }
}

public class Main {
    public static void main(String[] args) {
        Printer printer = new Printer();

        printer.print("안녕");
        printer.print(123);
        printer.print(3.14);
        printer.print(true);
    }
}

 

 

2. 컬렉션 프레임워크

컬렉션 프레임워크(Collection Framework)는 자바에서 데이터를 효율적으로 저장하고 처리할 수 있도록 다양한 자료구조(리스트, 집합, 맵 등)와 알고리즘을 제공하는 표준화된 클래스 집합입니다. 이를 통해 배열보다 더 유연하고 강력한 방식으로 데이터를 추가, 삭제, 검색할 수 있으며, 제네릭을 활용해 타입 안정성도 확보할 수 있습니다. 대표적으로 List, Set, Map 인터페이스와 이를 구현한 ArrayList, HashSet, HashMap 등의 클래스가 포함되어 있으며, 컬렉션 간 변환이나 정렬, 반복 처리 등의 기능도 제공합니다.

 

1. List 계열

  • 순서(인덱스)를 유지하며 저장
  • 중복된 요소 허용
  • 요소를 인덱스로 접근 가능 (배열처럼 list.get(0) 형태)
ArrayList 내부적으로 배열 사용, 빠른 검색, 느린 삽입/삭제 (특히 중간 삽입)
LinkedList 이중 연결 리스트 구조, 빠른 삽입/삭제, 느린 검색
Vector ArrayList와 유사하지만 동기화 처리 되어 멀티스레드에 적합
Stack Vector를 상속한 후 LIFO 구조를 따르는 클래스 (push/pop 지원)

 

import java.util.ArrayList;

public class StudentListExample {
    public static void main(String[] args) {
        // 1. ArrayList 생성
        ArrayList<String> students = new ArrayList<>();

        // 2. 요소 추가
        students.add("김사과");
        students.add("반하나");
        students.add("오렌지");

        // 3. 요소 삽입 (중간에 추가)
        students.add(1, "이메론");  // 1번 인덱스에 삽입 → 자동으로 뒤로 밀림

        // 4. 요소 출력
        System.out.println("전체 학생 목록:");
        for (String name : students) {
            System.out.println("- " + name);
        }

        // 5. 요소 검색
        String student = students.get(2);  // 인덱스로 접근
        System.out.println("\n2번 인덱스의 학생: " + student);

        // 6. 요소 수정
        students.set(2, "배애리");
        System.out.println("\n수정 후 2번 인덱스: " + students.get(2));

        // 7. 요소 삭제
        students.remove("이메론");         // 이름으로 삭제
        students.remove(0);               // 인덱스로 삭제

        // 8. 크기 확인
        System.out.println("\n현재 학생 수: " + students.size());

        // 9. 최종 목록 출력
        System.out.println("\n최종 학생 목록:");
        for (int i = 0; i < students.size(); i++) {
            System.out.println(i + ": " + students.get(i));
        }
    }
}

 

import java.util.LinkedList;

public class HospitalQueue {
    public static void main(String[] args) {
        // 1. LinkedList 생성 (문자열 타입)
        LinkedList<String> queue = new LinkedList<>();

        // 2. 환자 도착 (뒤에 추가)
        queue.add("김사과");
        queue.add("반하나");
        queue.add("오렌지");

        // 3. 긴급 환자 도착 (앞에 추가)
        queue.addFirst("이메론");  // 가장 앞에 삽입

        // 4. 대기열 출력
        System.out.println("현재 대기열:");
        for (String name : queue) {
            System.out.println("- " + name);
        }

        // 5. 진료 (앞에서 한 명씩 제거)
        System.out.println("\n진료 시작:");
        while (!queue.isEmpty()) {
            String patient = queue.removeFirst();  // 맨 앞 제거
            System.out.println("진료 중: " + patient);
        }

        // 6. 대기열 확인
        System.out.println("\n진료 완료. 남은 환자 수: " + queue.size());
    }
}

 

import java.util.Vector;

public class StudentScoreManager {
    public static void main(String[] args) {
        // 1. Vector 생성 (Integer 타입으로 학생 점수 저장)
        Vector<Integer> scores = new Vector<>();

        // 2. 점수 추가
        scores.add(90);  // 김사과
        scores.add(85);  // 반하나
        scores.add(78);  // 오렌지
        scores.add(92);  // 이메론
        scores.add(88);  // 배애리

        // 3. 전체 점수 출력
        System.out.println("전체 학생 점수:");
        for (int i = 0; i < scores.size(); i++) {
            System.out.println("- " + getName(i) + ": " + scores.get(i) + "점");
        }

        // 4. 평균 계산
        int sum = 0;
        for (int score : scores) {
            sum += score;
        }
        double average = (double) sum / scores.size();
        System.out.println("\n전체 평균 점수: " + average);

        // 5. 점수 수정 (오렌지의 점수 수정)
        scores.set(2, 80);  // 인덱스 2번 오렌지
        System.out.println("\n오렌지 점수 수정 후: " + scores.get(2) + "점");

        // 6. 마지막 학생 삭제 (배애리)
        scores.remove(scores.size() - 1);
        System.out.println("\n마지막 학생(배애리) 삭제 후 학생 수: " + scores.size());
    }

    // 인덱스에 따른 이름 반환
    public static String getName(int index) {
        switch (index) {
            case 0: return "김사과";
            case 1: return "반하나";
            case 2: return "오렌지";
            case 3: return "이메론";
            case 4: return "배애리";
            default: return "이름없음";
        }
    }
}

 

2. Set 계열

  • 중복을 허용하지 않음
  • 순서가 없음 (일반적으로 입력 순서를 보장하지 않음)
  • 하나의 집합(수학의 Set 개념과 유사)
HashSet 순서를 유지하지 않음, 가장 일반적인 Set
LinkedHashSet 입력 순서를 유지
TreeSet 자동 정렬됨 (오름차순), 내부적으로 트리 구조 사용
import java.util.HashSet;

public class AttendanceChecker {
    public static void main(String[] args) {
        // 1. HashSet 생성
        HashSet<String> attendance = new HashSet<>();

        // 2. 출석 체크
        attendance.add("김사과");
        attendance.add("반하나");
        attendance.add("오렌지");
        attendance.add("이메론");
        attendance.add("배애리");

        // 중복 출석 시도
        attendance.add("김사과");
        attendance.add("반하나");

        // 3. 전체 명단 출력
        System.out.println("📋 출석한 학생 명단:");
        for (String name : attendance) {
            System.out.println("- " + name);
        }

        // 4. 특정 학생 출석 여부 확인
        System.out.println("\n🔍 '오렌지' 출석 여부: " + attendance.contains("오렌지"));
        System.out.println("🔍 '박수박' 출석 여부: " + attendance.contains("박수박"));

        // 5. 학생 삭제
        attendance.remove("이메론");
        System.out.println("\n🗑️ '이메론' 삭제 후 출석 명단:");
        for (String name : attendance) {
            System.out.println("- " + name);
        }
    }
}

 

import java.util.TreeSet;

public class SortedStudentSet {
    public static void main(String[] args) {
        // 1. TreeSet 생성 (문자열 타입)
        TreeSet<String> students = new TreeSet<>();

        // 2. 학생 이름 추가
        students.add("김사과");
        students.add("반하나");
        students.add("오렌지");
        students.add("이메론");
        students.add("배애리");

        // 3. 중복 추가 시도
        students.add("김사과");  // 무시됨

        // 4. 자동 정렬된 학생 목록 출력
        System.out.println("📚 이름순으로 정렬된 학생 명단:");
        for (String name : students) {
            System.out.println("- " + name);
        }

        // 5. 특정 이름보다 작은 값, 큰 값 찾기
        System.out.println("\n🧐 '오렌지'보다 앞에 오는 이름: " + students.lower("오렌지"));
        System.out.println("🧐 '오렌지'보다 뒤에 오는 이름: " + students.higher("오렌지"));
    }
}

 

TreeSet 특징

정렬 저장 자동으로 오름차순 정렬 (String, Integer 등 Comparable 필요)
중복 허용 ❌ 허용하지 않음
탐색 기능 lower(), higher(), floor(), ceiling() 등
성능 이진 탐색 트리 기반 → 삽입, 삭제, 검색 모두 O(log n)
정렬 기준 변경 Comparator를 생성자에 전달하여 커스텀 정렬 가능
class Student {
    String name;
    int score;

    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }

    @Override
    public String toString() {
        return name + " (" + score + "점)";
    }
}

 

import java.util.TreeSet;

class Student implements Comparable<Student> {
    String name;
    int score;

    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }

    // 기본 정렬 기준: 점수 오름차순
    @Override
    public int compareTo(Student other) {
        return Integer.compare(this.score, other.score);
    }

    @Override
    public String toString() {
        return name + " (" + score + "점)";
    }
}

public class TreeSetComparableExample {
    public static void main(String[] args) {
        TreeSet<Student> set = new TreeSet<>();
        set.add(new Student("김사과", 90));
        set.add(new Student("반하나", 85));
        set.add(new Student("오렌지", 95));
        set.add(new Student("이메론", 92));
        set.add(new Student("배애리", 85));  // 같은 점수 → 중복 간주 안되지만 순서 주의

        System.out.println("🏅 점수 오름차순 정렬:");
        for (Student s : set) {
            System.out.println("- " + s);
        }
    }
}

 

import java.util.TreeSet;
import java.util.Comparator;

class Student {
    String name;
    int score;

    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }

    @Override
    public String toString() {
        return name + " (" + score + "점)";
    }
}

public class TreeSetComparatorExample {
    public static void main(String[] args) {
        // 이름 기준 내림차순 정렬 Comparator 정의
        Comparator<Student> nameDesc = (a, b) -> b.name.compareTo(a.name);

        TreeSet<Student> set = new TreeSet<>(nameDesc);
        set.add(new Student("김사과", 90));
        set.add(new Student("반하나", 85));
        set.add(new Student("오렌지", 95));
        set.add(new Student("이메론", 92));
        set.add(new Student("배애리", 85));

        System.out.println("🔤 이름 내림차순 정렬:");
        for (Student s : set) {
            System.out.println("- " + s);
        }
    }
}

 

3. Map 계열

  • 데이터를 Key-Value 쌍으로 저장
  • Key는 중복될 수 없음, Value는 중복 가능
  • 값을 검색할 때는 key를 사용 (map.get("name"))
HashMap 가장 많이 사용됨. 키 순서 없음
LinkedHashMap 입력 순서 유지
TreeMap 키 기준 정렬 (Comparable 또는 Comparator 필요)
Hashtable HashMap과 유사하나, 동기화 지원, null 키 불가

 

import java.util.HashMap;
import java.util.Map;

public class StudentScoreMap {
    public static void main(String[] args) {
        // 1. HashMap 생성 (이름: 점수)
        HashMap<String, Integer> scoreMap = new HashMap<>();

        // 2. 학생 점수 저장
        scoreMap.put("김사과", 90);
        scoreMap.put("반하나", 85);
        scoreMap.put("오렌지", 95);
        scoreMap.put("이메론", 88);
        scoreMap.put("배애리", 92);

        // 3. 전체 학생 점수 출력
        System.out.println("📋 전체 학생 점수:");
        for (Map.Entry<String, Integer> entry : scoreMap.entrySet()) {
            System.out.println("- " + entry.getKey() + ": " + entry.getValue() + "점");
        }

        // 4. 특정 학생 점수 조회
        String name = "오렌지";
        System.out.println("\n🔍 " + name + "의 점수: " + scoreMap.get(name) + "점");

        // 5. 점수 수정
        scoreMap.put("김사과", 95);  // 기존 값 덮어쓰기
        System.out.println("\n✏️ 김사과의 점수 수정 후: " + scoreMap.get("김사과") + "점");

        // 6. 학생 삭제
        scoreMap.remove("반하나");
        System.out.println("\n🗑️ 반하나 삭제 후 학생 목록:");
        for (String key : scoreMap.keySet()) {
            System.out.println("- " + key + ": " + scoreMap.get(key) + "점");
        }

        // 7. 전체 학생 수 출력
        System.out.println("\n👥 전체 학생 수: " + scoreMap.size() + "명");
    }
}

 

 

728x90
반응형

'백엔드 > Java' 카테고리의 다른 글

람다식  (1) 2025.05.25
DTO와 VO  (0) 2025.05.23
java.lang 패키지  (0) 2025.05.21
예외처리  (0) 2025.05.20
인터페이스  (1) 2025.05.20

관련글 더보기