1. 程式人生 > 實用技巧 >Class類與反射破壞了java的封裝嗎?

Class類與反射破壞了java的封裝嗎?

宣告:本文摘抄自:https://www.cnblogs.com/codespoon/p/13299568.html

1.Class類與反射,反射破壞了封裝嗎?

  舉個例子,把程式碼過程看作去一個目標地點,普通程式碼呼叫呢就是事先知道經緯度,然後你坐直升機直接就到了;而通過反射呢就像不知道具體的地點,只知道先去一個地點,然後前往下一個地點,一步步到達目標。這兩種方法殊途同歸,反射因為要“尋路”,所以會慢一些,但在找到目標地點後和直接呼叫是一樣的。

  有時候我們需要在程式中建立新的物件或是呼叫一個方法,而對應的細節我們事先並不知道,也就是說要在執行中動態地獲得類的資訊和呼叫方法

。下面介紹如何利用反射來實現。

2.Class類:儲存和類有關的資訊的類

  2.1需要了解的概念

    RTTI(RunTime Type Information,執行時型別資訊)能夠在程式執行時發現和使用型別資訊

    Class物件就儲存著執行時型別資訊RTTI,表示一個特定類的屬性

    Class類實際上表示的是一個泛型類Class<?>

    當編譯一個新類時JVM會呼叫類載入器把這個類載入到記憶體中

      類載入器首先會檢查這個類的 Class 物件是否已經載入,如果尚未載入,預設的類載入器就會根據類名查詢 .class 檔案

      一旦某個類的 Class 物件被載入記憶體,它就可以用來建立這個類的所有物件

    JVM為每個型別管理一個Class物件

  利用Class類得到類的資訊和例項化一個類.

    Class.getName()方法

      返回類的名字,如果類在一個包中還會加上包名

      使用getSimpleName()得到不帶包名的類名

    Class.forName(String className)方法

      獲得className類名對應的Class物件

    Object.newInstance()方法

      可以用來動態地建立一個類的例項

      方法呼叫類的預設構造器(沒有引數的構造器),如果沒有預設構造器,就會丟擲一個異常

      如果要呼叫帶引數的構造器,使用Constructor類中的newInstance方法

  例項:  

System.out.println("靜態建立一個新的物件");
Employee ae = new Employee();
System.out.println(ae);
//使用getClass.getName得到類名
String cName = ae.getClass().getName();
//再用forName和newInstance動態建立一個物件
System.out.println("getClass()+forName()動態建立一個新的物件");
Object o = Class.forName(cName).newInstance();
System.out.println(o.getClass());//會輸出實際型別Employee
System.out.println(o);

//先定義一個類名 再新建物件
String m = "CoreJava.c5_inheritance.Manager";
System.out.println("先定義再forName()動態建立一個新的物件");
Object am = Class.forName(m).newInstance();
System.out.println(am.getClass());//會輸出實際型別Manager
System.out.println(am);

  結果:

  

3.反射:分析類的能力,相對動態地執行一些方法

  利用Field類檢視任意物件的資料域名稱和型別

    首先獲得要分析類的Class物件getClass()/forName()

    getField(String fieldName) 和 getFileds() 能獲取Class物件的對應域(getDeclared(),getDeclaredFields()獲取所有已宣告域,包括私有域)

    Field.getName()能獲取域名稱,Field.getType()能獲取域型別

  例項:

System.out.println("利用反射獲得所有域");
Manager manager = new Manager();
Class clazz = manager.getClass();//先得到類的執行時資訊
Field[] fields = clazz.getDeclaredFields();//獲得所有宣告的域(包括私有域)
for (Field f : fields)
System.out.println(f.getType() + " " + f.getName());

  結果:

  

  獲得域中的值並修改

    Field.get(Object obj)

      可以獲得obj物件中用Filed物件表示的域值(設 f 是Field的一個例項,表示Manager類中的salary域,那麼f.get(m)可以獲得Manager例項m中salary域的值)

      如果是私有域,需要先設定可訪問標誌為true: fild.setAccessible(true)

    Field.set(object obj, Object newValue)

      用一個新值設定obj物件中Field物件表示的域

  示例:

//獲得域中的值並修改
System.out.println("利用反射獲得域中的值並修改");
System.out.println("使用類方法getSalary():" + manager.getSalary());
Field managerSalaryField = clazz.getDeclaredField("salary");//注意異常處理
managerSalaryField.setAccessible(true);//設定可訪問標誌為true,訪問私有域
System.out.println("使用反射獲得salary:" + managerSalaryField.get(manager));//注意異常處理
System.out.println("使用反射修改salary");
managerSalaryField.set(manager, 999);
System.out.println("修改後salary:" + manager.getSalary());

  結果:

  

  利用Method類獲得任意方法名稱和返回值

    Class.getMethod(String methodName, Class<?>[] paramTypes) 和 Class.getMethods() 分別能獲得類的對應Method物件和所有Method物件

    Method.getName()獲得方法名

    Methord.getReturnType()獲得方法返回型別

  示例:

System.out.println("利用反射獲得所有方法");
Method[] methods = clazz.getMethods();
for (Method method : methods)
    System.out.println(method.getReturnType().getSimpleName() + " " + method.getName() + " ");

  結果:

  

  呼叫任意方法:

    Method.invoke(Object obj, Object... params)可以呼叫obj物件中Method物件表示的方法,params是方法引數。對於靜態方法第一個引數可以傳入null

  示例:

System.out.println("利用反射呼叫任意方法");
System.out.println("修改前salary:" + manager.getSalary());
Method setManagerSalaryMethod = clazz.getMethod("setSalary", int.class);
setManagerSalaryMethod.invoke(manager, 222);//第一個引數為執行物件,靜態方法可傳入null
System.out.println("修改後salary:" + manager.getSalary());

  結果: