JAVA筆記_反射reflection


反射

reflection是Java被視為動態語言的關鍵
反射機制允許程序在執行期借助reflection API取得任何class的訊息以及直接操作object的內部屬性及方法

當檔案中編譯完class,便會在stack方法區中產生一個Class類的object
且一個class只會產生一個Class object (同種class都會有一共同Class object)
我們透過此Class object便可以了解class的內部結構,因此稱之為反射

優點: 可以動態創建object和編譯增大靈活性
缺點: 效能低下及安全隱憂

e.g.

package reflection;

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

        //通過reflection來取得class的Class object(c1)
        Class c1 = Class.forName("reflection.User");
        System.out.println(c1); //class reflection.User

        //觀察是否都是同個Class物件,因為class加載後只有同一個Class物件
        Class c2 = Class.forName("reflection.User"); //681842940
        Class c3 = Class.forName("reflection.User"); //681842940
        Class c4 = Class.forName("reflection.User"); //681842940

        System.out.println(c2.hashCode());
        System.out.println(c3.hashCode());
        System.out.println(c4.hashCode());


    }
}

//實體class
class User{

    private int id;
    private String name;
    private int age;

    public User(){  
    }

    public User(String name, int id,int age){
        this.name=name;
        this.id=id;
        this.age=age;
    }

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

    public int getId(){return id;}
    public void setId(int id){this.id=id;}

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

e.g. 如何建立Class類別

package reflection;

//如何創立Class類別
public class reflection2 {
    public static void main(String args[]) throws ClassNotFoundException{
        Person person = new Student();
        System.out.println("現在為:"+person.name);

        //方式一 通過object獲得
        Class c1 = person.getClass();
        System.out.println(c1.hashCode());

        //方式二 forname獲得
        Class c2 = Class.forName("reflection.Student");
        System.out.println(c2.hashCode());

        //方式三 通過class獲得
        Class c3 = Student.class;
        System.out.println(c3.hashCode());

        //獲得object父類別
        Class c4 = c1.getSuperclass();
        System.out.println(c4);
    }
}

class Person{
    String name;
    public Person(){}
    public Person(String name){this.name=name;}
}

class Student extends Person{
    public Student(){
        this.name="學生";
    }
}

class Teacher extends Person{
    public Teacher(){
        this.name="老師";
    }
}

現在為:學生
1392838282
1392838282
1392838282
class reflection.Person

其中方法四我們可以看見充分展現reflection特性
可以藉由object去反推得知有關其class的訊息

使用類的Class物件獲得訊息

儲存Class的屬性:

  • public - getFields()
  • 指定 - getDeclaredField()

儲存Class的方法:

  • public - getMethods()
  • all - getDeclaredMethods()
  • 指定 - getDeclaredMethods(paramenterType)

儲存Class的構造器:

