1. 程式人生 > >Java進階 六 Java反射機制可惡問題NoSuchFieldException

Java進階 六 Java反射機制可惡問題NoSuchFieldException

作為一種重要特性,Java反射機制在很多地方會用到。在此做一小結,供朋友們參考。

首先從一個問題開始著手。

 

可惡的問題又來了,NoSuchFieldException,如下圖所示:

 

完全不知道這個question是從哪裡來的。以前也遇到過這樣的問題,後來解決了,但是沒有寫文件,再次相遇這樣的問題,傻了。

經過上網一番查詢,發現遇到這個問題的小盆友還真不少,這個問題是屬於java反射機制裡的。

這是一個反射物件時候的異常,已經被捕獲了的。這個報錯程式碼是混淆了的,是不是這個question物件被混淆成其他名了。。

原始碼如下:

public

 static <T> List<T> findMoreRefResult(String sql, List<Object> params,

Class<T> cls) throws Exception {

 

//載入資料庫驅動

new MysqlUtil();

 

//連線資料庫

MysqlUtil.GetConnection();

 

// 構造一個初始容量為 10 的空列表。

List<T> list = new ArrayList<T>();

 

// 表示佔位符的第一個位置

int index = 1;

 

pstmt = connection.prepareStatement(sql);

 

System.out.println("MysqlUtil:" + params);

 

// 判斷所填充的佔位符是否有值;判斷集合的標準方式

if

 (params != null && !params.isEmpty()) {

for (int i = 0; i < params.size(); i++) {

 

// 使用給定物件設定指定引數的值。第二個引數必須是Object型別

pstmt.setObject(index++, params.get(i));

 

}

}

 

// 返回查詢結果

resultset = pstmt.executeQuery();

 

// 獲取列的相關資訊

java.sql.ResultSetMetaData metdata = resultset.getMetaData();

 

// 獲取列數

int col_lenth = metdata.getColumnCount();

while (resultset.next()) {

 

// 通過反射機制建立一個例項(生成物件)

T resultObject = cls.newInstance();

for (int i = 0; i < col_lenth; i++) {

String cols_name = metdata.getColumnName(i + 1);

Object cols_value = resultset.getObject(cols_name);

if (cols_value == null) {

cols_value = "";

}

 

// 通過欄位名獲得反射(返回一個 Field 物件,該物件反映此 Class 物件所表示的類或介面的指定已宣告欄位)

Field field = cls.getDeclaredField(cols_name);

 

// 開啟javabean的私有訪問許可權

field.setAccessible(true);

field.set(resultObject, cols_value);

}

list.add(resultObject);

}

 

//關閉資料庫

MysqlUtil.releaseConn();

 

return list;

}

呼叫以上方法的方法如下:

