Friday, September 18, 2015

bermain-main dengan java reflection

dalam posting ini saya hanya ingin mencoba java.lang.reflect.* dalam men-set property dari sebuah object. ada 2 cara yang saya coba yaitu dengan langsung men-set field-nya dan satu lagi yaitu dengan meng-invoke method setter-nya.

saya punya 2 class:

Model1.java:
 public class Model1 {  
        
      private String code;  
      private BigDecimal amount;  
        
      public String getCode() {  
           return code;  
      }  
      public void setCode(String code) {  
           this.code = code;  
      }  
      public BigDecimal getAmount() {  
           return amount;  
      }  
      public void setAmount(BigDecimal amount) {  
           this.amount = amount;  
      }  
        

dan Model2.java, yang meng-extend Model1.java
 public class Model2 extends Model1 {  
        
      private String name;  
   
      public String getName() {  
           return name;  
      }  
   
      public void setName(String name) {  
           this.name = name;  
      }  
   
      @Override  
      public String toString() {  
           return "Model2 [name=" + name + ", getCode()=" + getCode()  
                     + ", getAmount()=" + getAmount() + "]";  
      }  
        
 }  

dan saya punya class TestReflection.java yang saya gunakan untuk test-nya:
 public class TestReflection {  
        
      public static void main(String[] args) {  
           Model2 model2_1 = new Model2();  
             
           String[] fieldNames = {"code", "amount", "name"};  
             
           Date start = new Date();  
           for(String fieldName : fieldNames){  
                  
                Field field = getField(model2_1.getClass(), fieldName);  
                  
                //set directly to the field  
                if(field != null){  
                     field.setAccessible(true);  
                     if(field.getType().getName().equals("java.lang.String")){  
                          setField(field, model2_1, "asdf");  
                     }else if(field.getType().getName().equals("java.math.BigDecimal")){  
                          setField(field, model2_1, BigDecimal.valueOf(1234));  
                     }  
                }  
                  
                //set via setter method invocation  
                Method methodSet = getMethod(model2_1.getClass(), getMethodName("set", fieldName), field.getType());  
                if(methodSet != null){  
                     methodSet.setAccessible(true);  
                     if(fieldName.equals("amount")){  
                          invokeSetterMethod(methodSet, model2_1, BigDecimal.valueOf(1234));  
                     }else{  
                          invokeSetterMethod(methodSet, model2_1, "fdsa");  
                     }  
                }  
                  
           }  
             
           System.out.println("\n");  
           System.out.println(new Date().getTime() - start.getTime());  
             
           System.out.println("\n");  
           System.out.println("2_1 -> " + model2_1);  
             
      }  
        
      private static Field getField(Class clz, String fieldName){  
           Field field = null;  
             
           try {  
                field = clz.getDeclaredField(fieldName);  
                System.out.println("field " + fieldName + " in " + clz + " found");  
           } catch (NoSuchFieldException e) {  
                System.out.println("no field " + fieldName + " in " + clz);  
                if(clz.getSuperclass() != null){  
                     field = getField(clz.getSuperclass(), fieldName);  
                }  
           } catch (SecurityException e) {  
                e.printStackTrace();  
           }  
             
           return field;  
      }  
        
      private static void setField(Field field, Object object, Object value){  
           if(field != null){  
                try {  
                     field.set(object, value);  
                } catch (IllegalArgumentException e) {  
                     e.printStackTrace();  
                } catch (IllegalAccessException e) {  
                     e.printStackTrace();  
                }  
           }  
      }  
        
      private static String getMethodName(String setOrGet, String fieldName){  
           String methodName = setOrGet + StringUtils.capitalize(fieldName);  
           return methodName;  
      }  
        
      private static Method getMethod(Class clz, String methodName, Class argumentClass){  
           Method method = null;  
           try {  
                argumentClass = argumentClass == null ? (Class) null : argumentClass;  
                method = clz.getDeclaredMethod(methodName, argumentClass == null ? (Class) null : argumentClass);  
                System.out.println("method " + methodName + "(" + (argumentClass == null ? "" : argumentClass.getCanonicalName()) + ") in " + clz + " found");  
           } catch (NoSuchMethodException e) {  
                System.out.println("no method " + methodName + "(" + (argumentClass == null ? "" : argumentClass.getCanonicalName()) + ") in " + clz);  
                if(clz.getSuperclass() != null){  
                     method = getMethod(clz.getSuperclass(), methodName, argumentClass);  
                }  
           } catch (SecurityException e) {  
                e.printStackTrace();  
           }  
           return method;  
      }  
        
      private static void invokeSetterMethod(Method method, Object object, Object argument){  
           if(method != null){  
                try {  
                     method.invoke(object, argument);  
                } catch (IllegalAccessException e) {  
                     e.printStackTrace();  
                } catch (IllegalArgumentException e) {  
                     e.printStackTrace();  
                } catch (InvocationTargetException e) {  
                     e.printStackTrace();  
                }  
           }  
      }  
        
 }  

di main method class di atas, ada 2 bagian yang men-set value utk field, yaitu:
 //set directly to the field  
 if(field != null){  
      field.setAccessible(true);  
      if(field.getType().getName().equals("java.lang.String")){  
           setField(field, model2_1, "asdf");  
      }else if(field.getType().getName().equals("java.math.BigDecimal")){  
           setField(field, model2_1, BigDecimal.valueOf(1234));  
      }  
 }  
yang men-set dengan langsung men-set field-nya, dan
 //set via setter method invocation  
 Method methodSet = getMethod(model2_1.getClass(), getMethodName("set", fieldName), field.getType());  
 if(methodSet != null){  
      methodSet.setAccessible(true);  
      if(fieldName.equals("amount")){  
           invokeSetterMethod(methodSet, model2_1, BigDecimal.valueOf(1234));  
      }else{  
           invokeSetterMethod(methodSet, model2_1, "fdsa");  
      }  
 }  
yang men-set melalui pemanggilan method setter-nya.

keduanya berhasil, bisa dicoba sendiri. saya mencoba mencari perbedaan dari kedua cara di atas, salah satunya adalah dengan membandingkan waktu prosesnya. saya men-comment salah satu cara dan mencatat durasi prosesnya, masing-masing saya akan ambil datanya sebanyak 10 kali.

dan inilah hasilnya


terlihat selisihnya sangat jauh.

saya coba untuk mencatat durasi masing-masing getField dan getMethod:
 Date start = new Date();  
 Field field = getField(model2_1.getClass(), fieldName);  
 System.out.println("getField duration: " + (new Date().getTime() - start.getTime()));  
   
 ...  
   
 start = new Date();  
 Method methodSet = getMethod(model2_1.getClass(), getMethodName("set", fieldName), field.getType());  
 System.out.println("getMethod duration: " + (new Date().getTime() - start.getTime()));  

kurang lebih seperti inilah hasil print-nya:
 no field code in class org.mazb.usemefortesting.reflection.Model2  
 field code in class org.mazb.usemefortesting.reflection.Model1 found  
 getField duration: 2  
 no method setCode(java.lang.String) in class org.mazb.usemefortesting.reflection.Model2  
 method setCode(java.lang.String) in class org.mazb.usemefortesting.reflection.Model1 found  
 getMethod duration: 11  
   
 no field amount in class org.mazb.usemefortesting.reflection.Model2  
 field amount in class org.mazb.usemefortesting.reflection.Model1 found  
 getField duration: 0  
 no method setAmount(java.math.BigDecimal) in class org.mazb.usemefortesting.reflection.Model2  
 method setAmount(java.math.BigDecimal) in class org.mazb.usemefortesting.reflection.Model1 found  
 getMethod duration: 0  
   
 field name in class org.mazb.usemefortesting.reflection.Model2 found  
 getField duration: 0  
 method setName(java.lang.String) in class org.mazb.usemefortesting.reflection.Model2 found  
 getMethod duration: 0  

dari beberapa kali percobaan, saya mendapatkan invokasi getMethod pertama selalu memakan waktu lama, tapi berikut-berikutnya tidak. hal ini juga terjadi pada durasi getField, namun nilainya tidak jauh selisihnya, antara 2 dan 0.



awalnya saya menduga ini terkait dengan field/method-nya, tapi walaupun urutannya saya ubah-ubah, tetap yang pertama akan memakan waktu jauh lebih lama. begitu pula jika field 'name' (field dimiliki oleh Model2, tidak perlu mengambil dari Model1) yang pertama diproses, dia tetap memakan waktu paling lama, dengan kisaran nilai yang sama.

ini menarik. saya belum bisa menjelaskan kenapa ini bisa terjadi, apakah ada mekanisme tertentu dari jvm terkait hal ini atau mungkin ada alasan lain.

sampai saat ini yang bisa saya simpulkan dari percobaan di atas adalah, jika ingin memanfaatkan java reflection, maka set field secara langsung ke field-nya akan menghasilkan waktu proses yang jauh lebih cepat daripada dengan invokasi method setter.


No comments:

Post a Comment