본문 바로가기

Java 길찾기/이것이 자바다

[Java] 기본 API 클래스 - Class

자바는 클래스와 인터페이스의 메타 데이터를 java.lang 패키지에 소속된 Class 클래스로 관리한다. 여기서 메타 데이터란 클래스의 이름, 생성자 정보, 필드 정보, 메소드 정보를 말한다.

 

Class 객체 얻기(getClass(), forName())

프로그램에서 Class 객체를 얻기 위해서는 Object 클래스가 가지고 있는 getClass() 메소드를 이용하면 된다. Object는 모든 클래스의 최상위 클래스이므로 모든 클래스에서 getClass() 메소드를 호출할 수 있따.

Class Clazz = obj.getClass();

 

getClass() 메소드는 해당 클래스로 객체를 생성했을 때만 사용할 수 있는데, 객체를 생성하기 전에 직접 Class 객체를 얻을 수도 있다. Class는 생성자를 감추고 있기 때문에 new 연산자로 객체를 만들 수 없고, 정적 메소드인 forName()을 이용해야 한다. forName() 메소드는 클래스 전체 이름(패키지가 포함된 이름)을 매개값으로 받고 Class 객체를 리턴한다.

try {
    Class clazz = Class.forName(String className);
}
catch(ClassNotFoundException e) {
}

 

Class.forName() 메소드는 매개값으로 주어진 클래스를 찾지 못하면 ClassNotFoundException 예외를 발생시키기 때문에 예외 처리가 필요하다. 다음은 두 가지 방법으로 Car 클래스의 Class 객체를 얻고, Class의 메소드를 이용해 클래스의 전체 이름과 간단한 이름 그리고 패키지 이름을 얻어 출력한다.

// ClassExample.java
public class ClassExample {
    public static void main(String[] args) {
        Car car = new Car();
        Class clazz1 = car.getClass();
        System.out.println(clazz1.getName());
        System.out.println(clazz1.getSimpleName());
        System.out.println(clazz1.getPackage().getName());
        System.out.println();
        
        try {
            Class clazz2 = Class.forName("sec06.exam01_class.Car");
            System.out.println(clazz2.getName());
            System.out.println(clazz2.getSimpleName());
            System.out.println(clazz2.getPackage().getName());
        }
        catch(ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

 

리플렉션(getDeclaredConstructors(), getDeclaredFields(), getDeclaredMethods())

Class 객체를 이용하면 클래스의 생성자, 필드, 메소드 정보를 알아낼 수 있다. 이것을 리플렉션(Reflection)이라고 한다. Class 객체는 리플렉션을 위해 getDeclaredConstructors(), getDeclaredFields(), getDeclaredMethods()를 제공하고 있다.

Constructor[] constructors = clazz.getDeclaredConstructors();
Field[] fields = clazz.getDeclaredFields();
Method[] methods = clazz.getDeclaredMethods();

메소드 이름에서 알 수 있듯이 세 메소드는 각각 Consturctor 배열, Field 배열, Method 배열을 리턴한다. Constructor, Field, Method 클래스는 모두 java.lang.reflect 패키지에 소속되어 있다. getDeclaredFields(), getDeclaredMethods()는 클래스에 선언된 멤버만 가져오고 상속된 멤버는 가져오지 않는다. 만약 상속된 멤버도 얻고 싶다면 getFields(), getMethods()를 이용해야 한다. 단 getFields(), getMethods()는 public 멤버만 가져온다. 다음은 Car 클래스에서 선언된 생성자, 필드 메소드의 정보를 얻고 출력한다.

// ReflectionExample.java
public class ReflectionExample {
    public static void main(String[] args) throws Exception {
        Class clazz = Class.forName("sec06.exam02_reflection.car");
        
        System.out.println("[클래스 이름]");
        System.out.println(clazz.getName());
        System.out.println();
        
        System.out.println("[생성자 정보]");
        Constructor[] constructors = clazz.getDeclaredConstructors();
        for(Constructor constructor : constructors) {
            System.out.print(constructor.getName() + "(");
            Class[] parameters = constructor.getParameterTypes();
            printParameters(parameters);
            System.out.println(")");
        }
        System.out.println();
        
        System.out.println("[필드 정보]");
        Field[] fields = clazz.getDeclaredFields();
        for(Field field : fields) {
            System.out.println(field.getType().getSimpleName() + " " + field.getName());
        }
        System.out.println();
        
        System.out.println("[메소드 정보]");
        Method[] methods = clazz.getDeclaredMethods();
        for(Method method : methods) {
            System.out.println(method.getName() + "(");
            Class[] parameters = method.getParameterTypes();
            printParameters(parameters);
            System.out.println(")");
        }
    }
    
    private static void printParameters(Class[] parameters) {
        for(int i=0; i<parameters.length; i++) {
            System.out.println(parameters[i].getName());
            if(i<(parameters.length-1)) {
                System.out.println(",");
            }
        }
    }
}

실행 결과는 Car 클래스가 자체적으로 가지고 있는 전체 멤버들이 모두 출력된다.

 

동적 객체 생성(newInstatnce())

Class 객체를 이용하면 new 연산자를 사용하지 않아도 동적으로 객체를 생성할 수 있다. 이 방법은 코드 작성 시에 클래스 이름을 결정할 수 없고, 런타임 시에 클래스 이름이 결정되는 경우에 매우 유용하게 사용된다. 다음 코드처럼 Class.forName() 메소드로 Class 객체를 얻을 다음 newInstance() 메소드를 호출하면 Object 타입의 객체를 얻을 수 있다.

try {
    Class clazz = Class.forName("런타임 시 결정되는 클래스 이름");
    Object obj = clazz.newInstance();
} catch(ClassNotFoundException e) {
} catch(InstanctiationException e) {
} catch(IllegalAccessException e) {
}

 

newInstance() 메소드는 기본 생성자를 호출해서 객체를 생성하기 때문에 반드시 클래스에 기본 생성자가 존재해야 한다. 만약 매개 변수가 있는 생성자를 호출하고 싶다면 리플렉션으로 Constructor 객체를 얻어 newInstance() 메소드를 호출하면 된다. newInstance() 메소드는 두 가지 예외가 발생할 수 있는데, InstantiationException 예외는 해당 클래스가 추상 클래스이거나 인터페이스일 경우에 발생하고, IllegalAccessException 예외는 클래스나 생성자가 접근 제한자로 인해 접근할 수 없을 경우에 발생한다. 따라서 예외 처리 코드가 필요하다.

 

newInstance() 메소드의 리턴 타입은 Object이므로 이것을 원래 클래스 타입으로 변환해야만 메소드를 사용할 수 있다. 그렇게 하기 위해서는 강제 타입 변환을 해야 하는데, 클래스 타입을 모르는 상태이므로 변환을 할 수가 없다. 이 문제를 해결하려면 인터페이스 사용이 필요하다. 예를들어 Action 인터페이스와 구현 클래스인 SencAction, ReceiveAction이 있다고 가정해보자.

Class.forName() 메소드의 매개값으로 "SendAction" 또는 "ReceiveAction"을 주면 Class 객체가 만들어지고, Class 객체의 newInstance() 메소드로 Object 객체를 얻을 수 있다. 얻어진 객체는 모두 Action 인터페이스를 구현하고 있기 때문에 다음과 같이 Action 인터페이스 타입으로 변환이 가능하다. 그런 다음, Action 인터페이스의 execute() 메소드를 호출하면, 개별 클래스의 실체 메소드인 execute() 메소드가 실행된다.

Class clazz = Class.forName("SendAction" 또는 "ReceiveAction");
Action action = (Action) clazz.newInstance();
action.execute();

 

다음 예제를 작성하고 설명한 내용을 확인해보자.

// Action.java - 인터페이스
public interface Action {
    public void execute();
}
// SendAction.java -- 발신 클래스
public class SendAction implements Action {
    @Override
    public void execute() {
        System.out.println("데이터를 보냅니다.");
    }
}
// ReceiveAction.java -- 수신 클래스
public class ReceiveAction implements Action {
    @Override
    public void execute() {
        System.out.println("데이터를 받습니다.");
    }
}
// NewInstanceExample.java -- 동적 객체 생성 및 실행
public class NewInstanceExample {
    public static void main(String[] args) {
        try {
            Class clazz = Class.forName("sec06.exam03_newInstance.SendAction");  // 둘 중에 하나는
            Class clazz = Class.forName("sec06.exam03_newInstance.SendAction");  // 주석처리해서 사용
            Action action = (Action) clazz.newInstance();
            action.execute();
        } catch(ClassNotFoundException e) {
            e.printStackTrace();
        } catch(InstantiationException e) {
            e.printStackTrace();
        } catch(IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}