探索JAVA系列(一)探祕Java反射(Reflect)

小橘子2222發表於2019-02-21

前言

反射是java開發進階的一項重要內容,在我們使用的框架如spring中的aop中就有其最佳實踐的案例,我們有必要熟悉其Api,並且深刻理解它的作用,今天就讓我們一起來看看java的反射-reflect吧。

正篇

一:java反射載入類的三種方式

首先建立測試類:
介面類

package com.lly.springtest1.reflect;

/**
 * @ClassName IPerson
 * @Description TODO
 * @Author lly
 * @Date 2019/2/21 2:42 PM
 * @Version 1.0
 **/
public interface IPerson {
    void sayHello();
}

複製程式碼

實現類

package com.lly.springtest1.reflect;

/**
 * @ClassName ChineseEntity
 * @Description TODO
 * @Author lly
 * @Date 2019/2/20 3:24 PM
 * @Version 1.0
 **/
public class ChineseEntity implements IPerson{
    private String name;
    private int age;
    private String phone;
    public String addres;
    public ChineseEntity() {
    }
    public ChineseEntity(String name, int age, String phone) {
        this.name = name;
        this.age = age;
        this.phone = phone;
    }
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getPhone() {
        return phone;
    }
    public void setPhone(String phone) {
        this.phone = phone;
    }
    @Override
    public String toString() {
        return "ChineseEntity{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", phone='" + phone + '\'' +
                '}';
    }
    public String getAddres() {
        return addres;
    }
    public void setAddres(String addres) {
        this.addres = addres;
    }
    private void getTestName(){
    }
    @Override
    public void sayHello() {
        System.out.println("Chinese say hello");
    }
    public void test(String word) {
        System.out.println("testWord:"+word);
    }
}

複製程式碼
package com.lly.springtest1.reflect;

/**
 * @ClassName ChineseEntity
 * @Description TODO
 * @Author lly
 * @Date 2019/2/20 3:24 PM
 * @Version 1.0
 **/
public class AmericanEntity implements IPerson{

    private String name;
    private int age;
    private String phone;
    public String addres;
    public AmericanEntity() {
    }

