Java反射学习笔记

一、理论

1. 什么是反射
通常使用一个类需要先import"包.类" ----> 通过new实例化 ----> 取得实例化对象。而反射:实例化对象 ----> getClass方法 ---->得到完整的“包.类”名称。反射操作中一切的操作都是使用 Object 来完成的,类、数组的引用都可以使用 Object 进行接收。

2. 反射实现原理
每一个类在编译时都会生成一个 .class 文件,JVM 把每一个 .class 文件加载进内存后就会在内存中为每一个类创建一个 class object,class object 中包含 包.类名称、构造方法、方法、属性。内存中有这个 class object 之后我们就可以使用它来实例化对象。注意在内存中一个 class 只有一个 class object,这个 class object 是用来描述类本身的。

3. 反射对象实例化
有三种方法来使用 class object 来实例化对象:

(1) Class<?> c = Class.forName("包.类"); //c是一个泛型。
(2) Class<?> c = new X().getClass(); //X是类名
(3) Class<?> c = X.class

然后使用 c.newInstance() 来实例化对象.

//错:
Person p = c.newInstance(); //没有import Persion就不能直接使用Person
//对:
Object p = c.newInstance(); //Object类是所有类的父类,newInstance获取一个Person类,再向上转化为Object类。

4. 对于一个类在内存中有一个 class object, 对于基本数据类型,它也是一个类,它里面也有 class,eg: int.class

5. 使用反射后就不需要 import 类了。但是没有 import Person 类,就不能直接使用 Person 类及其成员。

6. 若不想处理一个函数中的异常,又想编译时不报 error,就可以加 throws Exception 把异常给扔出去。

//eg:
public static void main(String args[]) throws Exception {
}

7. newInstance() 实际上是去调用那个类的无参构造方法。若是没有写,类里面会有一个默认的什么都不做的无参构造方法(和C++一样)。若想使用有参构造方法来实例化对象,首先要获得它的构造方法,方法如下:
Constructor<?> con = c.getConstructor(class...<?> parameterTypes); //注意参数类型也是一个class

//eg:
Constructor<?> con = c.getConstructor(String.class); //获取构造方法,String为参数类型,要传入String.class
Object P2 = con.newInstance("XiaoMing");

使用 Constructor 需要 import java.lang.reflect.Constructor; 参考Android开发官网。

8. 获得并调用类的方法

Method meth = c.getMethod(String name, Class...<?>parameterTypes); //参数:成员方法名,成员方法的参数类型
Object obj = meth.invoke(Object receiver, Object...args);

对于静态方法,invoke 的第一个参数可以写为null.

9. 通过反射获取设置类/对象的属性
有2种方法:
(1) 使用 getMethod 获得 getter, setter 方法,然后 invoke 这些方法来实现读取/设置属性。

(2) 使用 Field
① 获得属性

Field f = c.getField(String name); //获得公共属性,此方法会先检索本类,再检索接口,最后检索其父类(?)。
Field f = c.getDeclaredField(String name); //获得类中名为name的属性

② 设置为可被外部访问(注意它会破坏类的封装性)

f.setAccessible(true);

③ 调用 get/set 方法

Object obj = f.get(Object objet);    //object为实例化对象
f.set(Object object, Object value);

注意:getField 只能获得公共属性,这些公共属性要么是本类的,要么是它实现的接口的。要么是他的父类的。若想获得本类的所有属性可以使用 getDeclaredField,它可以获取私有属性也可以是其它的public属性。

10. 使用反射的好处
(1) 增加程序的灵活性,比如可以通过参数传入类的名称,也可以通过配置文件传入类的名称,然后实例化出不同的对象。

11. Me: 一个 .java 文件中只能定义一个 public class, 但是可以定义多个非 public 的 class。
error: class Reflect is public, should be declared in a file named Reflect.java

12. Me: javac 编译一个 .java 文件后,其内部的每一个类 N 都会生成一个 N.class 文件。

 

二、例子

1. demo1:Reflect.java实现三种方法实例化对象

package a.b.c.d;

