Java - How to call method of private inner class with Java Reflection

在這裡主要會提到如何透過Java Reflection存取private inner class,但是一個類別的private inner class本來就不是要給外部類別存取的,因此在這邊會搭配實際的範例說明為何我們可能需要去存取private inner class!

在這裡會建立三個class,兩個class在manager package下,另一個則是main package

+package - manager
InfoManager.java
InfoMain.java

+package - main
InnerPrivateClassReflection.java(主程式)

一、InfoManager.java
package manager;

public class InfoManager {

...

 private Map<String, AbstractInfo> tmpInfos;
 private Map<String, AbstractInfo> recordInfos;
 private static InfoManager INSTANCE = new InfoManager();
 
 private InfoManager() {
    tmpInfos = new HashMap<String, AbstractInfo>();
    recordInfos = new HashMap<String, AbstractInfo>();
 }
 
 public static InfoManager getInstance() {
   return INSTANCE;
 }

 private abstract class AbstractInfo{
  private String param;
  private String value;
  public AbstractInfo(String param, String value) {
   super();
   this.param = param;
   this.value = value;
  }
  public String getParam() {
   return param;
  }
  public void setParam(String param) {
   this.param = param;
  }
  public void setValue(String value) {
   this.value = value;
  }
  public String getValue() {
   return value;
  }
  public String toString() {
   return "[param:"+param+", value:"+value+"]";
  }
 }
 
 private class InfoTmp extends AbstractInfo{
  public InfoTmp(String param, String value) {
   super(param, value);
  }
 }
 
 public class InfoPermanent extends AbstractInfo{
  public InfoPermanent(String param, String value) {
   super(param, value);
  }
 }
}

在此總共有三個inner class,AbstractInfo、InfoTmp皆為私有類別,InfoPermanent為公有類別再來InfoManager本身為singleton mode,所擔任的就是控制目前Info建立的情形,並且將它記錄到tmpInfos及recordInfos變數內。

那麼可以操作的動作如下:
protected void addInfo(String param, String value) {
  InfoTmp tmp = new InfoTmp(param, value);
  tmpInfos.put(param, tmp);
 }
 
 protected AbstractInfo getTmpInfo(String param) {
  return tmpInfos.get(param);
 }
 
 protected void updateInfo(String param, String value) {
  System.out.println("update "+param+" value to "+value);
  tmpInfos.put(param, new InfoTmp(param, value));
 }
 
 public AbstractInfo getInfo(String param) {
  return recordInfos.get(param);
 }
 
 public void displayAllInfo() {
  System.out.println("Display all informations...");
  for(String param : recordInfos.keySet()) {
   System.out.println(recordInfos.get(param));
  }
 }
 
 protected void displayAllNotSavedInfo() {
  System.out.println("Display all not saved informations...");
  for(String param : recordInfos.keySet()) {
   if(tmpInfos.containsKey(param)) {
    System.out.println(tmpInfos.get(param));
   }else {
    System.out.println(recordInfos.get(param));
   }
  }
 }
 
 public void save() {
  //System.out.println("save....");
  for(String param : tmpInfos.keySet()) {
   AbstractInfo info = getTmpInfo(param);
   recordInfos.put(param, new InfoPermanent(info.getParam(), info.getValue()));
  }
  tmpInfos.clear();
 }

基本上,這些動作如addInfo、updateInfo都會先產生InfoTmp的object,只有當做save的動作時才會建立InfoPermanent object,並且將暫存InfoTmp的變數clear掉!

二、InfoMain.java
package manager;

public class InfoMain {
private InfoManager manager = InfoManager.getInstance();
 
 public InfoMain() {
  manager.addInfo("${PARAM1}", "-p 111 -t 222");
  manager.addInfo("${PARAM2}", "-f 555 -g 888");
  manager.save();
 }
 
 public void updateInfo(String param, String value) {
  manager.updateInfo(param, value);
 }
 
 public void addInfo(String param, String value) {
  manager.addInfo(param, value);
 }
 
 public void displayAllInfo() {
  manager.displayAllNotSavedInfo();
 }
}

