제네릭(Generic)은 자바에서 클래스나 메서드를 선언할 때 사용할 데이터 타입을 나중에 지정할 수 있도록 하는 기능입니다. 이를 통해 코드의 재사용성과 타입 안정성을 높일 수 있으며, 컴파일 시 타입 검사를 가능하게 해줍니다. 예를 들어 List<String>처럼 사용할 경우, 리스트에 문자열만 담을 수 있도록 제한되어 런타임 오류를 줄일 수 있습니다. 제네릭은 다양한 타입을 처리해야 하는 클래스나 메서드에서 중복 없이 하나의 코드로 여러 타입을 다룰 수 있도록 도와줍니다.
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);
}
}
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();
}
}
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);
}
}
컬렉션 프레임워크(Collection Framework)는 자바에서 데이터를 효율적으로 저장하고 처리할 수 있도록 다양한 자료구조(리스트, 집합, 맵 등)와 알고리즘을 제공하는 표준화된 클래스 집합입니다. 이를 통해 배열보다 더 유연하고 강력한 방식으로 데이터를 추가, 삭제, 검색할 수 있으며, 제네릭을 활용해 타입 안정성도 확보할 수 있습니다. 대표적으로 List, Set, Map 인터페이스와 이를 구현한 ArrayList, HashSet, HashMap 등의 클래스가 포함되어 있으며, 컬렉션 간 변환이나 정렬, 반복 처리 등의 기능도 제공합니다.
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 "이름없음";
}
}
}
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("오렌지"));
}
}
정렬 저장 | 자동으로 오름차순 정렬 (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);
}
}
}
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() + "명");
}
}