    public AmericanEntity(String name, int age, String phone) {
        this.name = name;
        this.age = age;
        this.phone = phone;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    @Override
    public String toString() {
        return "ChineseEntity{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", phone='" + phone + '\'' +
                '}';
    }

    public String getAddres() {
        return addres;
    }

    public void setAddres(String addres) {
        this.addres = addres;
    }

    private void getTestName(){
    }

    @Override
    public void sayHello() {
        System.out.println("American say hello");
    }
}
複製程式碼

1:Class.forName()

public static void typeOne() {
        try {
            log.info("通過類的全路徑獲取類");
//            動態載入類,在需要的時候才載入所需要的類
            Class<?> pClass = Class.forName("com.lly.springtest1.reflect.ChineseEntity");
            log.info("獲取該類所有的屬性");
            Arrays.asList(pClass.getDeclaredFields()).stream().forEach(e -> System.out.println(e.getName()));
            log.info("獲取該類所有的所有公開屬性");
            Arrays.asList(pClass.getFields()).stream().forEach(e -> System.out.println(e.getName()));
            log.info("獲取類的例項");
            ChineseEntity person = (ChineseEntity) pClass.newInstance();
            person.sayHello();
            person.setAge(10);
            log.info("列印類的一個屬性");
            System.out.println("年齡:" + person.getAge());
            log.info("返回某個類的所有公用(public)方法包括其繼承類的公用方法,當然也包括它所實現介面的方法");
            Arrays.asList(pClass.getMethods()).stream().forEach(e -> {
                System.out.print(e.getName() + "(");
                Arrays.asList(e.getParameterTypes()).stream().forEach(e1 -> System.out.print(e1.getName() + ","));
                System.out.println(")");
                return;
            });
            log.info("類或介面宣告的所有方法,包括公共、保護、預設(包)訪問和私有方法,但不包括繼承的方法。當然也包括它所實現介面的方法");
            Arrays.asList(pClass.getDeclaredMethods()).stream().forEach(e -> System.out.println(e.getName()));
            log.info("獲取指定的方法");
            Method test = pClass.getDeclaredMethod("test", String.class);
            //方法有返回值,返回實際的值.沒有返回值返回null
            Object person1 = test.invoke(person, "person");
            System.out.println(person1);

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

    }
複製程式碼

2:Object.class

    public static void typeTwo() {
//       靜態載入需要的類
        Class<ChineseEntity> pClass = ChineseEntity.class;
        Arrays.asList(pClass.getDeclaredFields()).stream().forEach(e -> System.out.println(e.getName()));
    }

複製程式碼

3:new Object()

    public static void typeThree() {
//        new 物件是靜態載入類,在編譯時刻就需要載入所有需要可能的類
        ChineseEntity chineseEntity = new ChineseEntity();
        Class<? extends ChineseEntity> aClass = chineseEntity.getClass();
        Arrays.asList(aClass.getDeclaredFields()).stream().forEach(e -> System.out.println(e.getName()));

    }
複製程式碼

執行結果

15:33:58.553 [main] INFO com.lly.springtest1.reflect.ReflectTest - 通過類的全路徑獲取類
15:33:58.561 [main] INFO com.lly.springtest1.reflect.ReflectTest - 獲取該類所有的屬性
name
age
phone
addres
15:33:58.687 [main] INFO com.lly.springtest1.reflect.ReflectTest - 獲取該類所有的所有公開屬性
addres
15:33:58.688 [main] INFO com.lly.springtest1.reflect.ReflectTest - 獲取類的例項
Chinese say hello
15:33:58.688 [main] INFO com.lly.springtest1.reflect.ReflectTest - 列印類的一個屬性
年齡:10
15:33:58.688 [main] INFO com.lly.springtest1.reflect.ReflectTest - 返回某個類的所有公用(public)方法包括其繼承類的公用方法,當然也包括它所實現介面的方法
toString()
getName()
setName(java.lang.String,)
test(java.lang.String,)
sayHello()
setAge(int,)
getAge()
setPhone(java.lang.String,)
getAddres()
getPhone()
setAddres(java.lang.String,)
wait(long,int,)
wait(long,)
wait()
equals(java.lang.Object,)
hashCode()
getClass()
notify()
notifyAll()
15:33:58.691 [main] INFO com.lly.springtest1.reflect.ReflectTest - 類或介面宣告的所有方法,包括公共、保護、預設(包)訪問和私有方法,但不包括繼承的方法。當然也包括它所實現介面的方法
toString
getName
setName
test
sayHello
setAge
getAge
setPhone
getAddres
getPhone
setAddres
getTestName
15:33:58.691 [main] INFO com.lly.springtest1.reflect.ReflectTest - 獲取指定的方法
testWord:person
null

Process finished with exit code 0

複製程式碼
二:java反射的實際應用

瞭解過設計模式的同學肯定知曉工廠模式,該模式是幫助我們獲取一個類的例項,那麼其中實現的原理是什麼呢,沒錯,就是java的反射,通過反射獲取到具體類的例項,上面我們已經定義了2個實體類中國人和美國人,並且實現了IPerson介面,這樣我們在獲取其中一個國家人的例項的時候,通常我們會用

ChineseEntity chineseEntity = new ChineseEntity();
複製程式碼

這種寫法,但是如果我們以後要獲取到美國人或者其他國家的人恩,是不是就需要修改程式碼了,那麼我們就用了介面的有點,實現統一的標準,方便擴充套件,再利用工廠類來獲取我們指定的例項就大大優化了我們打程式碼,下面我們來實現以下
工廠類

package com.lly.springtest1.reflect;

/**
 * @ClassName PersonFactory
 * @Description persion工廠類
 * @Author lly
 * @Date 2019/2/22 2:44 PM
 * @Version 1.0
 **/
public class PersonFactory {

    public static IPerson getiPersonInstance(String className) {
        IPerson iPerson = null;
        try {
            iPerson = (IPerson) Class.forName(className).newInstance();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
        return iPerson;
    }
}

複製程式碼

測試類

package com.lly.springtest1.reflect;

/**
 * @ClassName ReflectAndFactoryTest
 * @Description TODO
 * @Author lly
 * @Date 2019/2/22 2:50 PM
 * @Version 1.0
 **/
public class ReflectAndFactoryTest {
    public static void main(String[] args) {
        IPerson chinese = PersonFactory.getiPersonInstance("com.lly.springtest1.reflect.ChineseEntity");
        chinese.sayHello();
        IPerson american = PersonFactory.getiPersonInstance("com.lly.springtest1.reflect.AmericanEntity");
        american.sayHello();
    }
}
複製程式碼

結果

探索JAVA系列(一)探祕Java反射(Reflect)
利用反射動態生成我們需要的例項物件,這裡有一點需要注意的使我們必須要知道類的全名,我們可以將全路徑類名和簡單類名做一個k-v的對映配置在java配置類中,這樣我們只需要修改配置類就可以了

三:關於setAccessible()方法

首先我們來看看jdk中的解釋:
將此物件的 accessible 標誌設定為指示的布林值。值為 true 則指示反射的物件在使用時應該取消 Java 語言訪問檢查。值為 false 則指示反射的物件應該實施 Java 語言訪問檢查。並不是代表反射類的屬效能否訪問的開關,而是是否檢查語言訪問,我們來做個試驗

 Method test = pClass.getDeclaredMethod("test", String.class);
            //方法有返回值,返回實際的值.沒有返回值返回null
            long stime = System.currentTimeMillis();
            for (int i = 0; i < 10000; i++) {
                test.invoke(person, "person");
            }
            long etime = System.currentTimeMillis();
            log.info("時間:{}", etime - stime);

            test.setAccessible(true);
            long stime1 = System.currentTimeMillis();
            for (int i = 0; i < 10000; i++) {
                test.invoke(person, "person");
            }
            long etime1 = System.currentTimeMillis();
            log.info("時間:{}", etime1 - stime1);
複製程式碼

大家覺得結果是什麼呢,都執行10000次哪個更快呢,結果是

15:47:49.967 [main] INFO com.lly.springtest1.reflect.ReflectTest - 時間:101
15:47:50.038 [main] INFO com.lly.springtest1.reflect.ReflectTest - 時間:68
複製程式碼

事實勝於雄辯,原來是由於JDK的安全檢查耗時較多.所以通過setAccessible(true)的方式關閉安全檢查就可以達到提升反射速度的目的。

總結

反射的三種方式中第一種使我們經常使用的,例如我們在springAop,jdbc底層載入資料庫驅動包等等,其他2種由於我們已經知道了所學要的類,不存在動態載入,所以基本上不使用。

相關文章