Java

java 反射

2021-09-09  本文已影响0人  zhbi98

1. 什么是反射操作

通过Class实例获取class信息的方法称为反射(Reflection),反射的目的是为了获得某个实例的信息,JVM为每个加载的class创建了对应的Class实例,并在实例中保存了该class的所有信息,包括类名、包名、父类、实现的接口、所有方法、字段等,因此,如果获取了某个Class实例,我们就可以通过这个Class实例获取到该实例对应的class的所有信息。

2. Class类

我们要怎么才能够获取一个class的Class实例呢,这里有三个方法

2.1. 直接通过一个class的静态变量class获取:

Class c = String.class;
--------------------------------
class java.lang.String

2.2. 如果我们有一个实例变量,可以通过该实例变量提供的getClass()方法获取:

String s = "Hello, World!";

Class c = s.getClass();
--------------------------------
class java.lang.String

2.3. 如果知道一个class的完整类名,可以通过静态方法Class.forName()获取:

在这里 java.lang.String 就是一个class的完整类名,这里检测异常是因为可能存在非法字符,或指定的类名不存在。

try {
    // 在这里 java.lang.String 就是一个class的完整类名
    Class c = Class.forName("java.lang.String");
    System.out.println(c);
} catch (NullPointerException e) {
    System.out.println("Error");
}

因为Class实例在JVM中是唯一的,所以上述方法获取的Class实例是同一个实例。

类可以直接通过类名获取,也可以通过类实例化后的对象获取,例如下方的例子:

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


class Student {
    public int score;
    private int grade;
}


public class ReflectionLearn {
    public static void main(String[] args) throws Exception {
        Class stdClass1 = Student.class;

        Student student = new Student();
        Class stdClass2 = student.getClass();

        System.out.println(stdClass1);
        System.out.println(stdClass2);
    }
}
---------------------------------------------------------------------
class com.zhbi.source.Student
class com.zhbi.source.Student

\color{#FF0000}{无论是使用类获取还是使用类的对象获取的Class他们都是相同的,或说获取到的是同一个Class。}


3. 访问字段(Field)

对任意的一个Object实例,只要我们获取了它的Class,就可以获取它的一切信息。Class类提供了以下几个方法来获取字段

  • Field getField(name):根据字段名获取某个public的field(包括父类)
  • Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)
  • Field[] getFields():获取所有public的field(包括父类)
  • Field[] getDeclaredFields():获取当前类的所有field(不包括父类)

使用测试:

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


class Person {
    public String name;
}


class Student extends Person {
    public int score;
    private int grade;
}


public class ReflectionLearn {
    public static void main(String[] args) throws Exception {
        Class stdClass = Student.class;
        // 获取 public 字段 "score":
        System.out.println(stdClass.getField("score"));
        // 获取继承的 public 字段 "name":
        System.out.println(stdClass.getField("name"));
        // 获取 private 字段 "grade":
        System.out.println(stdClass.getDeclaredField("grade"));
    }
}
--------------------------------------------------------------
public int com.zhbi.source.Student.score
public java.lang.String com.zhbi.source.Person.name
private int com.zhbi.source.Student.grade

3.1. 字段包含的信息

一个Field对象包含了一个字段的所有信息,Field含有以下方法:

  • getName():返回字段名称,例如,"name";name 其实就是变量(属性)名称
  • getType():返回字段类型,也是一个Class实例,例如,String.class;
  • getModifiers():返回字段的修饰符,它是一个int,不同的bit表示不同的含义。
import java.lang.reflect.Field;
import java.lang.reflect.Method;


class Student {
    public int score;
    private int grade;
}


public class ReflectionLearn {
    public static void main(String[] args) throws Exception {
        Class stdClass = Student.class;

        // score 其实就是变量(属性)名称,这个名称必须是与被调用变量同名的,
        // 并且加上引号,用于赋值
        Field field = stdClass.getField("score"); 

        System.out.println(field.getName());
        System.out.println(field.getType());
    }
}
--------------------------------------------------------------
score
int

3.2. 获取字段值

