Skip to content

Java 反射复习

发表于: 时间 20:03
加载中...

1. Java 反射的概念

Java 的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为 Java 语言的反射机制。反射被视为动态语言的关键。

2. 反射 API

(1) 反射中常用的类

java.lang.Class:Java 中类也可以看作一种特殊的对象,Class 便表示的是类的对象。

java.lang.reflect.Constructor:类的构造器。

java.lang.reflect.Field:类的成员变量。

java.lang.reflect.Method:类的成员方法。

(2) 获取类对象的三种方法

现在我在 top.mygld.demo.pojo 包下创建 User 类如下,方便接下来的演示:

package top.mygld.demo.pojo;
public class User {
    private String name;
    private int age;
}

然后接下来在主类中演示类对象的获取。

i)直接通过 类名.class 获取,例如:

Class c = User.class;   //假设User是我们封装好的类

ii)通过 Class.forName("类全名") 获取,例如:

Class c = Class.forName("top.mygld.demo.pojo.User");

iii)通过 具体对象.getClass() 获取,例如:

User user = new User("小明",30);
Class c = user.getClass();

通过上述三种方法获得的对象 c 便是 User 类所对应的类对象,接下来我们可以通过调用 c 中的一些方法,来获取 User 类中的内部信息,进而对内部信息进行操控,这便是 Java 的反射。

(3) 类对象的基本信息

在第(2)步中,我们获取到了类对象 c,那么如果我们直接打印输出 c 会得到什么?我们尝试一下。

package top.mygld.demo.test;
import top.mygld.demo.pojo.User;
public class ReflectionDemo {
    public static void main(String[] args){
        Class c = User.class;
        System.out.println(c);
    }
}

image.png

如上图,是输出了 class + 类全名。这说明 Class 类重写了 toString 方法。

此外,Class 类还提供了两个主要的成员方法:getNamegetSimpleName,分别用来获得类对象的类全名类简名效果如下。

package top.mygld.demo.test;
import top.mygld.demo.pojo.User;
public class ReflectionDemo {
    public static void main(String[] args){
        Class c = User.class;
        System.out.println(c.getName());
        System.out.println(c.getSimpleName());
    }
}

image.png

(4) 通过反射获得目标类的构造器信息

通过反射机制,我们可以获得目标类的构造器(Constuctor)信息,如构造器的个数、构造器的参数情况(无参构造器、有参构造器)、有参构造器的参数个数、参数类型、访问修饰符等等。

为了便于测试,我接下来对 User 类进行改写,如下:

package top.mygld.demo.pojo;
public class User {
    private String name;
    private int age;

    public User(){}

    private User(String name){
        this.name = name;
    }

