[Java] static 메서드에서 제네릭 T 사용 시 컴파일 오류가 나는 이유

ichubtou·2025년 4월 17일
0

static 메서드에서 제네릭 T를 사용할 때 발생하는 컴파일 오류와 그 이유

사이드 프로젝트를 하다가 ResponseWrapper 역할을 하는 ApiResponse<T> 클래스를 만들던 중 정적 메서드를 정의하면서 아래와 같은 코드를 작성했다.

public static ApiResponse<T> of(HttpStatus httpStatus, T data) {
    return new ApiResponse<>(httpStatus, httpStatus.value(), data);
}

그런데 컴파일러는 이렇게 반응했다.

error: non-static type variable T cannot be referenced from a static context

에러 메시지 그대로 static한 문맥에서는 비정적(non-static) 타입 변수인 T를 참조할 수 없다는 얘기다.


처음엔 이해가 잘 안 갔다

public static ApiResponse<T> of(HttpStatus httpStatus, T data)

"아니 T data를 넘겼는데? 컴파일러는 그 타입이 뭔지 볼 수 있지 않나?"

라는 생각이 먼저 들었다.

그런데 조금만 더 생각해보니 이게 사실 말이 안 되는 구조였다.

static 메서드는 인스턴스가 생성되기 전에 실행 가능한데 클래스에 붙은 T는 인스턴스가 생성되어야만 어떤 타입인지 결정된다.
그런 T를 static 메서드 안에서 쓰는 건 시점적으로 모순이 생긴다.


예제로 더 구체적으로 보자

public class ApiResponse<T> {
    T data;
  
    public static ApiResponse<T> of(HttpStatus status, T data) {
        return new ApiResponse<>(status, data); // 컴파일 오류 발생
    }
}

ApiResponse는 제네릭 타입 T를 가지고 있지만 of() 메서드는 static이다.
즉 인스턴스를 만들지 않아도 호출이 가능하다.

문제는 클래스에 선언된 T는 클래스를 사용할 때(즉, 객체를 만들거나, 그 타입으로 변수를 선언할 때) 구체적인 타입이 정해진다는 점이다.

ApiResponse<T>는 클래스 전체에 제네릭 T를 적용하지만,
static 메서드는 인스턴스와 무관하게 클래스 레벨에서 동작하기 때문에 클래스에 선언된 T를 사용할 수 없다.

그래서 컴파일러는 이 메서드 안에서 T가 뭔지 알 수 없다는 것이다.

클래스에 선언된 T는 객체가 만들어져야만 사용할 수 있는데
그런데 static 메서드는 "아직 뭘 담을지도 모르는 상태"에서 실행되니까
"어떤 걸 넣어야 할지(T가 뭔지) 난 모르겠어!"라고 말하는 것이다.


해결 방법: 메서드에 제네릭 타입 <T> 직접 선언하기

public static <T> ApiResponse<T> of(HttpStatus status, T data) {
    return new ApiResponse<>(status, status.value(), data);
}

여기서 <T>는 이 메서드 안에서만 사용할 수 있는 타입 변수다.
클래스의 T와는 전혀 다른 독립적인 제네릭이다.

즉 컴파일러한테 이렇게 말하는 것이다

"이 static 메서드를 호출할 땐 T라는 타입 하나를 새로 정해서 쓸게"


어떻게 작동하나?

ApiResponse<String> response = ApiResponse.of(HttpStatus.OK, "data");

이 코드가 컴파일되는 이유는 메서드에 <T>가 선언되어 있어서 컴파일러가 "data"의 타입을 보고 T를 자동으로 추론할 수 있기 때문이다.

"즉 이 static 메서드는 호출될 때마다 새로운 T를 유추해서 쓸 수 있는 독립적인 메서드로 작동한다."


헷갈렸던 부분

public class ApiResponse<T> {
    public static <T> ApiResponse<T> of(T data) { ... }
}

"어? 클래스에도 T 있고, 메서드에도 T 있는데 같은 거 아냐?"

내 생각이 틀렸었다.
완전히 별개의 타입 변수다.

클래스의 T는 객체가 생성될 때 결정되는 제네릭

메서드의 <T>는 그 메서드 안에서만 쓰는 새로운 제네릭 변수

같은 이름을 썼을 뿐, 역할이나 범위가 완전히 다르다.

다음과 같이 이름을 다르게 쓰는 것도 가능

public class ApiResponse<C> {
    public static <M> ApiResponse<M> of(M data) {
        return new ApiResponse<>(data); // 클래스의 C와 무관
    }
}

이렇게 C는 클래스용, M은 메서드용으로 확실히 분리해서 쓰면 더 직관적으로 알 수 있다.


정리 요약

항목설명
클래스의 T인스턴스가 만들어질 때 결정됨
static 메서드인스턴스 없이 호출됨
에러 원인static 메서드에서 클래스의 T를 사용할 수 없음
해결 방법메서드 자체에 를 선언해서 새로운 타입 변수로 지정
클래스 T와 메서드 이름이 같더라도 전혀 다른 타입 변수 (스코프 다름)

느낀 점

이 경험을 통해 확실히 알게 된 건 하나다.

제네릭은 문법이 아니라 “타입이 언제 결정되는가”에 대한 감각이 중요하다.

처음엔 그냥 외워야 하나 싶었는데,
static은 인스턴스 없이 먼저 실행되고 제네릭 타입은 인스턴스가 사용되면서 타입이 결정되기 때문에
이 둘의 타이밍 차이를 깨닫는 것이 이번 오류의 본질이었다는 걸 확실히 느꼈다.

결국 이 둘을 동시에 쓰려면
메서드에서 직접 제네릭 타입을 선언해주는 것 외에는 방법이 없다.

0개의 댓글