利用反射拿到字段的一个Field实例只是第一步,我们还可以拿到一个实例对应的该字段的值。
如果这个实例中存在私有的字段,那么会得到一个IllegalAccessException,这是因为需要访问的这个字段被定义为一个private字段,正常情况下,一个类无法访问另外一个类的private字段。
要修复错误,可以将字段的修饰类型private改为public,或者,在调用 Field.get(p) 之前,先写一句 Field.setAccessible(true); 调用Field.setAccessible(true)的意思是,别管这个字段是不是public,一律允许访问。

类中使用 private 对外界不需要访问的实例属性进行封装,使得外界无法进行随意的访问,或非法的访问从而做到更好的模块封装。但是现在我们可以通过Java的反射来获取 private 属性,这似乎违背了java的封装性。其实不然,反射是一种非常规的用法,使用反射,首先代码非常繁琐,其次,它更多地是给工具或者底层框架来使用,目的是在不知道目标实例任何信息的情况下,获取特定字段的值。此外,setAccessible(true)可能会失败。如果JVM运行期存在SecurityManager,那么它会根据规则进行检查,有可能阻止setAccessible(true)。例如,某个SecurityManager可能不允许对java和javax开头的package的类调用setAccessible(true),这样可以保证JVM核心库的安全。

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


class Student {
    public int score = 100;
    public int grade = 90;
}


public class ReflectionLearn {
    public static void main(String[] args) throws Exception {
        // Student std = new Student();
        // Class stdClass = std.getClass();
        Class stdClass = Student.class;

        Field scoreField = stdClass.getField("score");
        Field gradeField = stdClass.getField("grade");

        System.out.println(scoreField.get(new Student()));
        System.out.println(gradeField.get(new Student()));
    }
}
----------------------------------------------------------
100
90

3.3. 设置字段值

通过Field实例既然可以获取到指定实例的字段值,自然也可以设置字段的值。设置字段值是通过Field.set(Object, Object)实现的,其中第一个Object参数是指定的实例,第二个Object参数是待修改的值。

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


class Student {
    public int score = 100;
    public int grade = 90;
}


public class ReflectionLearn {
    public static void main(String[] args) throws Exception {
        // Student std = new Student();
        // Class stdClass = std.getClass();
        Class stdClass = Student.class;

        Field scoreField = stdClass.getField("score");
        Field gradeField = stdClass.getField("grade");

        Student student = new Student();

        scoreField.set(student, 80);
        gradeField.set(student, 60);

        System.out.println(scoreField.get(student));
        System.out.println(gradeField.get(student));
    }
}
------------------------------------------------------
80
60

4. 调用方法(Method)

通过Class实例可以获取所有Field对象,同样的可以通过Class实例获取所有Method信息。
Class类提供了以下几个方法来获取Method:

  • Method getMethod(name, Class...):获取某个public的Method(包括父类)
  • Method getDeclaredMethod(name, Class...):获取当前类的某个Method(不包括父类)
  • Method[] getMethods():获取所有public的Method(包括父类)
  • Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类)

4.1. 调用实例方法

Method都含有一个invoke方法,因为调用的方法是实例方法,所以传入的第一个参数,为这个方法所在类的实例。

第一种,被调用的方法不带有参数
第二种,被调用的方法带有参数

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

class Student {
    public int score = 100;
    public int grade = 90;

    public void printScore() {
        System.out.println("Score is :" + score);
    }

    public void printGrade(String s) {
        System.out.println(s + "grade is :" + grade);
    }
}


public class ReflectionLearn {
    public static void main(String[] args) throws Exception {
        Student std = new Student();
        // Class stdClass = std.getClass();
        Class stdClass = Student.class;

        // printScore 其实就是被调用方法的名称,这个名称必须是与被调用方法同名的,
        // 并且加上引号,用于赋值
        Method smethod = stdClass.getMethod("printScore");
        smethod.invoke(std);

        // 这里 String.class 带参数方法的参数类型,这里这个参数的类型是 String
        // printGrade 其实就是被调用方法的名称,这个名称必须是与被调用方法同名的,
        // 并且加上引号,用于赋值
        Method gmethod = stdClass.getMethod("printGrade", String.class);
        // 在这里填入参数 "My "
        gmethod.invoke(std, "My ");
    }
}

------------------------------------------------------
Score is :100
My grade is :90

对Method实例调用invoke就相当于调用该方法,invoke的第一个参数是对象实例,即在哪个实例上调用该方法,后面的可变参数要与方法参数一致,否则将报错。