    protected User(int age){
        this.age = age;
    }

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

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

使用 Class 中的 getDeclaredConstructors() 方法可以获得目标类中的所有构造器的构造器对象数组,使用 Constructor 中的 getParameterCount() 可以获得对应构造器中的参数个数,具体如下:

package top.mygld.demo.test;
import top.mygld.demo.pojo.User;

import java.lang.reflect.Constructor;

public class ReflectionDemo {
    public static void main(String[] args){
        Class c = User.class;
        Constructor[] cons = c.getDeclaredConstructors();
        System.out.println("共有 " + cons.length + " 个构造器") ;
        for(Constructor con : cons){
            System.out.println(con + " 参数个数:" + con.getParameterCount());
        }
    }
}

image.png

当然如果只想获得 public 修饰的构造器,使用 Class 中的 getConstructors() 方法即可,这里将不再演示。

那么有一个问题,如果我想获得单个指定的构造器对象,该如何获取呢?其实只需要调用 getDeclaredConstructor 的重载方法 getDeclaredConstructor(Class<?>... parameterTypes) 即可,其中 parameterTypes 只需要传入 类名.class基本数据类型.class 即可,具体如下:

package top.mygld.demo.test;
import top.mygld.demo.pojo.User;

public class ReflectionDemo {
    public static void main(String[] args) throws NoSuchMethodException {
        Class c = User.class;
        System.out.println(c.getDeclaredConstructor(int.class));
        System.out.println(c.getDeclaredConstructor(String.class, int.class));
    }
}

image.png

当然,如果想获得某个 public 修饰的构造器,也可以使用 Class 中的 getConstructor(Class<?>... parameterTypes) 来获得,其中它的参数可以为空,表示获得 public 修饰的无参构造。

(5) 通过反射创建目标类实例对象

已经获得到了目标类的构造器的对象,此时有人会有疑问了,用这个构造器对象我们能做什么?能做的其实非常多。

首先,通过调用 Constructor 中的 newInstance(Object... initargs) 方法,我们可以实例化一个目标类的对象,例如:

package top.mygld.demo.test;
import top.mygld.demo.pojo.User;
import java.lang.reflect.Constructor;

public class ReflectionDemo {
    public static void main(String[] args) throws Exception{
        Class c = User.class;
        Constructor con = c.getDeclaredConstructor(String.class,int.class);
        User kun = (User) con.newInstance("坤坤", 18);
        System.out.println(kun);
    }
}

image.png

那有人要问了,这不脱裤子放屁多此一举吗?我直接 new User("坤坤",18) 不就行了?还需要这么麻烦吗?

那接下来我要说点有意思的,我们观察到,在 User 类中,有一个 private 修饰的的构造方法,我们正常去执行如下代码一定会报错:

User user = new User("坤坤");

这是因为 private User(String name) 是私有的,不可以被外部直接访问,但是,通过反射我们可以临时绕过访问权限,使得能够调用这个构造器。调用 Constructor 中的 setAccessible(boolean) 即可,具体如下:

package top.mygld.demo.test;
import top.mygld.demo.pojo.User;
import java.lang.reflect.Constructor;

public class ReflectionDemo {
    public static void main(String[] args) throws Exception{
        Class c = User.class;
        Constructor con = c.getDeclaredConstructor(String.class);
        con.setAccessible(true);
        System.out.println(con.newInstance("坤坤"));
    }
}

(6) 通过反射获取目标类成员变量

既然通过反射能获得目标类的构造方法信息,那么肯定也能获取目标类的成员变量(Field)信息。

其实,类比于构造器的获取,成员变量的获取基本类似:

通过调用 Field 中的 getDeclaredFields() 方法可以获得目标类下所有的成员变量信息;调用 Field 中的 getDeclaredField(String name) 可以获得成员变量中变量名为 name 的指定变量;通过调用 Field 中的 getFields() 方法可以获得目标类下所有的 public 修饰的成员变量信息;调用 Field 中的 getField(String name) 可以获得 public 修饰的成员变量中变量名为 name 的指定变量,具体如下:

package top.mygld.demo.test;
import top.mygld.demo.pojo.User;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;

public class ReflectionDemo {
    public static void main(String[] args) throws Exception{
        Class c = User.class;
        System.out.println(c.getConstructor());

        Field[] fields = c.getDeclaredFields();
        for (Field f : fields) {
            System.out.println(f);
        }

        Field name = c.getDeclaredField("name");
        System.out.println(name);

        System.out.println(c.getFields().length);
        //因为User中未设置public修饰的成员变量,这里不再演示getField()方法,大家可以自行尝试
    }
}

(7) 通过反射修改和获取目标类对象的成员变量的值

在第(6)步中,我们获得了目标类成员变量信息,那么我们能否可以根据这个成员变量对象,去修改或获取目标类对象的成员变量的值?答案是可以的,我们只需要使用 Field 中的 set(Object obj, Object value)get(Object obj) 方法即可,具体如下(由于 User 中成员变量都是 private 的,不能直接修改,需要先通过调用 Field 中的 setAccessible(true) 来临时绕过访问权限):

package top.mygld.demo.test;
import top.mygld.demo.pojo.User;
import java.lang.reflect.Field;

public class ReflectionDemo {
    public static void main(String[] args) throws Exception{
        Class c = User.class;
        Field name = c.getDeclaredField("name");
        name.setAccessible(true);

        User user = new User("坤坤",18);
        System.out.println(user);

        name.set(user,"鸽鸽");
        System.out.println(user);
        System.out.println(name.get(user));
    }
}

image.png

(9) 通过反射获取目标类成员方法

如果你详细看了上述 8 个部分内容,你应该能够猜到,获取成员方法(Method)信息也主要有四个方法:

分别是 Method 中的 getDeclaredMethodsgetDeclaredMethodgetMethodsgetMethod,他们的功能与注意事项应该不言而喻了吧,为了方便演示,我先在 User 中添加若干方法,如下:

package top.mygld.demo.pojo;
public class User {
    private String name;
    private int age;