public static void main(String[] args) {

 

//Question question = new Question();

//

//Field property = null;

//try {

//property = question.getClass().getDeclaredField("description");

//} catch (NoSuchFieldException e1) {

//e1.printStackTrace();

//} catch (SecurityException e1) {

//e1.printStackTrace();

//}

//        System.out.println(property);

 

List<Object> params = new ArrayList<Object>();

 

String sql = "SELECT * FROM question ";

 

try {

System.out.println(MysqlUtil.findMoreRefResult(sql, params, Question.class));

catch (Exception e) {

e.printStackTrace();

}

在呼叫findMoreRefResult()方法的時候,請注意紅色背景部分,先獲取資料庫表中欄位名,然後依據該欄位名來反射此 Class 物件所表示的類或介面的指定已宣告欄位。也就是說資料庫中的欄位名應該與類中的屬性欄位名一一對應才對,這是符合“建資料表時應與類一一對應”原則的。

下面,我們來深入學習一下Java的反射機制,只有做到深入瞭解某件事情,當這件事情發生問題時,我們自然就會懂得解決之道。

注:下面的內容只瞭解基本的原理就行,程式碼可忽略。

 

一、反射的基礎---Class

Class是所有java類的一個總稱,就好像各式各樣的人都可以用Person來稱呼,每一個類被載入之後都會在記憶體中生存一個Class物件,這個物件我們通常稱之為位元組碼,而我們通過呼叫一個類創造的物件其實都是位元組碼搞出來的,一個類只會產生一份位元組碼。

 那麼我們怎麼獲得一個類的Class呢?有三種方式:

    1.呼叫某個類的class屬性  

    2.使用ClassforName()靜態方法  

    3.呼叫某個物件的getClass()方法。

 

下面我們通過一個例項來展示下上面兩點:

ClassDemo1.java

 

package com.lyl.exercise;  

  

public class ClassDemo1 {  

    public static void main(String[] args) throws ClassNotFoundException{  

        String str="iteye";  

        Class cl1=String.class;  

        Class cl2=str.getClass();  

        Class cl3=Class.forName("java.lang.String");  

        System.out.println("str物件與String是同一份位元組碼嗎?"+(cl1==cl2));  

        System.out.println("通過Class.forName()獲得的位元組碼與String.class一樣嗎?"+(cl1==cl3));  

    }  

}  

 

通過檢視JDK文件,我們可以發現,Class有許多方法,通過這些方法我們可以得到java類的相關資訊,Constructor,Method,Field等,具體的大家可以參考JDK文件。

 

二、反射的應用

那麼什麼是反射呢?曾經有人說過,反射就是把java類中的各種成分對映成相應的java類。為什麼呢?從上一個講解中大家是否發現,通過Class我們可以解析出一個java類的各種成分,他們返回的也是一個類,所以這句話還是很有道理的。

下面讓我們來看看反射的一些應用:

    1.使用反射生成物件

     通過反射來生成物件有兩種方式:

        a.使用Class物件的newInstance()方法

        b.使用Class物件獲取指定的Constructor物件,再呼叫Constructor物件的newInstance()方法來建立Class對應類的例項。

 

例項: ClassDemo3.java

 

package com.lyl.exercise;  

  

import java.lang.reflect.Constructor;  

  

public class ClassDemo3 {  

    public static void main(String[] args) throws Exception{  

        //使用Class物件的newInstance()方法  

        String str=(String)Class.forName("java.lang.String").newInstance();  

        //使用Class物件獲取指定的Constructor物件,再呼叫Constructor物件的newInstance()方法來建立Class對應類的例項。  

        Constructor constructor=String.class.getConstructor(StringBuffer.class);  

        String str2=(String)constructor.newInstance(new StringBuffer("abc"));  

    }  

}  

 

     2.成員變數的反射

 

     接上例項大家看看:

 

ReflectDemo1.java

 

package com.lyl.exercise;  

  

import java.lang.reflect.Field;  

  

public class ReflectDemo1 {  

    public static void main(String[] args) throws Exception {  

        ReflectHelper rh=new ReflectHelper("iteye", "javaeye");  

        Field fieldb=rh.getClass().getField("b");  

        //fieldb只是類的欄位,不是物件的,所以要想獲得物件上的值,要使用get()方法  

        System.out.println(fieldb.get(rh));  

        //如果我們使用上面方法來訪問a的值呢?丟擲java.lang.NoSuchFieldException  

        /*Field fielda=rh.getClass().getField("a"); 

        System.out.println(fieldb.get(rh)); 

        因為a是私有成員變數 

        */  

        //我們可以通過暴力反射來獲取它的值  

        Field fielda=rh.getClass().getDeclaredField("a");  

        //設定為可以訪問  

        fielda.setAccessible(true);  

        System.out.println(fielda.get(rh));  

    }  

}  

 

ReflectHelper.java

 

package com.lyl.exercise;  

 

public class ReflectHelper {  

    private String a;  

    public String b;  

    public ReflectHelper(String a,String b){  

        this.a=a;  

        this.b=b;  

    }  

}  

 

如果將上面的搞懂,那麼我們不妨來做一道經典的題目:將一個類中所有String型別的成員變數,其中含有的‘a’替換為‘b’。

這個題目很明顯通過反射可以輕易的完成,大家不妨來看看。

 

package com.lyl.exercise;  

  

import java.lang.reflect.Field;  

  

public class ReflectDemo2 {  

    public static void main(String[] args) throws Exception {  

        ReflectHelper rh=new ReflectHelper("abc", "basketball");  

        changeStringValue(rh);  

        System.out.println(rh);  

    }  

  

    private static void changeStringValue(Object object) throws Exception{  

        Field[] fields=object.getClass().getFields();  

        for(Field field:fields){  

            if(field.getType()==String.class){  

                String oldValue=(String)field.get(object);  

                String newValue=oldValue.replace('a','b');  

                //objectString型別的變數替換為newValue  

                field.set(object, newValue);  

            }  

        }  

    }  

}  

 

這樣就搞定了,是不是很簡單?

 

一、ClassLoader初步

類載入器負責載入所有的類,系統為所有被載入記憶體中的類生成一個java.lang.Class例項。一旦一個類被載入到JVM中,同一個類就不會再次被載入了,這是針對同一個載入器,不同的載入器還是可以載入同一個類的,不同載入器載入同一個類在JVM中是不同的。因為在JVM中用類的全限定類名加類載入器作為其唯一標識。

    在JVM啟動時,會形成有三個類載入器組成的初始類載入器層次結構:

    -->Bootstrap ClassLoader:根類載入器

    -->Extension ClassLoader:擴充套件類載入器

    -->System ClassLoader:系統類載入器

 

Bootstrap ClassLoader是使用C++寫的,我們是無法得到它的原始碼,它是java的核心Loader,比如我們通過String.class.getClassLoader()是獲得不到它的名字的,返回的是空值。

如果父類載入器載入了某個類,子類就不會載入了。

   ClassLoader動態載入:

    a.並非一次性載入

    b.需要執行時才載入

    c.可以觀察類的具體載入過程:java -verbose:class  eclipse中只要配置後面的-verbose:class

    d.static語句塊在載入後執行一次。

    e.dynamic語句塊每次new新的物件都會執行

        等同於構造方法中的語句。

        用的比較少。

具體看參見:http://gzcj.iteye.com/blog/394644

 

二、使用反射實現JDK動態代理

java中提供了Proxy類和一個InvocationHandler介面,通過他們倆我們就可以建立JDK動態代理,下面讓我們通過一個例項來看看如何使用吧:

Person介面

  

package com.lyl.reflect;  

  

public interface Person {  

    void walk();  

    void sayHello(String name);  

}  

 

MyInvocationHandler.java  

  

package com.lyl.reflect;  

  

import java.lang.reflect.InvocationHandler;  

import java.lang.reflect.Method;  

  

public class MyInvocationHandler implements InvocationHandler {  

  

    @Override  

    public Object invoke(Object proxy, Method method, Object[] args)  

            throws Throwable {  

        System.out.println("正在執行的方法:"+method);  

        if(args!=null){  

            System.out.println("執行該方法傳入的引數:");  

            for(Object arg:args){  

                System.out.println(arg);  

            }  

        }else {  

            System.out.println("該方法沒有傳入引數!");  

        }  

        return null;  

    }  

  

}  

 

ProxyTest.java  

  

package com.lyl.reflect;  

  

import java.lang.reflect.InvocationHandler;  

import java.lang.reflect.Proxy;  

  

public class ProxyTest {  

    public static void main(String[] args) {  

        InvocationHandler handler=new MyInvocationHandler();  

        //返回Person介面的代理類例項  

        Person person=(Person)Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[]{Person.class}, handler);  

        person.walk();  

        person.sayHello("ITEYE");  

    }  

}  

 通過Proxy類的newProxyInstance()方法我們可以獲得一個指定介面的代理類例項,在MyInvocationHandler中會自動執行invoke方法,執行結果如下:

正在執行的方法:public abstract void com.lyl.reflect.Person.walk()  

該方法沒有傳入引數!  

正在執行的方法:public abstract void com.lyl.reflect.Person.sayHello(java.lang.String)  

執行該方法傳入的引數:  

ITEYE  

 

首先必須明一點 Field類主要是用來輔助獲取和操作類的屬性的!

 

1.怎麼通過反射獲取類的屬性

 

 先來看JDK提供的方法有如下幾種:

 

  a)Class.getDeclaredField(String name);

 

