1. 程式人生 > >java反射操作類結構(二)

java反射操作類結構(二)

java反射操作類結構

  • 反射操作構造方法
    Class只是作為反射的操作源頭,但是嚴格來講,反射還有其他內容。反射給使用者最大的方便有三點:
    1.構造呼叫
    2.方法呼叫
    3.屬性呼叫
    在反射機制裡面提供有java.lang.reflect包,包中最重要的三個類:
    Constructor、Method、Field

構造方法呼叫
利用Class類中的forName()方法可以取得Class類的物件而後利用newInstance()進行物件的例項化處理操作。
但是這種操作最大的缺陷在於:類中必須要有提供有無參的構造方法;
這裡寫圖片描述

這裡寫圖片描述

因為此時的Emp類中不存在有無參構造方法,所以無法利用Class類的newInstance()方法來進行例項化操作。
要想解決此類問題,那麼就必須依靠Constructor類完成,在Class類中定義有兩個取得Constructor類的操作方法。
取得全部構造:
public Constructor [] getConstructors() throws SecurityException
取得指定引數型別構造:
這裡寫圖片描述

取得全部構造:

package com.wanghaoxin.demo;

import java.lang.reflect.Constructor;

class Emp{
    public Emp(){}
    public Emp(String name ){}
    public Emp(String name,double sal){}
}
public class TestClassDemo {
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("com.wanghaoxin.demo.Emp"
); Constructor<?> cons [] = cls.getConstructors(); //取得全部的構造 for(int i=0;i<cons.length;i++){ System.out.println(cons[i]); } } }

執行結果:
public com.wanghaoxin.demo.Emp()
public com.wanghaoxin.demo.Emp(java.lang.String)
public com.wanghaoxin.demo.Emp(java.lang.String,double)

這個時候取得構造方法資訊依靠的是Constructor類中的toString()方法完成。
但是在Constructor中提供有如下方法:
取得方法名字
———public String getName()以字串形式返回此構造方法的名稱。它總是與構造方法的宣告類的簡單名稱相同。

取得訪問修飾符
———-public int getModifiers()以整數形式返回此 Constructor 物件所表示構造方法的 Java 語言修飾符。應該使用 Modifier 類對這些修飾符進行解碼。
注意:所有的修飾符都是數字的疊加,如果要想將數字轉換為可讀懂的修飾符,要使用Modifier類來進行轉換。
將數字轉換為修飾符。

取得方法引數型別:
————–public Class<?>[] getParameterTypes()按照宣告順序返回一組 Class 物件,這些物件表示此 Constructor 物件所表示構造方法的形參型別。如果底層構造方法不帶任何引數,則返回一個長度為 0 的陣列。

程式碼:

package com.wanghaoxin.demo;

import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;

class Emp{
    public Emp(){}
    public Emp(String name ){}
    public Emp(String name,double sal){}
}
public class TestClassDemo {
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("com.wanghaoxin.demo.Emp");
        Constructor<?> cons [] = cls.getConstructors();         //取得全部的構造方法
        for(int i=0;i<cons.length;i++){
            System.out.println(Modifier.toString(cons[i].getModifiers()));//取得構造方法的修飾符
            System.out.println(cons[i].getName());//取得方法的名字
            Class<?> clsparams [] = cons[i].getParameterTypes();//取得方法引數型別
            System.out.println("(");
            for(int j=0;j<clsparams.length;j++){
                System.out.println(clsparams[j].getSimpleName()+"---arg---"+j);
                if(j<clsparams.length-1){//後面還有內容的時候輸出逗號
                    System.out.println(",");
                }
            }
            System.out.println(")");
            //System.out.println(cons[i]);
        }
    }
}

以上的程式碼對於使用者的直接開發意義不大, 但是可以利用這個功能對類中是否含有無參構造進行檢測;

package com.wanghaoxin.demo;

import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;