  • public - getConstructors()
  • all - getDeclaredConstructors()
  • 指定 - getDeclaredConstructors(paramenterType)
package reflection;

import java.lang.reflect.*;

public class reflection5 {
    public static void main(String args[]) throws ClassNotFoundException, NoSuchFieldException, SecurityException, NoSuchMethodException{
        Class c1 = Class.forName("reflection.User");

        //獲得class的名字
        System.out.println(c1.getName()); //獲得包名+類名
        System.out.println(c1.getSimpleName()); //獲得類名

        //獲得類的屬性
        System.out.println("===========================");
            //只能獲得public屬性
        Field[] fields=c1.getFields();   
        for(Field field : fields){
            System.out.println(field);
        }

        //獲得特定屬性值得方法
        Field name = c1.getDeclaredField("name");
        System.out.println(name);

        //獲得類的方法
        System.out.println("===========================");
            //獲得類的public方法
        Method[] methods = c1.getMethods();
        for(Method method: methods){
            System.out.println(method);
        }

            //獲得類的全部方法
        methods=c1.getDeclaredMethods();
        for(Method method: methods){
            System.out.println(method);
        }

            //獲得類的指定方法
        Method getName = c1.getMethod("getName",null);
        Method setName = c1.getMethod("setName", String.class);
        System.out.println(getName);


        //獲得類的構造器
        System.out.println("===========================");
            //獲得類的public構造器
        Constructor[] constructors = c1.getConstructors();
        for(Constructor c : constructors){
            System.out.println(c);
        }
            //獲得類的全部構造器
        constructors = c1.getDeclaredConstructors();
        for(Constructor c : constructors){
            System.out.println(c);
        }

            //獲得指定的構造器
        Constructor declareC = c1.getDeclaredConstructor(String.class,int.class,int.class);
        System.out.println(declareC);
    }
}

reflection.User
User
......................................................
private java.lang.String reflection.User.name
......................................................
public java.lang.String reflection.User.getName()
public void reflection.User.setName(java.lang.String)
public int reflection.User.getId()
public void reflection.User.setId(int)
public void reflection.User.setAge(int)
public int reflection.User.getAge()
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()

利用反射完成一般程序的class功能

得到class的 method,field,constructor,用constructor去創立object

package reflection;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class reflection6 {
    public static void main(String args[]) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException, NoSuchFieldException{
        Class c1 = Class.forName("reflection.User");

        //創立一個object
        User user = (User) c1.newInstance();
        System.out.println(user);

        //通過constructor建立object
        Constructor c = c1.getDeclaredConstructor(String.class,int.class,int.class);
        User user2 = (User) c.newInstance("廷恩",001,18);
        System.out.println(user2);

        //通過反射調用普通方法
        User user3 = (User) c1.newInstance();
            //通過反射獲取方法
        Method setName = c1.getDeclaredMethod("setName",String.class);
            //invoke激活方法 (object,方法的值)
        setName.invoke(user3, "LTN");
        System.out.println(user3.getName());

        //通過反射操作屬性
        System.out.println("-------------------------------------------");
        User user4 =(User) c1.newInstance();
        Field name = c1.getDeclaredField("name");
            //不能操作private屬性
        name.setAccessible(true); //關掉private的權限限制
        name.set(user4,"Jimmy");
        System.out.println(user4.getName());
    }
}

reflection.User@5305068a
reflection.User@1f32e575
LTN
...................................
Jimmy

解說

invoke:
Method setName = c1.getDeclaredMethod("setName",String.class);
因為此段程式碼的主要目的是抓取特定的方法,但其參數還不確定
此時就需要invoke將方法激活,讓參數與作用object連結
使方法能以特定參數作用在特定的物件上
方法.invoke(object,方法的參數)

從setAccessible談論性能問題

物件的Method,Field,Constructo都有setAccessible()方法
其作用是可以將private檢測關閉,讓程序可以直接的使用private的內容不需先驗證

package reflection;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class reflection7 {

    //普通方式
    public static void test1(){
        User user = new User();
        long start = System.currentTimeMillis();
        for(int i=0;i<10000000;i++){
            user.getName();
        }
        long end = System.currentTimeMillis();
        System.out.println("普通調用方式"+(end-start)+"ms");
    }

    //反射方式
    public static void test2() throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{
        User user = new User();

        Class c1 = user.getClass();
        Method getName = c1.getDeclaredMethod("getName", null);
        getName.setAccessible(true);

        long start = System.currentTimeMillis();
        for(int i=0;i<10000000;i++){
            getName.invoke(user, null);
            user.getName();
        }
        long end = System.currentTimeMillis();
        System.out.println("反射調用方式"+(end-start)+"ms");
    }

    //關閉檢測的反射方式
    public static void test3() throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{
        User user = new User();

        Class c1 = user.getClass();
        Method getName = c1.getDeclaredMethod("getName", null);

        long start = System.currentTimeMillis();
        for(int i=0;i<10000000;i++){
            getName.invoke(user, null);
            user.getName();
        }
        long end = System.currentTimeMillis();
        System.out.println("關閉檢測的反射方式"+(end-start)+"ms");
    }

    public static void main(String args[]) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{
        test1();
        test2();
        test3();
    }
}

普通調用方式6ms
反射調用方式1534ms
關閉檢測的反射方式611ms
-->因此在反射很多的code中可以用關閉檢測增加執行效率

初談類的加載過程

java中的class加載分為三個過程:

  • 加載: 將class程式碼載入並且生成java.lang.Class object
  • 鏈接: 1.驗證加載內容符合JVM規範 2.為class的static變量分配內存及設定默認初始值
  • 初始化: 執行()方法將所有static block做合併動作
    e.g.
package reflection;

public class reflection3 {
    public static void main(String args[]){
        A a = new A();
        System.out.println(A.m);
    }
}

class A{
    static{
        System.out.println("A類static block初始化");
        m=300;
    }
    static int m=100;

    public A(){
        System.out.println("A類constructor初始化");
    }
}

A類static block初始化
A類constructor初始化
100

解說:

加載至內存 產生一個類對應的Class object
再進行鏈結初始static --> m=0

new object之後直接由Class object去抓取相關初始化資料
在()中已經將static block合併
之後程式碼便開始執行

static{
        System.out.println("A類static block初始化");
        m=300;
    }
static int m=100;

合併之後m=300 --> m=100 -->最終m=100

甚麼時候會發生class初始化

class主動引用(class一定會初始化):
1.當new object和反射
2.且當子類別用到父類的變量時一定要先初始化父類
3.main類一定會被初始化
其餘狀況皆為class被動引用(class不會發生初始化)

package reflection;

public class reflection4 {

    static {
        System.out.println("Main類被加載");
    }

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

        //1.主動引用
        Son son = new Son();
        /*
        Main類被加載
        父類別被加載
        子類別被加載
        */

        //2.反射
        Class.forName("reflection.Son");
        /*
        Main類被加載
        父類別被加載
        子類別被加載
        */

        //3.不會被引用
        System.out.println(Son.b);
        /*
        Main類被加載
        父類別被加載
        2
        */

        Son[] s = new Son[5];
        /*
        Main類被加載
        */

        System.out.println(Son.M);
        /*
        Main類被加載
        */
    }
}

class Father{
    static int b =2;
    static{
        System.out.println("父類別被加載");
    }
}

class Son extends Father{
    static{
        System.out.println("子類別被加載");
        m=300;
    }
    static int m=100;
    static final int M=1;
}






你可能感興趣的文章

Angular 9 SCSS Global Variable

Angular 9 SCSS Global Variable

學習 DNS, CDN, Cloudflare

學習 DNS, CDN, Cloudflare

Java 學習筆記 09 – 常用類別套件

Java 學習筆記 09 – 常用類別套件






留言討論