再來則是透過InfoMain來進行addInfo及updateInfo針對InfoManager,而且InfoMain呼叫的displayAllInfo會包含尚未saved的Info,也就是包含tmpInfo也會在裡面!
PS. 部分針對manager呼叫的method為protected,因此需要同一個package下的class才可以呼叫!

三、InnerPrivateClassReflection.java
再來則是主程式,主程式會建立InfoMain object,並且進行addInfo、updateInfo
package main;
public class InnerPrivateClassReflection {

 public static void main(String[] args) throws Exception {
    InfoMain main = new InfoMain();
    main.displayAllInfo();
    main.updateInfo("${PARAM1}", "-r 333 -u 666");
    main.displayAllInfo();
    ...
 }
}
Console output:
Display all not saved informations...
[param:${PARAM1}, value:-p 111 -t 222]
[param:${PARAM2}, value:-f 555 -g 888]
update ${PARAM1} value to -r 333 -u 666
Display all not saved informations...
[param:${PARAM1}, value:-r 333 -u 666]
[param:${PARAM2}, value:-f 555 -g 888]

上面的console output可以得知update前後的資料顯示
不過主程式本身是無法透過InfoManager來進行相關操作的,針對protected method。主程式呼叫InfoManager的displayAllInfo只能顯示已saved的Info! 所以,主程式呼叫getInfo所得到的資料也是舊的! 更別提想要存取到未儲存的${PARAM1}的value -r 333 -u 666
InfoManager manager = InfoManager.getInstance();
System.out.println("InnerPrivateClassReflection get ${PARAM1} info");
System.out.println(manager.getInfo("${PARAM1}"));

Console output:
InnerPrivateClassReflection get ${PARAM1} info
[param:${PARAM1}, value:-p 111 -t 222]

所以,假設主程式想要取得tmpInfo object的value,那麼又該怎麼辦?  因為一般透過Java Reflection要呼叫該object的method,首先需要建立Method object
Method method = InfoManager.InfoTmp.class.getDeclaredMethod("getValue");  
但是在主程式內無法利用InfoManager呼叫private的inner class!
因此在這邊會透過
Class<?> innerClass[] = InfoManager.class.getDeclaredClasses();
來取得InfoManager的inner class,再比對該class name是否是我們要的,以此取得class type

如下:
Method method = InfoManager.class.getDeclaredMethod("getTmpInfo", String.class);
  method.setAccessible(true);
  Object tmpInfo = method.invoke(manager, "${PARAM1}");
  System.out.println("Call getTmpInfo() by Java Reflection\n"+tmpInfo);
  
  Class innerClass[] = InfoManager.class.getDeclaredClasses();
  for(Class clazz : innerClass) {
   if(clazz.getSimpleName().equals("InfoTmp")) {
    method = clazz.getSuperclass().getDeclaredMethod("getValue");
    method.setAccessible(true);
    Object obj = method.invoke(tmpInfo);
    System.out.println(obj);
   }
  }

首先透過呼叫getTmpInfo來取得InfoTmp object,再比對哪一個class name是InfoTmp 而變數clazz還需要呼叫getSuperclass(),由於InfoTmp並沒有getValue() method,因此需要透過其父類別才可以!
PS. 取得InfoTmp object其實體為InfoTmp,但是其型態為AbstractInfo,因此變數clazz應也可以針對AbstractInfo,那麼此時clazz就可以直接呼叫getDeclaredMethod了
綜合上面的結果,最後執行如下:

總結

在這邊有點弔詭,針對未儲存的Info為何需要去get它的value呢?
假設InfoMain及InnerPrivateClassReflection是一個大dialog下的兩個不同頁面的功能,可以透過切換的方式到這兩個頁面。InfoMain這個頁面可以CRUD Info但是在切換至
InnerPrivateClassReflection頁面時可以不一定要儲存,而InnerPrivateClassReflection頁面內又需要即時取得Info最新的value,若在InnerPrivateClassReflection頁面取得的還是更改之前的就感覺沒有sync了,雖然說InfoMain還沒有存檔! 如此一來,為了達到sync的目的,即使是private inner class也要取得它的value才是!
但還是有點奇怪,那不就只要改InfoMain內相關的存取範圍就好了呀! 可惜的是,假設InfoMain是一個第三方核心的code且已經包在jar檔內,那麼當然採用前者的方式囉!

留言