class Emp{
    public Emp(){}
    public Emp(String name ){}
    public Emp(String name,double sal){}
}
@SuppressWarnings("serial")
class NoParamException extends Exception{
    public NoParamException(String msg){
        super(msg);
    }
}
public class TestClassDemo {
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("com.wanghaoxin.demo.Emp");
        System.out.println(chekNoneParam(cls));
        if(!chekNoneParam(cls)){
            throw new NoParamException("沒有無參構造");
        }

    }
    public static boolean chekNoneParam(Class<?> cls){
        Constructor<?> cons[] = cls.getConstructors();
        for(int i=0;i<cons.length;i++){
            if(cons[i].getParameterTypes().length==0){//沒有引數
                return true;
            }
        }
        return false;


    }

}

在反射的處理操作過程之中,最大的特點在於可以利用反射進行指定引數構造的呼叫來實現類物件的例項化操作。
例項化物件:

newInstance
public T newInstance(Object… initargs)
throws InstantiationException,
IllegalAccessException,
IllegalArgumentException,
InvocationTargetException

呼叫指定構造例項化物件:

package com.wanghaoxin.demo;

import java.lang.reflect.Constructor;

class EEmp{
    private String name;
    private double sal;
    public EEmp(String name,double sal){
        this.name=name;
        this.sal=sal;
    }
    @Override
    public String toString() {
        return "姓名:"+this.name+"  sal:"+this.sal;
    }
}
public class TestDemo {
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("com.wanghaoxin.demo.EEmp");
        Constructor<?> cons = cls.getConstructor(String.class,double.class);
        Object obj = cons.newInstance("WANGHAOXIN",800);
        System.out.println(obj);
    }
}

雖然可以實現反射呼叫構造的方式例項化物件,但是實際開發中無法這樣使用,你很難確定要使用的構造方法的引數型別。正因為如此在實際開發中,才要求簡單javal類必須要提供有無參構造方法。

一般情況下,用構造呼叫無參例項化物件,這樣Constructor使用的概率很低。

  • 反射類操作普通方法—息息相關
    在Class類裡面定義有取得方法的操作:
    取得全部的方法:
    —-public Method[] getMethods()
    throws SecurityException
    取得指定方法:
    public Method getMethod(String name,
    Class<?>… parameterTypes)
    throws NoSuchMethodException,
    SecurityException

在Method類中定義有如下取得結構的方法:
取得方法的返回值型別:public Class<?> getReturnType();
取得全部丟擲異常:
public Class<?> getExceptionTypes();

範例:取得全部方法:

package com.wanghaoxin.demo;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

class EEEmp{
    private String name;
    private double sal;
    public EEEmp(String name,double sal){
        this.name=name;
        this.sal=sal;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getSal() {
        return sal;
    }

    public void setSal(double sal) {
        this.sal = sal;
    }
}
public class TestClassMethod {
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("com.wanghaoxin.demo.EEEmp");
        Method meths [] = cls.getMethods();//取得全部方法
        for(int i = 0;i<meths.length;i++){
            System.out.print(Modifier.toString(meths[i].getModifiers())+"  ");//取得方法的訪問修飾符
            System.out.print(meths[i].getReturnType().getName()+"  ");//取得方法的返回值型別
            System.out.print("(");
            Class<?> clsparams [] = meths[i].getParameterTypes();//取得方法引數型別
            for(int j=0;j<clsparams.length;j++){
                System.out.print(clsparams[j].getSimpleName()+"---arg"+j);
                if(j<clsparams.length-1){//後面還有內容的時候輸出逗號
                    System.out.println(",");
                }
            }
            System.out.print(")");
            System.out.print(meths[i].getName());
            Class<?> exs [] = meths[i].getExceptionTypes();//取得異常型別
            if(exs.length>0){
                System.out.print(" throws  ");
            }
            for(int k=0;k<exs.length;k++){
                System.out.print(exs[k]);
                if(k<exs.length-1){//後面還有內容的時候輸出逗號
                    System.out.print(", ");
                }
            }
            System.out.println();
        }
    }
}

