Java反射介绍

136 阅读6分钟

这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战

文章目录

什么是反射

一般情况下,我们使用某个类时必定知道它是什么类,是用来做什么的。于是我们直接对这个类进行实例化,之后使用这个类对象进行操作。例如:

Person person = new Person();
person.setId(22);

而反射则是一开始并不知道要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象了。我们使用 JDK 提供的反射 API 进行反射调用:

Class clz = Class.forName("com.example.testapplication.Person");
Method method = clz.getMethod("setId", int.class);
Constructor constructor = clz.getConstructor();
Object object = constructor.newInstance();
method.invoke(object, 22);;

面两段代码的执行结果,其实是完全一样的。但是其思路完全不一样,第一段代码在未运行时就已经确定了要运行的类(Person),而第二段代码则是在运行时通过字符串值才得知要运行的类(com.example.testapplication.Person)。

所以 Java 反射说的是在运行状态中,对于任何一个类,我们都能够知道这个类有哪些方法和属性。对于任何一个对象,我们都能够对它的方法和属性进行调用。我们把这种动态获取对象信息和调用对象方法的功能称之为反射机制。

所谓反射其实是获取类的字节码文件,也就是.class文件,那么我们就可以通过 Class 这个对象进行获取。

举个栗子:通过new和反射分别创建类

首先创建 Person 类

class Person {
    private int id;
    private String name;

    public Person() {
        super();
    }

    public Person(int id,String name) {
        super();
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
		Person person = new Person();
        person.setId(22);
        Log.d("TTT",person.getId()+"");

        try {
            Class clz = Class.forName("com.example.testapplication.Person");
            Method method = clz.getMethod("setId", int.class);
            Constructor constructor = clz.getConstructor();
            Object object = constructor.newInstance();
            method.invoke(object, 22);

            Method getPriceMethod = clz.getMethod("getId");
            Log.d("TTT",getPriceMethod.invoke(object)+"");
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

输出结果:
在这里插入图片描述
从这个简单的例子可以看出,一般情况下我们使用反射获取一个对象的步骤

1、获取类的 Class 对象实例

Class clz = Class.forName("com.example.testapplication.Person");

2、根据 Class 对象实例获取 Constructor 对象

Constructor constructor = clz.getConstructor();

3、使用 Constructor 对象的 newInstance 方法获取反射类对象

Object object = constructor.newInstance();

而如果要调用某一个方法,则需要经过下面的步骤

1、获取方法的 Method 对象

Method method = clz.getMethod("setId", int.class);

2、利用 invoke 方法调用方法

method.invoke(object, 22);

反射常用API

获取 Class 类对象有三种方法

在反射中,要获取一个类或调用一个类的方法,我们首先需要获取到该类的 Class 对象。
在 Java API 中,获取 Class 类对象有三种方法:

第一种,使用 Class.forName 静态方法。当你知道该类的全路径名时,你可以使用该方法获取 Class 类对象。

Class clz = Class.forName("java.lang.String");

第二种,使用 .class 方法。这种方法只适合在编译前就知道操作的 Class。

Class clz = String.class;

第三种,使用类对象的 getClass() 方法。

String str = new String("Hello");
Class clz = str.getClass();

通过反射创建类对象的两种方法

通过反射创建类对象主要有两种方式:通过 Class 对象的 newInstance() 方法、通过 Constructor 对象的 newInstance() 方法。

第一种:通过 Class 对象的 newInstance() 方法。

Class clz = Person.class;
Object object = clz.newInstance();

第二种:通过 Constructor 对象的 newInstance() 方法。

Class clz = Person.class;
Constructor constructor = clz.getConstructor();
Object object = constructor.newInstance();

通过 Constructor 对象创建类对象可以选择特定构造方法,而通过 Class 对象则只能使用默认的无参数构造方法。下面的代码就调用了一个有参数的构造方法进行了类对象的初始化。

Class clz = Person.class;
Constructor constructor = clz.getConstructor(int.class,String.class);
Object object = constructor.newInstance(22,"Errol");

方法介绍

创建一个 Person 类

class Person {
    public int id;
    private String name;

    public Person(int id, String name) {
        super();
        this.id = id;
        this.name = name;
    }
}

MainActivity 中

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Person person = new Person(22, "Errol");
        //获取Class对象
        Log.d("TTT", person.getClass() + "");//class com.example.testapplication.Person
        //获取Class对象所表示的实体
        Log.d("TTT", person.getClass().getName() + "");//com.example.testapplication.Person
        //得到类的简写名称
        Log.d("TTT", person.getClass().getSimpleName() + "");//Person

        Field[] fields = person.getClass().getFields();
        for (int i = 0; i < fields.length; i++) {
            //获取字段的名称,无法获取私有属性
            //getDeclaredFields()方法则可以获取包括私有属性在内的所有属性
            Log.d("TTT", fields[i].getName());//id
            //获取字段修饰符。什么都不加:0;public:1;private:2;protected:4;static:8;final:16
            Log.d("TTT", fields[i].getModifiers() + "");//1
            //与某个具体的修饰符进行比较
            Log.d("TTT", Modifier.isStatic(fields[i].getModifiers()) + "");//false
            //获取字段的声明类型
            Log.d("TTT", fields[i].getType() + "");//int
            //与某个类型进行比较
            Log.d("TTT", (fields[i].getType() == int.class) + "");//true
            //获取字段的值
            try {
                Object fieldObject = fields[i].get(person);
                Log.d("TTT", fieldObject.toString());//22
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            //判断Class是否为原始类型(boolean、char、byte、short、int、long、float、double)
            Log.d("TTT", (fields[i].getType().isPrimitive()) + "");//true
            //isAccessible()值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。值为 false 则指示反射的对象应该实施 Java 语言访问检查
            //setAccessible是启用和禁用访问安全检查的开关,并不是为true就能访问为false就不能访问
            Log.d("TTT", fields[i].isAccessible() + "");//false
        }
    }
}          

tips:由于JDK的安全检查耗时较多。所以通过setAccessible(true)的方式关闭安全检查就可以达到提升反射速度的目的

一些疑问

getFields() 方法和 getDeclaredFields () 的区别

关于获取类的字段有两种方式:getFields()getDeclaredFields(),区别如下:

getFields():获得某个类的所有的公共(public)的字段,包括父类中的字段
getDeclaredFields():获得某个类的所有声明的字段,即包括public、private和proteced,但是不包括父类的申明字段

同样类似的还有getConstructors()getDeclaredConstructors()getMethods()getDeclaredMethods(),这两者分别表示获取某个类的方法、构造函数

使用 new 和 newInstance() 创建类的区别

在初始化一个类,生成一个实例的时候,newInstance()方法和new关键字除了一个是方法,一个是关键字外,最主要有什么区别?

它们的区别在于创建对象的方式不一样,前者是使用类加载机制,后者是创建一个新类。newInstance() 生成对象只能调用无参的构造函数,首先调用Class.forName("")返回的是类来加载某个类,然后使用Class.forName("").newInstance()返回的是 object 来实例化。只能调用无参的构造函数。是使用newInstance()方法的时候,就必须保证:1、这个类已经加载;2、这个类已经连接了。

而使用new关键字生成对象没有这个限制。new 创建一个类的时候,这个类可以没有被加载。

简言之:
newInstance(): 弱类型,低效率,只能调用无参构造。你可以对该类一无所知。
new: 强类型,相对高效,能调用任何 public 构造。必须要知道一个明确的类才能使用。

参考:
大白话说Java反射:入门、使用、原理