返回一個 Field 物件,該物件反映此 Class 物件所表示的類或介面的指定已宣告欄位。

 

  b)Class.getDeclaredFields();

 

返回 Field 物件的一個數組,這些物件反映此 Class 物件所表示的類或介面所宣告的所有欄位。

 

  c)Class.getField(String name);

 

返回一個 Field 物件,它反映此 Class 物件所表示的類或介面的指定公共成員欄位。

 

  d)Class.getField();

 

返回一個包含某些 Field 物件的陣列,這些物件反映此 Class 物件所表示的類或介面的所有可訪問公共欄位。

 

來一個例子來說明一下 :

實體類:

import java.util.Date;

/**

 * @ClassName: Student

 * @Description: 學生實體

 * @author  ***  

 * @date 2014-3-18 下午5:17:35

 * @version V1.0 

 */

public class Student

{

  private Long id;

  private String name;

  private Date createdate;

  private String no;

  public String nickname;

public Long getId()

{

    return id;

}

public void setId(Long id)

{

    this.id = id;

}

public String getName()

{

    return name;

}

public void setName(String name)

{

    this.name = name;

}

................   

測試類:

 

import java.lang.reflect.Field;

import java.util.Date;

/**

 * @ClassName: ReflectFieldTest

 * @Description: 反射Field測試。

 * @author  ***  

 * @date 2014-3-18 下午5:16:17

 * @version V1.0 

 */

public class ReflectFieldTest

{

  