Method要比Constructor類要重要,一定要記住在Method類中提供有一個反射呼叫的方法
反射呼叫:
public Object invoke(Object obj,
Object… args)
throws IllegalAccessException,
IllegalArgumentException,
InvocationTargetException
引數:
第一個引數表示類的例項化物件,只有例項化物件才可以呼叫方法
第二個引數:方法需要的引數型別

利用反射呼叫方法:

package com.wanghaoxin.demo;
import java.lang.reflect.Method;

class EEEmp{
    private String name;
    private double sal;
    public EEEmp(){}
    public EEEmp(String name,double sal){
        this.name=name;
        this.sal=sal;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getSal() {
        return sal;
    }

    public void setSal(double sal) {
        this.sal = sal;
    }
}
public class TestClassMethod {
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("com.wanghaoxin.demo.EEEmp");
        Object obj = cls.newInstance();
        Method meth = cls.getMethod("setName", String.class);
        meth.invoke(obj, "zhangsan");//等價於EEEmp物件setName("zhangsan")
        Method getMeth = cls.getMethod("getName");
        System.out.println(getMeth.invoke(obj));



    }
}

這種方法的反射呼叫是可以解決提交引數與簡單java類自動轉換的處理操作。
invoke方法的使用

  • 反射操作成員
    首先在成員裡面必須明確一點,它分為父類成員和子類成員兩個部分。所以對於成員取得就有如下兩種形式:
    1.取得父類成員
    —–取得全部成員:public Field getField(String name)
    throws NoSuchFieldException,
    SecurityException
    —–取得指定成員:public Field[] getFields()
    throws SecurityException
    2.取得子類成員
    —–取得全部成員:public Field getDeclaredField(String name) throws NoSuchFieldException,
    SecurityException
    —–取得指定成員:public Field[] getDeclaredFields()
    throws SecurityException

範例:成員取得:

package com.wanghaoxin.demo;

import java.lang.reflect.Field;
import java.util.Arrays;

interface Person{
     public static final String MSG="wanghaoxin";

}
class Empp implements Person{
    private String job;
    private Double sal;

}
public class TestMemberDemo {
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("com.wanghaoxin.demo.Empp");
        {
            Field fields [] = cls.getDeclaredFields();//獲取子類成員
            System.out.println(Arrays.toString(fields));
        }
        System.out.println("-------------------");{
            Field fields [] = cls.getFields();//獲取父類成員
            System.out.println(Arrays.toString(fields));
        }
    }
}

父類指的是介面
只要說成員,大部分情況下我們只關心普通成員。
於是來觀察Field類中定義的相關操作方法:
取得成員型別:
public Class<?> getType()

package com.wanghaoxin.demo;

import java.lang.reflect.Field;
import java.util.Arrays;

class Empp{
    private String ename;
    private String job;
    private Double sal;

}
public class TestMemberDemo {
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("com.wanghaoxin.demo.Empp");
        {
            Field fields [] = cls.getDeclaredFields();//獲取子類成員

            for(int x=0;x<fields.length;x++){
                System.out.println(fields[x].getName()+"  ,  "
            +fields[x].getType().getSimpleName());
            }
        }
    }
}

除了取得全部的成員之外也可以取得指定名字的成員,取得成員之後,在Field類中提供有成員的直接操作
設定成員內容:public void set(Object obj,
Object value)
throws IllegalArgumentException,
IllegalAccessException

取得成員內容:public Object get(Object obj)
throws IllegalArgumentException,
IllegalAccessException
取消封裝:public void setAccessible(boolean flag)
throws SecurityException

jdk1.8之前:
AccessibleObject下邊有三個子類:Field、Method、Constructor
之後:
這裡寫圖片描述

在set()於get()方法裡邊接受的Object就屬於操作的例項化物件,成員只有在物件例項化之後才會分配空間。

範例:

package com.wanghaoxin.demo;

import java.lang.reflect.Field;
import java.util.Arrays;

