본문 바로가기
개발/Java

[Java] Day 3 - Generic, Enum, Record 정리

by kota 2026. 9. 7.

1. Generic이란?

Generic(제네릭)은 클래스나 메서드에서 사용할 타입을 미리 정해놓지 않고, 사용하는 시점에 타입을 지정할 수 있도록 하는 기능이다.

쉽게 말하면,

"이 클래스는 String을 사용할 수도 있고 Integer를 사용할 수도 있는데, 실제 어떤 타입을 사용할지는 사용하는 쪽에서 결정하자."

라는 개념이다.

 

Generic을 사용하지 않는 경우

public class Box {

    private Object value;

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

    public Object getValue() {
        return value;
    }
}
 

사용할 때는 다음과 같다.

 
Box box = new Box();

box.setValue("Hello");

String value = (String) box.getValue();
 

여기서는 Object를 사용했기 때문에 데이터를 꺼낼 때 타입 캐스팅이 필요하다.

그리고 잘못된 타입을 넣어도 컴파일 시점에 바로 알기 어렵다.

 
Box box = new Box();

box.setValue("Hello");

Integer value = (Integer) box.getValue();
 

이 코드는 컴파일은 되지만 실행할 때 ClassCastException이 발생한다.


2. Generic을 사용하면?

Generic을 사용하면 타입을 클래스 선언 시점에 지정할 수 있다.

 
public class Box<T> {

    private T value;

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

    public T getValue() {
        return value;
    }
}
 

사용하는 쪽에서 타입을 지정한다.

 
Box<String> stringBox = new Box<>();

stringBox.setValue("Hello");

String value = stringBox.getValue();
 

Integer도 사용할 수 있다.

 
Box<Integer> integerBox = new Box<>();

integerBox.setValue(100);

Integer value = integerBox.getValue();
 

이렇게 하면 별도의 형변환이 필요 없다.

또한 잘못된 타입을 넣으면 컴파일 시점에 오류를 확인할 수 있다.

 
Box<String> box = new Box<>();

box.setValue(100); // 컴파일 오류
 

Generic의 가장 큰 장점은 결국 다음과 같다.

Generic의 장점

  • 타입 안정성
  • 불필요한 형변환 감소
  • 코드 재사용성 향상
  • 컴파일 시점에 타입 오류 확인

3. Generic Method

Generic은 클래스에만 사용할 수 있는 것이 아니다.

메서드에서도 사용할 수 있다.

 
public static <T> T getFirst(T[] array) {
    return array[0];
}
 

사용할 때 타입을 자동으로 추론할 수 있다.

 
String[] names = {"Kim", "Lee", "Park"};

String name = getFirst(names);
 
 
Integer[] numbers = {1, 2, 3};

Integer number = getFirst(numbers);
 

여기서

 
<T>
 

가 Generic Type Parameter를 의미한다.

처음 Java를 공부하면서 헷갈렸던 부분인데, T는 특정 자료형을 의미하는 것이 아니라 타입을 표현하기 위한 이름이다.

일반적으로 많이 사용하는 이름은 다음과 같다.

T = Type
E = Element
K = Key
V = Value
N = Number
 

예를 들어 List<E>에서 E는 Element를 의미하고,

 
Map<K, V>
 

에서는 K가 Key, V가 Value를 의미한다.


4. Enum이란?

Enum은 서로 관련된 상수들을 하나의 타입으로 정의하기 위한 기능이다.

예를 들어 주문 상태를 관리한다고 생각해 보자.

READY
PAID
SHIPPED
CANCELLED
 

문자열이나 숫자로 관리할 수도 있지만, Enum을 사용하는 것이 더 명확하다.

 
public enum OrderStatus {
    READY,
    PAID,
    SHIPPED,
    CANCELLED
}
 

사용할 때는 다음과 같다.

 
OrderStatus status = OrderStatus.PAID;
 