class Person {
    private String name;

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

public class Reflect {
    public static void main(String args[]) {
        Person p = new Person();
        Class<?> c1 = null;

        try {
            c1 = Class.forName("a.b.c.d.Person");    //法一: 根据"包.类"来实例化对象
        } catch (ClassNotFoundException e) {
            System.out.println(e);
        }
        
        Class<?> c2 = p.getClass();        //法二
        Class<?> c3 = Person.class;     //法三

        System.out.println(c1.getName());
        System.out.println(c2.getName());
        System.out.println(c3.getName());

        int arr1[] = {1,2,3};
        int arr2[] = {1,2,3,4};
        int arr3[][] = {{1,2,3,4},{1}};

        Class<?> c4 = arr1.getClass();
        Class<?> c5 = arr2.getClass();
        Class<?> c6 = arr3.getClass();

        Class<?> c7 = int.class;            //基本类型也有一个class

        System.out.println(c4.getName());
        System.out.println(c5.getName());
        System.out.println(c6.getName());
        System.out.println(c7.getName());

        System.out.println((c4 == c5));    //都是一维数组,类别是一样的,这里是ture
        System.out.println((c4 == c6)); //false 一维数组和二维数组
    }
}

/*
$ javac -d . Reflect.java 
$ java a.b.c.d.Reflect
a.b.c.d.Person
a.b.c.d.Person
a.b.c.d.Person
[I
[I
[[I
int
true
false
*/

 

2. demo2: 使用成员方法和属性和反射增加灵活性

Person.java

package a.b.c.d;

public class Person {
    public String name;

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

    public Person() {
        System.out.println("Constructor of Person");
    }
    public Person(String name) {
        this.name = name;
        System.out.println("Constructor2 of Person, name is "+this.name);
    }
};

Student.java

package a.b.c.d;

public class Student {
    public String name;

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

    public Student() {
        System.out.println("Constructor of Student");
    }
    public Student(String name) {
        this.name = name;
        System.out.println("Constructor2 of Student, name is "+this.name);
    }
};

Reflect.java

//import a.b.c.d.Person;    //反射不需要import了

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

public class Reflect {
    public static void main(String args[]) throws Exception {    //不想处理类中的异常,扔掉异常以免编译不过
        Class<?> c = null;

        try {
            c = Class.forName("a.b.c.d.Person");    //实例化对象,传入参数"a.b.c.d.Person"可以在运行时更改,改为args[1]获得灵活性
        } catch (ClassNotFoundException e) {
            System.out.println(e);
        }

        Object p = null;    //由于没有import Person不能直接使用Persion类,Object类是所有类的父类

        try {
            p = c.newInstance(); //这里默认使用的是无参的构造函数
        } catch (InstantiationException e) {
            System.out.println(e);
        }

        /*
         * 获取构造函数,想使用有参构造函数,参数为String,因此这里
         * 传参是String.class
         */
        Constructor<?> con = c.getConstructor(String.class);
        Object p2 = con.newInstance("xiaoming"); /*调用有参构造函数*/

        /*获取“setName()”成员方法*/
        Method set = c.getMethod("setName", String.class);
        /*分别是实例化对象p2和p调用setName()方法*/
        set.invoke(p2, "123");
        set.invoke(p, "abc");

        Method get = c.getMethod("getName");
        System.out.println(get.invoke(p));
        System.out.println(get.invoke(p2));

        /*获取属性成员name*/
        Field name = c.getDeclaredField("name");

        /*
         * 可以看出在使用setAccessible的时候破坏了类的封装性,name属性本来是私有的,
         * 现在强制被设置为可以被外界访问.
         * 所以一般不介意使用这个方法来操作成员变量,而是通过set/get方法访问。
         */
        //name.setAccessible(true); /*不设置它会产生异常,若是把name属性指定为public的,就不用设置了。*/

        /*设置属性成员name的值*/
        name.set(p, "www");    //设置p这个实例化对象的name属性,注意设置哪个对象的属性
        name.set(p2, "100ask");
        System.out.println(name.get(p));
        System.out.println(name.get(p2));
        
    }
}

使用:

/*
$ javac -d . Person.java 
$ javac -d . Student.java 
$ javac Reflect.java 
$ java Reflect a.b.c.d.Person
Constructor of Person
Constructor2 of Person, name is xiaoming
abc
123
www
100ask
$ 
$ java Reflect a.b.c.d.Student
Constructor of Student
Constructor2 of Student, name is xiaoming
abc
123
www
100ask
$
*/

 

2. demo2: 静态函数反射中直接通过类调用

//在Person.java中加:
public static void treaceEnable(String enable) { System.out.println("enable=" + enable); }

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

//Reflect.java 改为:
public class Reflect {
    public static void main(String args[]) throws Exception {
        Class<?> c = null;

        try {
            c = Class.forName("a.b.c.d.Person");
        } catch (ClassNotFoundException e) {
            System.out.println(e);
        }

        Method set = c.getMethod("treaceEnable", String.class);
        set.invoke(null, "disable");
    }
}

/*
$ javac -d . Person.java
$ javac Reflect.java
$ java Reflect 
enable=disable
*/

注:若是找不到 "a.b.c.d.Person" 类, Class.forName("a.b.c.d.Person") 直接异常,不会给你判断返回是否是null的机会。

 

3. demo3: 反射单例模式的成员函数

Person.java类是单例模式

package a.b.c.d;

public class Person {
    public String name;
    public static Person mPerson = null;

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