class Empp{
    private String ename;
    private String job;
    private Double sal;

}
public class TestMemberDemo {
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("com.wanghaoxin.demo.Empp");
        Object obj = cls.newInstance();//例項化物件開闢堆記憶體
        Field fieldsename = cls.getDeclaredField("ename");//獲取指定子類成員
        fieldsename.setAccessible(true);//取消封裝。如果不取消封裝,會報錯,
        // java.lang.IllegalAccessException: Class com.wanghaoxin.demo.TestMemberDemo can not access a member of class com.wanghaoxin.demo.Empp with modifiers "private"
        fieldsename.set(obj, "wanghaoxin");
        System.out.println(fieldsename.get(obj));
    }
}

封裝並不是萬能的,關鍵是要通過反射才可以動態控制。
從實際開發來講,直接訪問成員,是一件絕對不可能做的事情。必須通過setter和getter來完成。

  • *綜合案例:反射設定屬性
    現在假設有一個Emp類,裡面的結構組成如下:
    簡單java類:
    class Emppp{
    private Integer empno;
    private String ename;
    private String job;
    private Double sal;
    private Date hiredate;
    //set、get方法略

}
現在對於類中所有的屬性的內容都儲存咋了字串之中:
結構:”屬性:內容| 屬性:內容….”
String str = “empno:7369|ename:wanghaoxin|job:CK|sal:800.0|hiredate:2018-12-10”;
目的:將屬性設定給物件
如果要想設定屬性值,必須使用setter()和getter()方法,這就要求屬性的第一個字字母必須大寫,設定工具類

public class StringUtil {
    public static String initcap(String str){
        if(str == null || "".equals(str)){
            return str;
        }
        if(str.length() == 1){
            return str.toUpperCase();
        }else{
            return str.substring(0, 1).toUpperCase()+str.substring(1);
        }

    }
}

工具類:

package com.wanghaoxin.util;

public class ValidateData {
    /**
     * 驗證字串是否為空
     * @param str
     * @return 如果為空返回true,否則返回false
     */
    public static boolean isEmpty(String str){
        return str == null||"".equals(str);

    }
    /**
     * 驗證是否是整數
     * @param str
     * @return 是整數返回true
     */
    public static boolean isNumber(String str){
        if(!isEmpty(str)){//現在內容不是空
            return str.matches("\\d+");
        }
        return false;

    }
    /**
     * 驗證是否是double數
     * @param str
     * @return
     */
    public static boolean isDouble(String str){
        if(!isEmpty(str)){//現在內容不是空
            return str.matches("\\d+(\\.\\d+)?");
        }
        return false;

    }
    /**
     * 驗證是否是日期類
     * @param str
     * @return
     */
    public static boolean isDate(String str){
        if(!isEmpty(str)){//現在內容不是空
            if(str.matches("\\d{4}-\\d{2}-\\d{2}")){
                return true;
            }else{
                return str.matches("\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}");
            }
        }
        return false;

    }
}

範例:反射設定屬性內容:


package com.wanghaoxin.demo;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;

import com.wanghaoxin.util.StringUtil;
import com.wanghaoxin.util.ValidateData;

