java 反射

作者: zhbi98 | 来源:发表于2021-09-09 08:34 被阅读0次

    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!
    

    相关文章

      网友评论

        本文标题:java 反射

        本文链接:https://www.haomeiwen.com/subject/ggndrltx.html