    private Person() {
        System.out.println("Constructor of Person");
    }

    private Person(String name) {
        this.name = name;
        System.out.println("Constructor2 of Person, name is "+this.name);
    }
    public static Person getInstance() {
        if (mPerson == null) {
            mPerson = new Person();
        }
        return mPerson;
    }
    public void TraceEnable(boolean enable) { System.out.println("enable=" + enable); }
};

Reflect.java:

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

class Trace {
    private Trace() { System.out.println("Trace() called"); }
    private static Trace sInstance = null;
    private static Method mTraceEnable = null;
    private static Object mp = null;

    private void setTraceEnable(boolean enable) throws Exception {
        Class<?> c = null;

        c = Class.forName("a.b.c.d.Person");

        Method getInstance = c.getMethod("getInstance");
        Object p = getInstance.invoke(c);

        Method TraceEnable = c.getMethod("TraceEnable", boolean.class);
        TraceEnable.invoke(p, enable);

        // cache for next use
        mTraceEnable = TraceEnable;
        mp = p;
    }

    public static Trace getInstance() {
        if (sInstance == null) {
            sInstance = new Trace();
        }
        return sInstance;
    }

    public static void TraceEnable(boolean enable) {
        if (mTraceEnable == null) {
            Trace ut = getInstance();
            try {
                ut.setTraceEnable(enable);
            } catch (Exception e) {
                System.out.println(e);
            }
        } else {
            try {
                mTraceEnable.invoke(mp, enable);
            } catch (Exception e) {
                System.out.println(e);
            }
        }
    }
}

public class Reflect {
    public static void main(String args[]) {
        Trace.TraceEnable(true);
        Trace.TraceEnable(false);
    }
}

/*
$ java Reflect 
Trace() called
Constructor of Person
enable=true
enable=false
*/

若是先获取到了单例对象,可以:

sMethodTrace = instance.getClass().getMethod("TraceEnable", boolean.class);
sMethodTrace.invoke(instance, enable);

如:

class RefectDemo {
    static final String TAG = "RefectDemo";
    private static Method sMethod;

    public static void refectDemoTest(boolean enable) {
        //instance 是通过单例模式获取的实例
        if (instance != null) {
            if (sMethod == null) {
                try {
                    sMethod = instance.getClass().getMethod("refectDemoTest", boolean.class);
                } catch (NoSuchMethodException e) {
                    Log.i(TAG, "refectDemoTest: ", e);
                    return;
                }
            }

            if (sMethod != null) {
                try {
                    sMethod.invoke(instance, enable);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        } else {
            Log.i(TAG, "Can't get an XXX instance.");
        }
    }
}

 

三、补充

1. sMethod.invoke(null, scene, enable); 即使是只有2个参数的静态函数,instance位置也要传null,否则报错:

W System.err: java.lang.IllegalArgumentException: Wrong number of arguments; expected 2, got 1
W System.err:     at java.lang.reflect.Method.invoke(Native Method)
W System.err:     at com.example.myapp4.XPBoost.refectDemoTest(MainActivity.java:45)

 

 

 

 

 

 

 

sMethod.invoke(null, scene, enable);

posted on 2019-02-21 00:35  Hello-World3  阅读(225)  评论(0编辑  收藏  举报

导航