4.2. 调用静态方法

如果获取到的Method表示一个静态方法,调用静态方法时,由于无需指定实例对象,所以invoke方法传入的第一个参数永远为null

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

class StaticFunction {
    public static void printGood(String s) {
        System.out.println(s + "very good!");
    }
}


public class ReflectionLearn {
    public static void main(String[] args) throws Exception {
        // Class stdClass = std.getClass();
        Class stdfClass = StaticFunction.class;

        Method dmethod = stdfClass.getMethod("printGood", String.class);
        // 由于调用的是静态方法,所以不需要创建类的实例即可调用,
        // 所以传递的实例即为null,方法的参数在这里填入参数为 "Im "
        dmethod.invoke(null, "Im ");
    }
}
--------------------------------------------------
Im very good!

4.3. 调用非public方法

和Field有些相似,对于非public的私有方法,我们虽然可以通过Class.getDeclaredMethod()获取该方法实例,但直接对其调用将得到一个IllegalAccessException。为了调用非public方法,我们通过Method.setAccessible(true)允许其调用。

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

class Student {
    public int score = 100;
    public int grade = 90;

    public void printScore() {
        System.out.println("Score is :" + score);
    }

    private void printGrade(String s) {
        System.out.println(s + "grade is :" + grade);
    }
}


public class ReflectionLearn {
    public static void main(String[] args) throws Exception {
        Student std = new Student();
        // Class stdClass = std.getClass();
        Class stdClass = Student.class;

        // 这里 String.class 带参数方法的参数类型,这里这个参数的类型是 String
        // printGrade 其实就是被调用方法的名称,这个名称必须是与被调用方法同名的,并且加上引号
        Method gmethod = stdClass.getMethod("printGrade", String.class);
        
        // 使得私有的方法允许被调用
        gmethod.setAccessible(true);
        // 在这里填入参数 "My "
        gmethod.invoke(std, "My ");
    }
}

5. 调用构造方法

我们通常使用new操作符创建新的实例,如果通过反射来创建新的实例,可以调用Class提供的newInstance()方法,但是,调用Class.newInstance()的局限是,它只能调用该类的public无参数构造方法。如果构造方法带有参数,或者不是public,就无法直接通过Class.newInstance()来调用。

为了调用任意的(有无参数的)构造方法,Java的反射API提供了Constructor对象,它包含一个构造方法的所有信息,可以创建一个实例。Constructor对象和Method非常类似,不同之处仅在于它是一个构造方法,并且,调用结果总是返回实例。

通过Class实例获取Constructor的方法如下:

  • getConstructor(Class...):获取某个public的Constructor;
  • getDeclaredConstructor(Class...):获取某个Constructor;
  • getConstructors():获取所有public的Constructor;
  • getDeclaredConstructors():获取所有Constructor。

注意:Constructor总是当前类定义的构造方法,和父类无关,因此不存在多态的问题。

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

class Student {
    public int score = 100;
    public int grade = 90;

    public Student(String s) {
        System.out.println(s);
    }

    public void printScore() {
        System.out.println("Score is :" + score);
    }

    public void printGrade(String s) {
        System.out.println(s + "grade is :" + grade);
    }
}


public class ReflectionLearn {
    public static void main(String[] args) throws Exception {
        // 这里需要注意被调用类的构造函数必须是声明为public的,否则将会发生 
        // NoSuchMethodException 错误这是因为构造函数没有声明时默认是
        // private私有的,所以无法访问
        
        // String.class 指定的是构造函数的参数类型,这里的参数类型是String
        Constructor cstr = Student.class.getConstructor(String.class);

        // 这里是对类进行实例化,这里传入参数为字符串 "Hello, World!"
        Student std = (Student)cstr.newInstance("Hello, World!");
    }
}
--------------------------------------------------------------
Hello, World!

这里需要注意,即使是使用Constructor,被调用类的构造函数同样必须是声明为public的,否则将会发生 NoSuchMethodException 错误,这是因为构造函数没有声明时默认是private私有的,所以无法访问。也可以通过setAccessible(true)设置允许访问。但是同样setAccessible(true)可能会失败的。

6. 获取父类的Class

有了Class实例,我们还可以获取它的父类的Class,例如下面这样:

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


class Person {
    public int score = 100;
    public int grade = 90;

