HOWTO · Java

Java에서 객체의 런타임 클래스를 확인하는 방법

Java에서 getClass(), getSimpleName(), getTypeName()으로 객체의 런타임 클래스를 확인하는 방법을 알아봅니다. 변수의 선언 타입이 아니라 객체의 런타임 클래스를 확인합니다.

이 페이지의 내용

getClass()를 사용하면 null이 아닌 객체의 런타임 클래스를 확인할 수 있으며, 변수의 선언 타입이 드러나는 것은 아닙니다. 패키지가 없는 클래스 이름은 getSimpleName()을, 패키지를 포함한 이름은 getName()을, java.lang.String과 같은 타입 이름은 getTypeName()을 호출하세요. 이름 대신 예/아니오 판정만 필요할 때는 instanceof 연산자를 사용합니다.

getClass().getSimpleName()으로 Java에서 변수의 타입 확인하기

객체에 getClass()를 호출해 Class<?> 객체를 얻은 다음, getSimpleName()을 호출하면 패키지 정보가 없는 클래스 이름을 얻을 수 있습니다. 변수가 Object로 선언되어 있어도 결과는 객체의 런타임 클래스를 반영합니다.

이 예제를 실행하세요:

public class RuntimeSimpleNames {
  public static void main(String[] args) {
    Object value = "Hello, Java!";
    String[] names = {"Ada", "Lin"};
    int count = 3;
    Object boxedCount = count;

    System.out.println(value.getClass().getSimpleName());
    System.out.println(names.getClass().getSimpleName());
    System.out.println(boxedCount.getClass().getSimpleName());
  }
}

출력:

String
String[]
Integer

배열은 객체이므로 getClass()는 배열에도 동작합니다. String[] 타입 값의 단순 이름은 String[]입니다. int 같은 기본형에는 getClass() 메서드가 없습니다. 그 이유와 박싱이 대신 무엇을 반환하는지는 아래 기본형 절에서 설명합니다.

getClass().getName()으로 Java에서 변수의 타입 확인하기