이제 status에는 OrderStatus에 정의된 값만 넣을 수 있다.

 
OrderStatus status = OrderStatus.PAID;
 

가능하지만,

 
OrderStatus status = "PAID";
 

이런 식으로 사용할 수는 없다.


5. Enum은 단순한 상수가 아니다

처음에는 Enum을 그냥 여러 개의 상수를 묶어놓은 기능이라고 생각했는데, Enum 자체가 하나의 클래스처럼 동작할 수 있다는 점이 중요하다.

예를 들어 주문 상태에 설명을 추가할 수 있다.

 
public enum OrderStatus {

    READY("주문 준비"),
    PAID("결제 완료"),
    SHIPPED("배송 중"),
    CANCELLED("주문 취소");

    private final String description;

    OrderStatus(String description) {
        this.description = description;
    }

    public String getDescription() {
        return description;
    }
}
 

사용:

 
OrderStatus status = OrderStatus.PAID;

System.out.println(status.getDescription());
 

결과:

결제 완료
 

즉 Enum은 단순히

 
READY
PAID
SHIPPED
 

를 정의하는 것에서 끝나는 것이 아니라 상태와 관련된 데이터와 로직을 함께 관리할 수 있다.

실무에서는 이런 경우에 유용하다.

  • 주문 상태
  • 결제 상태
  • 회원 등급
  • 사용자 권한
  • 서비스 타입
  • 에러 코드
  • 성별이나 국가처럼 정해진 값의 집합

6. Enum을 사용하는 이유

문자열 상수를 사용하는 방식도 가능하다.

 
String status = "PAID";
 

하지만 이런 방식은 오타에 취약하다.

 
String status = "PAID";
String status2 = "PAIDED";
String status3 = "PAY";
 

문자열이기 때문에 잘못된 값이 들어갈 수 있다.

반면 Enum을 사용하면:

 
OrderStatus status = OrderStatus.PAID;
 

정해진 값만 사용할 수 있다.

즉 Enum은 도메인에서 사용할 수 있는 값을 제한하면서 타입 안정성을 높이는 역할을 한다.


7. Record란?

Record는 데이터를 전달하기 위한 클래스를 간결하게 정의할 수 있도록 만들어진 Java의 문법이다.

예를 들어 일반 클래스로 사용자 정보를 표현하려면 다음과 같이 작성할 수 있다.

 
public class User {

    private final String name;
    private final int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}
 

데이터 전달만 하는 객체인데도 코드가 꽤 많다.

Record를 사용하면 훨씬 간단해진다.

 
public record User(String name, int age) {
}
 

이렇게 작성하면 Java가 필요한 기본적인 메서드를 제공한다.


8. Record에서 자동으로 제공되는 것

다음 Record를 기준으로 생각해 보자.

 
public record User(String name, int age) {
}
 

다음 기능이 제공된다.

생성자

 
User user = new User("Kim", 40);
 

접근 메서드

일반적인 Java Bean처럼 getName()이 아니라 컴포넌트 이름과 동일한 메서드를 사용한다.

 
String name = user.name();
int age = user.age();
 

equals()

같은 값을 가진 Record인지 비교할 수 있다.

hashCode()

값을 기반으로 hash 값을 계산한다.

toString()

객체의 내용을 문자열로 확인할 수 있다.

 
System.out.println(user);
 

예:

User[name=Kim, age=40]
 

9. Record는 불변 데이터 객체에 적합하다

Record의 중요한 특징 중 하나는 선언된 컴포넌트가 final이라는 것이다.

다음과 같이 값을 변경할 수 없다.

 
User user = new User("Kim", 40);

// user.name = "Lee"; // 불가능
 

그래서 Record는 다음처럼 한 번 생성한 뒤 변경하지 않는 데이터를 표현할 때 잘 어울린다.

  • API Request/Response DTO
  • 조회 결과
  • 설정 값
  • 좌표
  • 검색 조건
  • 간단한 데이터 전달 객체

예를 들어 API 응답 DTO라면:

 
public record UserResponse(
        Long id,
        String name,
        int age
) {
}
 

기존 DTO 클래스보다 상당히 간결하게 표현할 수 있다.


10. Record의 주의점

Record라고 해서 객체 내부의 모든 데이터가 완전히 불변이 되는 것은 아니다.

예를 들어 List를 가지고 있다면:

 
public record User(List<String> hobbies) {
}
 

Record 자체의 hobbies 참조는 변경할 수 없지만, 전달된 List 자체가 수정 가능한 객체라면 내부 값은 변경될 수 있다.

 
List<String> hobbies = new ArrayList<>();
hobbies.add("Basketball");

User user = new User(hobbies);

hobbies.add("Movie");
 

이 경우 user.hobbies()에서도 변경된 내용을 볼 수 있다.

따라서 Record를 완전한 deep immutable 객체라고 생각하면 안 된다.


11. Generic + Enum + Record를 비교해 보면

오늘 공부한 세 가지를 정리하면 다음과 같다.

기능목적
Generic 타입을 안전하게 처리하고 코드 재사용
Enum 제한된 값들을 하나의 타입으로 표현
Record 데이터를 간결하게 표현

각각의 목적이 다르다.

 

Generic

List<String>
List<Integer>
 

"어떤 타입의 데이터를 다룰 것인가?"

 

Enum

OrderStatus.PAID
OrderStatus.CANCELLED
 

"어떤 값만 허용할 것인가?"

 

Record

UserResponse(Long id, String name)
 

"데이터를 어떻게 간결하게 전달할 것인가?"


12. PHP 개발자 입장에서 느낀 점

PHP/Laravel을 주로 개발하다가 Java를 공부하면서 느끼는 가장 큰 차이 중 하나는 타입을 코드 구조에서 상당히 중요하게 다룬다는 점이다.

PHP에서는 상대적으로 유연하게 데이터를 처리할 수 있지만, Java에서는 Generic, Enum 등을 이용해서 컴파일 단계에서 잘못된 데이터가 들어오는 것을 막는 구조를 만들 수 있다.

예를 들어 PHP에서는 상태값을 문자열로 관리하는 경우가 흔하지만,

$status = 'PAID';
 

Java에서는

OrderStatus status = OrderStatus.PAID;
 

처럼 도메인에서 허용하는 값을 타입 자체에 표현할 수 있다.

또한 Record를 사용하면 DTO를 간결하게 표현할 수 있어서, API 중심의 백엔드 개발에서도 유용할 것 같다.


13. 오늘 공부하면서 정리한 핵심

이번 학습에서 가장 중요하다고 느낀 부분은 Java가 단순히 타입을 엄격하게 검사하는 언어가 아니라, 타입 자체를 이용해서 코드의 의도를 표현할 수 있다는 점이다.

Generic은 타입 안정성과 재사용성을 높이고,

Enum은 허용 가능한 값의 범위를 명확하게 만들고,

Record는 데이터 전달 객체를 간결하게 표현할 수 있다.

결국 세 가지 모두 코드의 의도를 명확하게 표현하고 잘못된 사용을 줄이는 데 도움이 되는 기능이라고 이해했다.


오늘의 한 줄 정리

Generic은 타입을 안전하게 다루고, Enum은 정해진 값을 표현하며, Record는 데이터를 간결하게 표현한다.

반응형

'개발 > Java' 카테고리의 다른 글

[Java] Day 8 - Exception  (0) 2026.09.14
[Java] Day 7 - Stream API 정리  (0) 2026.09.12
[Java] Day 6 - Lambda + Functional Interface  (0) 2026.09.11
[Java] Day 5 - HashMap + equals/hashCode  (0) 2026.09.10
[Java] Day 4 - Collection Framework  (0) 2026.09.09

댓글