티스토리 뷰

주의 사항!

  • 이 글은 제가 직접 공부하는 중에 작성되고 있습니다.
  • 따라서 제가 이해하는 그대로의 내용이 포함됩니다.
  • 따라서 이 글은 사실과는 다른 내용이 포함될 수 있습니다.


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

 

1. Class 객체 얻기 : getClass(), forName()

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

 

getClass() 메서드는 해당 클래스로 객체를 생성했을 때만 사용할 수 있는데, 객체를 생성하기 전에 직접 Class 객체를 얻을 수도 있습니다. Class는 생성자를 감추고 있기 때문에 new 연산자로 객체를 만들 수는 없고, 정적 메서드인 forName()을 이용해야 합니다. forName() 메서드는 클래스 전체 이름(패키지가 포함된 이름)을 매개 값으로 받고 Class 객체를 리턴합니다. Class.forName() 메서드는 매개 값으로 주어진 클래스를 찾지 못하면 ClassNotFoundException 예외를 발생시키기 때문에 예외처리가 필요합니다.

 

다음은 두 가지 방법으로 Car 클래스의 Class 객체를 얻고, Class의 메서드를 이용해 클래스의 전체 이름과 간단한 이름 그리고 패키지 이름을 얻어 출력합니다.

//Main.java
package Example;

public class Main {
	public static void main(String[] args) {
		Student s1 = new Student(11, "KOEY");
		Class clazz1 = s1.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("Example.Student");
			
			System.out.println(clazz2.getName());
			System.out.println(clazz2.getSimpleName());
			System.out.println(clazz2.getPackage().getName());
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}
}

/*
실행결과

Example.Student
Student
Example

Example.Student
Student
Example

*/

 

2. 리플렉션 : getDeclaredConstructors(), gerDeclaredFields(), getDeclaredMethods()

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

//clazz는 Class 객체 이름
Constructor[] constructors = clazz.getDeclaredConstructors();
Field[] fields = clazz.getDeclaredFields();
Method[] methods = clazz.getDeclaredMethods();

메서드 이름에서 알 수 있듯이 세 메서드는 각각 Constructor 배열, Field 배열, Method 배열을 리턴합니다. Constructor, Field, Method 클래스는 모두 java.lang.reflect 패키지에 소속되어 있습니다. getDeclaredFields(), getDeclaredMethods()는 클래스에 선언된 멤버만 가져오고 상속된 멤버는 가져오지 않습니다. 만약 상속된 멤버도 얻고 싶다면 getFields(), getMethods()를 이용해야 합니다. 단, getFields(), getMethods()는 public 멤버만 가져옵니다. 

 

다음은 Student 클래스에서 선언된 생성자, 필드, 메서드의 정보를 얻고 출력합니다.

//Student.java
package Example;

public class Student{
	public int sno;
	public String name;
	
	public Student(int sno, String name) {
		this.sno = sno;
		this.name = name;
	}
	
	@Override
	public String toString() {
		return name;
	}
}
//Main.java
package Example;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

public class Main {
	public static void main(String[] args) {
		try {
			Class clazz = Class.forName("Example.Student");
			System.out.println("[클래스 이름]");
			System.out.println(clazz.getSimpleName());
			System.out.println();
		
			System.out.println("[생성자 정보]");
			Constructor[] constructors = clazz.getDeclaredConstructors();
		
			for(Constructor constructor : constructors) {
				System.out.print(constructor.getName() + "(");
				Parameter[] parameters = constructor.getParameters();
				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.print(method.getName() + "(");
				Parameter[] parameters = method.getParameters();
				printParameters(parameters);
				System.out.println(")");
			}
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}
	
	private static void printParameters(Parameter[] parameters) {
		for(int i = 0; i < parameters.length; i++) {
			System.out.print(parameters[i].getType().getSimpleName() + " " + parameters[i].getName());
			if( i < parameters.length - 1) {
				System.out.print(", ");
			}
		}
	}
}

/*
실행결과

[클래스 이름]
Student

[생성자 정보]
Example.Student(int arg0, String arg1)

[필드 정보]
int sno
String name

[메서드 정보]
toString()

*/

 

3. 동적 객체 생성 : newInstance()

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

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

newInstance() 메서드는 기본 생성자를 호출해서 객체를 생성하기 때문에 반드시 클래스에 기본 생성자가 존재해야 합니다. 만약 매개 변수가 있는 생성자를 호출하고 싶다면 리플렉션으로 Constructor 객체를 얻어 newInstance() 메서드를 호출하면 됩니다.

 

newInstance() 메서드는 두 가지 예외가 발생할 수 있습니다. InstantiationException 예외는 해당 클래스가 추상 클래스이거나 인터페이스일 경우에 발생하고, IllegalAccessException 예외는 클래스나 생성자가 접근 제한자로 인해 접근할 수 없을 경우에 발생합니다. 따라서 예외 처리 코드가 필요합니다.

 

newInstance() 메서드의 리턴 타입은 Object이므로 이것을 원래 클래스 타입으로 변환해야만 메서드를 사용할 수 있습니다. 그렇게 하기 위해서는 강제 타입 변환을 해야 하는데, 클래스 타입을 모르는 상태이므로 변환을 할 수 없습니다. 이 문제를 해결하려면 인터페이스 사용이 필요합니다.

 

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

Class clazz = Class.forName("SendAction");
Action action = (Action) clazz.newInstace();
action.execute();    //SendAction의 execute()가 실행됨

다음 예제를 보고 이해하면 되겠습니다.

//Action.java
package Example;

public interface Action {
	public void execute();
}
//SendAction.java
package Example;

public class SendAction implements Action{
	@Override
	public void execute() {
		System.out.println("데이터를 보냅니다.");
	}
}
//ReceiveAction.java
package Example;

public class ReceiveAction implements Action{
	@Override
	public void execute() {
		System.out.println("데이터를 받습니다.");
	}
}
//Main.java
package Example;

public class Main {
	public static void main(String[] args) {
		try {
			Class clazz = Class.forName("Example.SendAction");
			Action action = (Action) clazz.newInstance();
			action.execute();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
	}
}

/*
실행결과

데이터를 보냅니다.

*/

 

'공부 일지 > JAVA 공부 일지' 카테고리의 다른 글

자바, StringTokenizer 클래스  (0) 2021.04.21
자바, String 클래스  (0) 2021.04.20
자바, System 클래스  (0) 2021.04.20
자바, Objects 클래스  (0) 2021.04.20
자바, Object 클래스  (0) 2021.04.14
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
글 보관함