@SuppressWarnings("serial")
class Emppp implements Serializable{
    private Integer empno;
    private String ename;
    @Override
    public String toString() {
        return "Emppp [empno=" + empno + ", ename=" + ename + ", job=" + job + ", sal=" + sal + ", hiredate=" + hiredate
                + "]";
    }
    private String job;
    private Double sal;
    private Date hiredate;
    public Integer getEmpno() {
        return empno;
    }
    public void setEmpno(Integer empno) {
        this.empno = empno;
    }
    public String getEname() {
        return ename;
    }
    public void setEname(String ename) {
        this.ename = ename;
    }
    public String getJob() {
        return job;
    }
    public void setJob(String job) {
        this.job = job;
    }
    public Double getSal() {
        return sal;
    }
    public void setSal(Double sal) {
        this.sal = sal;
    }
    public Date getHiredate() {
        return hiredate;
    }
    public void setHiredate(Date hiredate) {
        this.hiredate = hiredate;
    }


}
public class TestComDemo {
public static void main(String[] args) throws Exception {
    String str = "empno:7369|ename:wanghaoxin|job:CK|sal:800.0|hiredate:2018-12-10";
    //1.此時沒有明確的Emppp物件,只有Object類物件,而且屬性也使用了字串描述,要取得操作類的class物件

    Class<?> cls = Class.forName("com.wanghaoxin.demo.Emppp");
    //2.例項化類物件,只有例項化後才會開闢堆記憶體
    Object obj = cls.newInstance();
    //3.需要拆分字串獲得屬性名字和屬性值,牽扯到字串轉型
    String result[]  = str.split("\\|");
    for(int i = 0;i<result.length;i++){//要求迴圈設定內容
        //4.進一步拆分內容,區分拿出屬性和內容值
        String temp[] = result[i].split(":");//相當於拆分了屬性和內容
        //5.取得具體操作setter方法名稱
        String methodName = "set" + StringUtil.initcap(temp[0]);
        //6.為了確認setter方法中的引數型別,所以需要取出method的方法的引數型別
        Field filed = cls.getDeclaredField(temp[0]);//取得屬性
        //7這樣可以取得指定的方法物件
        Method met = cls.getMethod(methodName,filed.getType());
        //定義一個object型別,儲存具體的資料
        Object value = null;
        //8、處理資料的轉型
        if("Integer".equals(filed.getType().getSimpleName())||"int".equals(filed.getType().getSimpleName())){
            if(ValidateData.isNumber(temp[1])){
                value = Integer.parseInt(temp[1]);

            }else{
                value = 0;
            }
        }else if("Double".equals(filed.getType().getSimpleName())||"double".equals(filed.getType().getSimpleName())){
            if(ValidateData.isDouble(temp[1])){
                value = Double.parseDouble(temp[1]);

            }else{
                value = 0;
            }
        }else if("Date".equals(filed.getType().getSimpleName())){
            if(ValidateData.isDate(temp[1])){
                if(temp[1].split(" ").length == 1){
                    value = new SimpleDateFormat("yyyy-MM-dd").parse(temp[1]);
                }else{
                    value = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(temp[1]); 
                }


            }else{
                value = 0;
            }
        }else if("String".equals(filed.getType().getSimpleName())){
            if(!ValidateData.isEmpty(temp[1])){
                value = temp[1];
            }
        }
        //10.反射設定具體內容
        met.invoke(obj,value);

    }
    System.out.println(obj);
}
}

實際的開發裡面都是採用此類模式動態設定屬性內容的,所以為什麼有嚴格的setter()和getter()也在於此。

  • 反射取得父類結構
    所謂父結構指的是類可以取得它所繼承的父類以及所實現的所有介面。這些操作在Class類都有支援。

取得一個類的父類:
public Class<? super T> getSuperclass()
取得所有的實現父介面:
public Class<?>[] getInterfaces()

範例:觀察父類取得:(為代理設計模式解決)

package com.wanghaoxin.demo;

import java.io.Serializable;

interface IMess{

}
@SuppressWarnings("serial")
class Dept implements IMess,Serializable,Cloneable{

}
public class TestFather {
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("com.wanghaoxin.demo.Dept");
        System.out.println("父類:"+cls.getSuperclass());//輸出  父類:class java.lang.Object
        Class<?> si [] = cls.getInterfaces();
        for(int i = 0;i < si.length ; i++){
            System.out.println(si[i]);
        }
/*      輸出
        interface com.wanghaoxin.demo.IMess
        interface java.io.Serializable
        interface java.lang.Cloneable*/
    }
}

獲取父類介面此處操作是為了代理模式設計做鋪墊的。

  • 總結
    所以反射機制最大的用處在於可以反射呼叫setter方法設定屬性內容。
    利用反射請求引數可自動轉化為obj物件的操作。