Reflection이란?
Reflection은 자바 언어의 기능중 하나로 프로그램 내부 속성을 조작 할 수 있게 합니다. 예를 들어 Java클래스가 가지고 있는 모든 멤버의 이름을 가져와서 표시 할 수 있습니다.
다른 언어에서는 이러한 기능이 보통 존재하지 않습니다. 예를 들어 파스칼, C,C++ 등의 언어에서 정의 된 함수에 대한 정보를 얻는 방법은 없습니다.
간단한 리플렉션 예제
String 클래스의 풀패스를 통해 String 이 가지고 있는 모든 메소드를 출력하는 간단한 예제입니다.
try { Class c = Class.forName("java.lang.String"); Method m[] = c.getDeclaredMethods(); for (int i = 0; i < m.length; i++) System.out.println(m[i].toString()); } catch (Throwable e) { System.err.println(e); }
리플렉션을 사용하기 위한 3가지 스텝
첫째. 클래스 Class객체를 얻는다. 예를들면 다음과 같은 방법으로 클래스 객체를 얻을 수 있습니다.
Class c1 = Class.forName("java.lang.String"); Class c2 = int.class; Class c3 = Integer.TYPE;
둘째. getDeclaredMethods() 와 같은 메소드를 호출 한다. 클래스내에 정의된 메소드들을 모두 가져올 수 있다.
셋째. 리플렉션 API를 사용하여 정보를 조작하자.
Class c = Class.forName("java.lang.String"); Method m[] = c.getDeclaredMethods(); System.out.println(m[0].toString());
위의 예제는 첫 번째 메소드의 이름을 출력하게 됩니다.
instanceof 연산자 모의실험 해보기
클래스정보를 손에 쥐고 나면 클래스 객체에 대해 몇가지 궁금한 것을 물어볼 수 있습니다. 예를들면 Class.isInstance 메소드는 instanceof 연산자를 시뮬레이팅 할 수 있습니다.
Class cls = Class.forName("java.lang.String"); boolean b1 = cls.isInstance(3); Log.e(TAG, "b1="+b1);//b1=false boolean b2 = cls.isInstance("Test"); Log.e(TAG, "b2="+b2);//b2=true
클래스의 메소드 찾기
리플렉션의 가장 기본적이고 가장 중요한 사용법중 하나가 바로 클래스에 정의된 메소드 찾기 입니다. 메소드를 찾는것 뿐만 아니라 메소드가 가지고 있는 파라미터 타입, Exception타입 , 반환 타입 등을 알아 낼 수 있습니다.
try { Class cls = Class.forName("java.lang.String"); Method methods[] = cls.getDeclaredMethods(); for (int i = 0; i < methods.length; i++) { Method m = methods[i]; Log.e(TAG, "메소드 이름 = " + m.getName()); Log.e(TAG, "정의된 클래스이름 = " + m.getDeclaringClass()); Class pvec[] = m.getParameterTypes(); for (int j = 0; j < pvec.length; j++) { Log.e(TAG, "인자 #" + j + " " + pvec[j]); } Class evec[] = m.getExceptionTypes(); for (int j = 0; j < evec.length; j++) { Log.e(TAG, "익셉션 #" + j + " " + evec[j]); } Log.e(TAG,"return type = " + m.getReturnType()); Log.e(TAG,"-----"); } } catch (Throwable e) { Log.e(TAG,e.toString()); }
생성자에 대한 정보 얻기
메소드 찾는 방법과 비슷합니다. 예제를 확인해보겠습니다.
try { Class cls = Class.forName("java.lang.String"); Constructor ctorlist[] = cls.getDeclaredConstructors(); for (int i = 0; i < ctorlist.length; i++) { Constructor ct = ctorlist[i]; Log.e(TAG,"생성자 이름 = " + ct.getName()); Log.e(TAG,"정의된 클래스이름 = " + ct.getDeclaringClass()); Class pvec[] = ct.getParameterTypes(); for (int j = 0; j < pvec.length; j++){ Log.e(TAG,"param #" + j + " " + pvec[j]);//생성자 파라미터 } Class evec[] = ct.getExceptionTypes(); for (int j = 0; j < evec.length; j++){ Log.e(TAG,"exc #" + j + " " + evec[j]);//익셉션 타입 } Log.e(TAG,"-----"); } } catch (Throwable e) { Log.e(TAG,e.toString()); }
생성자는 반환 타입이 없기 때문에 반환타입은 뺐습니다.
클래스 필드 찾기
클래스에 정의된 데이터 필드 또한 찾는게 가능합니다. 예제를 확인 하겠습니다.
try { Class cls = Class.forName("java.lang.String"); Field fieldlist[] = cls.getDeclaredFields(); for (int i = 0; i < fieldlist.length; i++) { Field fld = fieldlist[i]; Log.e(TAG,"필드명 = " + fld.getName()); Log.e(TAG,"정의된클래스 = " + fld.getDeclaringClass()); Log.e(TAG,"필드타입 = " + fld.getType()); int mod = fld.getModifiers(); Log.e(TAG,"접근제어자 = " + Modifier.toString(mod)); Log.e(TAG,"-----"); } } catch (Throwable e) { Log.e(TAG,e.toString()); }
이전 예제와 비슷하지만 한가지 새로운 점은 접근제어자(modifier)를 알수 있다는 점입니다. 한가지 재밌는 점은 private 필드도 찾을 수 있습니다.
메소드 이름으로 실행하기
지금까지의 예제는 클래스 정보와 관련된것을 찾거나 표현했다면, 이번에는 조금 다른 방법으로 리플렉션을 사용해보겠습니다. 예를 들면 메소드이름으로 특정 메소드를 실행하는 것입니다.
try { Class cls = Class.forName("java.lang.String"); String data = "Hello World";//테스트용 데이터 Method lengthMethod = cls.getMethod("length");//length()메소드를 찾는다. int length = (int) lengthMethod.invoke(data);//data.length() 수행 Log.e(TAG, "length=" + length); //length=11 출력 Method substringMethod = cls.getMethod("substring", int.class, int.class); //두개의 int타입이 있는 substring메소드를 가져옵니다. String subStr = (String) substringMethod.invoke(data,0, 5); //data.substring(0,5)와 같은 효과 Log.e(TAG,"subStr="+subStr);//Hello 출력 } catch (Throwable e) { Log.e(TAG, e.toString()); }
그럼 한가지 의문점이 생길수 있습니다.
private한 메소드도 찾는데 invoke는 못하나?
가능합니다.
public class A { public static final String TAG = A.class.getSimpleName(); private void show() { Log.e(TAG, "Hello I am private method"); } }
try { A a = new A(); //메소드가 private하여 a.show() 찾을 수가 없음 Method showMethod = a.getClass().getDeclaredMethod("show"); showMethod.setAccessible(true); //접근 가능! showMethod.invoke(a); }catch (Exception e){ Log.e(TAG,e.toString()); }
A라는 클래스에서 private한 메소드 show()를 선언하면 다른 클래스에서는 이 클래스로 만든 인스턴스로는 메소드에 접근이 불가능합니다. 하지만 리플렉션은 이를 가능하게 해줍니다.
Note:getMethod()는 public한 메소드를 가지고 오며, getDeclaredMethod()는 private한 메소드를 포함한 클래스에 선언된 모든 메소드를 가지고 온다.
리플렉션으로 오브젝트 생성하기
리플렉션으로 오브젝트를 생성할 수 있습니다. 예제를 보겠습니다.
public class Person { public static final String TAG = Person.class.getSimpleName(); String name; int age; public Person(String name, int age){ this.name = name; this.age = age; } public void sayMyName(){ Log.e(TAG,String.format("Hello! My name is %s and I'm %d years old",name, age) ); } }
try { Class personClass = Class.forName("com.charlezz.reflection.Person"); Constructor personConstructor = personClass.getConstructor(String.class, int.class); Person person = (Person) personConstructor.newInstance("Charles",20); person.sayMyName(); //Hello! My name is Charles and I'm 20 years old 출력 } catch (Throwable e) { Log.e(TAG,e.toString()); }
Constructor.newInstance를 이용하여 new 생성자()와 같이 객체를 생성 할 수 있습니다.
필드의 값 변경하기
필드의 값 또한 리플렉션을 이용한다면 변경시킬 수 있습니다.
위의 작성한 Person 클래스를 가지고 다시 한 번 예제를 확인해보도록 하겠습니다.
try { Class cls = Class.forName("com.charlezz.reflection.Person"); Field ageField = cls.getField("age"); Person person = new Person("Charles", 20); Log.e(TAG,"person.age = " + person.age);//person.age = 20 ageField.setInt(person, 10); Log.e(TAG,"person.age = " + person.age);//person.age = 10 } catch (Throwable e) { Log.e(TAG,e.toString()); }
setXXX()메소드를 이용하면 필드의 값을 변경 시킬 수 있다는 점을 확인하였습니다.
실제 제 나이도 10살 어려졌으면 좋겠네요.
Conclusion
오랜만에 자바코드로 예제를 작성해보았습니다. 실제로 리플렉션은 비용이 큰 작업이므로 신중하게 사용하여야 합니다. 대부분의 안드로이드 애플리케이션의 개발에 있어서 리플렉션을 직접적으로 쓸일은 많지 않지만 안드로이드(자바) 개발자라면 반드시 알고 있어야 하는 개념이므로 이번 포스팅을 작성하였습니다.
Ps: 제 경우에는 안드로이드 BluetoothAdapter의 숨겨진 메소드인 setScanMode(int mode)를 사용하여 discoverable 상태를 지속시킨다거나, 커스텀 롬이 포팅된 기기의 API사용을 위해서 리플렉션을 사용한 경험 등이 있습니다.
본문의 예제는 github에서 확인 가능합니다. 감사합니다.
2개의 댓글
Jeremih · 2021년 8월 24일 9:19 오전
Parcelable찾아보다가 자바에 리플렉션이란 기능이 있는걸 첨 알았습니다. 자세한 설명 감사합니다. Serializable보다 Pacelable이 퍼포먼스가 좋은 이유를 이해했습니다.
성빈 · 2022년 5월 30일 9:52 오전
좋은 글 감사합니다.