   public static void main(String[] args)

{    

Student stu=new Student();

      stu.setId(1L);

      stu.setName("Josean");

      stu.setNo("201403185203344");

      stu.setCreatedate(new Date());

     try

     {

         Field property1=stu.getClass().getDeclaredField("name");

//private java.lang.String com.cx.test.Student.name

        System.out.println(property1);

        Field property3=stu.getClass().getField("nickname");

//public java.lang.String com.cx.test.Student.nickname

        System.out.println(property3);

        //錯誤方法 getField系列方法只能獲取公共欄位

        //Field property2=stu.getClass().getField("name");

        //System.out.println(property2);

        //會拋java.lang.NoSuchFieldException

        

     } catch (SecurityException e)

     {

         e.printStackTrace();

     } catch (NoSuchFieldException e)

     {

         e.printStackTrace();

     }

}   

}

 

2.進行屬性獲取、更改

得到這個Field之後你就可以獲取他的值或者設定他的值了。

 

獲取它的值用get型別方法,針對常見型別提供了對應get方法,這裡就不一一列舉了。

 

值得注意的是獲取私有屬性的時候必須先設定Accessibletrue,然後才能獲取。

 

同理設定的時候呼叫set型別方法,這裡也不一一列舉了,下面放程式碼。

 

import java.lang.reflect.Field;

import java.util.Date;

/**

 * @ClassName: ReflectFieldTest

 * @Description: 反射Field測試。

 * @author JoseanLuo  

 * @date 2014-3-18 下午5:16:17

 * @version V1.0 

 */

public class ReflectFieldTest

{

  

    public static void main(String[] args) throws Exception

{    Student stu=new Student();

     stu.setId(1L);

     stu.setName("Josean");

     stu.setNo("201403185203344");

     stu.setCreatedate(new Date());

     stu.setNickname("copyman");

        Field property1=stu.getClass().getDeclaredField("name");

        //System.out.println(property1);

//out:private java.lang.String com.cx.test.Student.name

        Field property3=stu.getClass().getField("nickname");

        System.out.println(property3.get(stu));

        //System.out.println(property3);

//out:public java.lang.String com.cx.test.Student.nickname

        //錯誤方法 getField系列方法只能獲取公共欄位

        //Field property2=stu.getClass().getField("name");

        //System.out.println(property2);

        //會拋java.lang.NoSuchFieldException

        Field [] prFields4=stu.getClass().getDeclaredFields();

        for(Field field:prFields4)

        {

            System.out.println(field);

            System.out.println(field.equals(property1));

            //私有變數必須先設定Accessibletrue

            field.setAccessible(true);

            //獲取用get類方法。

            System.out.println(field.get(stu));

        }

        //設定用set類方法

        property3.set(stu, "名字被我改了,哈哈");

          

        System.out.println(stu.getNickname());        

}

     

}

這個是控制檯輸出:

copyman

private java.lang.Long com.cx.test.Student.id

false

1

private java.lang.String com.cx.test.Student.name

true

Josean

private java.util.Date com.cx.test.Student.createdate

false

Tue Mar 18 18:19:39 CST 2014

private java.lang.String com.cx.test.Student.no

false

201403185203344

public java.lang.String com.cx.test.Student.nickname

false

copyman

名字被我改了,哈哈

再分享一下我老師大神的人工智慧教程吧。零基礎!通俗易懂!風趣幽默!希望你也加入到我們人工智慧的隊伍中來!http://www.captainbed.net