getName()은 패키지를 포함한 런타임 클래스의 전체 이름을 반환합니다. 같은 이름의 클래스를 구분하기 위해 패키지가 필요할 때 사용하세요. 다만 배열에 대해서는 읽기 쉬운 소스 구문 대신 JVM 내부 이름을 반환합니다. 앞에 오는 [는 배열의 차원을 하나씩 나타내고, L<클래스>;는 구성 요소 타입을 인코딩합니다.

이 예제를 실행하세요:

public class RuntimeName {
  public static void main(String[] args) {
    Object value = "Hello, Java!";
    String[] names = {"Ada", "Lin"};

    System.out.println(value.getClass().getName());
    System.out.println(names.getClass().getName());
  }
}

출력:

java.lang.String
[Ljava.lang.String;

두 번째 줄은 java.lang.String의 1차원 배열을 설명합니다. 이 인코딩은 읽기 어렵기 때문에 배열에는 getSimpleName()이나 getTypeName()이 더 적합합니다.

getClass().getTypeName()으로 Java에서 변수의 타입 확인하기

getTypeName()은 타입 이름을 String으로 반환합니다. String 객체의 경우 getSimpleName()과 달리 패키지 이름이 포함됩니다.

이 예제를 실행하세요:

public class RuntimeTypeName {
  public static void main(String[] args) {
    Object value = "Hello, Java!";
    String typeName = value.getClass().getTypeName();
    System.out.println(typeName);
  }
}

출력:

java.lang.String

짧은 이름이면 getSimpleName()을, 패키지를 포함한 클래스 이름이면 getName()을, 배열도 읽기 쉽게 표시되는 타입 이름이면 getTypeName()을 선택하세요. 이 중 어느 것도 변수의 선언 타입을 반환하지는 않습니다.

instanceof로 객체가 어떤 타입의 인스턴스인지 확인하기

instanceof 연산자는 이름을 반환하는 대신 예/아니오 질문에 답합니다. 이 객체가 지정한 타입 또는 그 하위 타입의 인스턴스인가? 따라서 전체 타입 계층을 따르는 반면, getClass()를 ==와 비교하면 정확히 하나의 런타임 클래스만 검사합니다.

이 예제를 실행하세요:

import java.util.ArrayList;
import java.util.List;

public class InstanceofCheck {
  public static void main(String[] args) {
    Object value = new ArrayList<String>();

    System.out.println(value instanceof ArrayList);
    System.out.println(value instanceof List);
    System.out.println(value instanceof String);
    System.out.println(value.getClass() == ArrayList.class);
    System.out.println(value.getClass() == List.class);
  }
}

출력:

true
true
false
true
false

이 객체는 ArrayList의 인스턴스이자 ArrayList가 구현하는 List 인터페이스의 인스턴스이므로 두 instanceof 검사가 모두 true를 반환합니다. 마지막 비교는 정확한 런타임 클래스가 List가 아니라 ArrayList이기 때문에 false를 반환합니다. 기준은 이렇습니다. instanceof는 하위 타입을 허용하고, getClass() 동등 비교는 정확한 클래스만 허용합니다.

instanceof 패턴 매칭으로 한 번에 검사하고 변환하기

Java 16부터 instanceof는 검사하는 타입의 패턴 변수를 선언할 수도 있습니다. 검사가 성공하면 그 변수는 이미 변환된 상태이며, 검사가 성공한 것으로 알려진 범위에서만 유효합니다. 예전 코드에 필요했던 별도의 변환이 사라집니다.

이 예제를 실행하세요:

public class InstanceofPattern {
  public static void main(String[] args) {
    Object value = "Hello, Java!";

    if (value instanceof String text) {
      System.out.println(text.toUpperCase());
    }

    Object number = 42;
    if (number instanceof String text) {
      System.out.println(text);
    } else {
      System.out.println("Not a string");
    }
  }
}

출력:

HELLO, JAVA!
Not a string

첫 번째 검사는 text를 바로 사용할 수 있는 String으로 바인딩합니다. 두 번째 검사는 박싱된 42가 Integer이기 때문에 실패하고, else 분기가 실행됩니다.

null일 수 있는 변수의 타입 확인하기

null 참조는 어떤 객체도 가리키지 않으므로 런타임 클래스가 없습니다. 여기에 getClass()를 호출하면 NullPointerException이 발생합니다. instanceof 연산자는 null에 안전한 검사로, 예외를 던지는 대신 null에 대해 단순히 false를 반환합니다.

이 예제를 실행하세요:

public class NullTypeCheck {
  public static void main(String[] args) {
    Object value = null;

    System.out.println(value instanceof String);

    try {
      System.out.println(value.getClass().getSimpleName());
    } catch (NullPointerException e) {
      System.out.println("getClass() failed: value is null");
    }
  }
}

출력:

false
getClass() failed: value is null

null일 수 있는 값에 getClass() 메서드를 호출하기 전에, 명시적인 value == null 검사나 instanceof 검사로 보호하세요.

Java에서 기본형 값의 타입 확인하기

int와 double 같은 기본형 값은 객체가 아닌 단순한 값입니다. 메서드가 없고 런타임 클래스도 없습니다. 그 타입은 컴파일 시점에 고정되며, 실행 시점에 JVM은 원시 값만 다룹니다. count.getClass()가 컴파일되지 않는 이유가 바로 이것입니다.

실행 시점에 확인하려면 래퍼 클래스로 박싱되게 하세요. 그러면 반환되는 타입은 래퍼이며, 기본형 자체가 되는 일은 없습니다. 기본형 타입 자체에 대한 클래스 리터럴은 존재합니다. int.class는 Integer.TYPE과 같은 객체이지만, 특정 값이 아니라 추상적인 타입을 가리킬 뿐입니다.

이 예제를 실행하세요:

public class PrimitiveTypeCheck {
  public static void main(String[] args) {
    int count = 3;
    double price = 9.99;

    Object boxedCount = count;
    Object boxedPrice = price;

    System.out.println(boxedCount.getClass().getSimpleName());
    System.out.println(boxedPrice.getClass().getSimpleName());
    System.out.println(int.class.getSimpleName());
    System.out.println(double.class == Double.TYPE);
  }
}

출력:

Integer
Double
int
true

요컨대 기본형의 타입은 컴파일 시점의 사실일 뿐입니다. 박싱으로 객체가 만들어지기 전까지는 실행 시점에 getClass()나 instanceof가 검사할 대상이 존재하지 않습니다.

결론

객체의 정확한 런타임 클래스 이름이 필요할 때는 getClass().getSimpleName(), getClass().getName(), getClass().getTypeName() 중 하나를 사용하세요. 어떤 하위 타입이라도 검사를 통과해야 할 때는 instanceof를 사용하고, 변환도 필요하면 Java 16 이상의 패턴 변수를 함께 쓰세요. null일 수 있는 값에는 instanceof가 안전합니다. 기본형 값에는 반환할 런타임 타입이 없습니다. 먼저 박싱하고, int에 대한 Integer 같은 래퍼 클래스가 반환될 것을 예상하세요.