    public Person() {
        System.out.println("Person");
    }
}


class Student extends Person {
    public int score = 100;
    public int grade = 90;

    public Student(String s) {
        System.out.println(s);
    }

    public void printScore() {
        System.out.println("Score is :" + score);
    }

    public void printGrade(String s) {
        System.out.println(s + "grade is :" + grade);
    }
}


public class ReflectionLearn {
    public static void main(String[] args) throws Exception {
        Class sclass = Student.class;
        Class pclass = sclass.getSuperclass();

        System.out.println(sclass);
        System.out.println(pclass);
    }
}
---------------------------------------------------------
class com.zhbi.source.Student
class com.zhbi.source.Person

7. 获取接口(interfcae)

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


interface Person {
    public void speak();
}


class Student implements Person {
    public int score = 100;
    public int grade = 90;

    public Student(String s) {
        System.out.println(s);
    }

    public void printScore() {
        System.out.println("Score is :" + score);
    }

    public void printGrade(String s) {
        System.out.println(s + "grade is :" + grade);
    }

    @Override
    public void speak() {
        System.out.println("Speak");
    }
}


public class ReflectionLearn {
    public static void main(String[] args) throws Exception {
        Class sclass = Student.class;
        
        // 这里需要定义为一个数组,这是因为一个类可以实现多个接口
        Class[] iclass = sclass.getInterfaces();

        System.out.println(sclass);

        for (Class i : iclass) {
            System.out.println(i);
        }
    }
}
--------------------------------------------------------------
class com.zhbi.source.Student
interface com.zhbi.source.Person

要特别注意:getInterfaces()只返回当前类直接实现的接口类型,并不包括其父类实现的接口类型

8. 继承关系

当我们判断一个实例是否是某个类型时,正常情况下,使用instanceof操作符

Object n = Integer.valueOf(123);

boolean isDouble = n instanceof Double; // 结果为 false
boolean isInteger = n instanceof Integer; // 结果为 true
boolean isNumber = n instanceof Number; // 结果为 true
boolean isSerializable = n instanceof java.io.Serializable; // 结果为 true

如果是两个Class实例,要判断一个向上转型是否成立,可以调用isAssignableFrom()

// Integer i = ?
Integer.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Integer
// Number n = ?
Number.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Number
// Object o = ?
Object.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Object
// Integer i = ?
Integer.class.isAssignableFrom(Number.class); // false,因为Number不能赋值给Integer

9. 动态代理

我们都知道接口和类两者之间有着相似之处,我们也已经知道除了抽象类之外,其他的类都是可以 实例化的,接口虽然和类有相似之处但是我们知道接口是不能够直接实例化的,接口只能通过类实现,通过向上转型并指向某个实例的。

那么有没有可能不编写实现类,直接在运行期创建某个interface的实例呢?答案是可能的,因为Java标准库提供了一种动态代理(Dynamic Proxy)的机制:可以在运行期动态创建某个interface的实例。

一般普通的接口实现方式:这种方式就是我们通常编写代码的方式

interface Person {
    public void speak();
}


class Student implements Person {
    public int score = 100;
    public int grade = 90;

    public void printScore() {
        System.out.println("Score is :" + score);
    }

    public void printGrade(String s) {
        System.out.println(s + "grade is :" + grade);
    }

    @Override
    public void speak() {
        System.out.println("Speak");
    }
}


public class ReflectionLearn {
    public static void main(String[] args) throws Exception {
        Student student = new Student();

        student.speak();
    }
}
--------------------------------------------------------------
Speak

动态创建的方式:动态创建的方式

interface Person {
    public void speak();
}


public class ReflectionLearn {
    public static void main(String[] args) throws Exception {
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println(method);

                if (method.getName().equals("speak")) {
                    System.out.println("Hello, World!");
                }
                return null;
            }
        };

        Person person = (Person) Proxy.newProxyInstance(
            Person.class.getClassLoader(), // 传入ClassLoader
            new Class[] { Person.class },  // 传入要实现的接口
            handler);                      // 传入处理调用方法的InvocationHandler
        person.speak();
    }
}
------------------------------------------------------------
public abstract void com.zhbi.source.Person.speak()
Hello, World!
上一篇下一篇

猜你喜欢

热点阅读