    public User(){}

    private User(String name){
        this.name = name;
    }

    protected User(int age){
        this.age = age;
    }

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

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

    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;
    }

    private void hello(){
        System.out.println("你好,User");
    }
}

然后我再在主类中测试:

package top.mygld.demo.test;
import top.mygld.demo.pojo.User;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class ReflectionDemo {
    public static void main(String[] args) throws Exception{
        Class c = User.class;
        Method[] declaredMethods = c.getDeclaredMethods();
        for(Method m : declaredMethods){
            System.out.println(m);
        }

        System.out.println(c.getMethod("setName", String.class));

        Method[] methods = c.getMethods();
        for(Method m : methods){
            System.out.println(m);
        }

        System.out.println(c.getDeclaredMethod("hello"));

    }
}

image.png

(10) 通过反射调用目标类对象的成员方法

通过反射,我们也可以调用 Method 中的 invoke(目标类对象,参数列表) 方法来调用目标类对象中的成员方法,具体如下:

package top.mygld.demo.test;
import top.mygld.demo.pojo.User;
import java.lang.reflect.Method;

public class ReflectionDemo {
    public static void main(String[] args) throws Exception{
        Class c = User.class;
        Method setName = c.getMethod("setName", String.class);

        User user = (User) c.newInstance();
        System.out.println(user);

        setName.invoke(user, "坤坤");
        System.out.println(user);
    }
}

image.png

3. 反射的作用

i) 基本作用:可以得到一个类的全部成分然后操作。(在上述步骤中已经体现)

ii) 可以破坏封装性。(因为可以修改类内部的成员的访问权限,因此会破坏类的封装性)

iii) 可以绕过泛型的约束,例如下述代码的执行结果:

package top.mygld.demo.test;
import java.lang.reflect.Method;
import java.util.ArrayList;

public class ReflectionDemo {
    public static void main(String[] args) throws Exception {
        ArrayList<String> a = new ArrayList<>();
        a.add("hello");
        Class c = a.getClass();
        Method m = c.getDeclaredMethod("add", Object.class);
        m.invoke(a, 123);
        System.out.println(a);
    }
}

image.png

4. 总结

综上,Java 反射的核心知识点主要包括四个方面:类对象(获取 Class 对象及其信息)、构造器(获取和操作类的构造方法)、成员变量(获取和操作类的字段)、以及成员方法(获取和调用类的方法)。

很多框架都是通过反射的方法实现的,例如 Spring Boot

通过反射 ,我们还可以自主设计一些框架,假如我要获取任意类的成员变量信息,我可以写一个比较简单的框架函数,如下:

package top.mygld.demo.test;
import top.mygld.demo.pojo.User;

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

public class ReflectionDemo {
    public static void main(String[] args) throws Exception {
        User user = new User("坤坤",18);
        getInformation(user);
    }
    public static void getInformation(Object object) throws IllegalAccessException {
        Class c = object.getClass();
        Field[] fields = c.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            System.out.println(field.getName() + ":" + field.get(object));
        }
    }
}

image.png

当然,上述只是举了个例子,这个函数代码可能对某些其他的类不适用。

”反射“为什么叫”反射“,我认为,正常调用某个对象的成员属性或成员方法,都是 对象.成员 的格式,而在反射中的格式基本都是 成员.方法(对象) 的形式,和正常的形式是反着的,所以叫 反射。当然这只是我的一个个人理解,置于为啥真的叫”反射“,感兴趣的可以去查一下资料了~


上一篇文章
Java 的动态代理
下一篇文章
人工智能大判官