劣实基础–Java 反射机制

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lwj_zeal/article/details/89001021

Java 反射机制

反射

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

反射则是一开始并不知道我要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象了。这时候,我们使用 JDK 提供的反射 API 进行反射调用。反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法或者属性。

反射机制主要提供以下几种功能

  • 在运行时构造任意类的对象。
  • 在运行时获取一个类的成员变量和方法。
  • 在运行时调用某个类对象的方法和成员变量。

在 Java 这个面向对象的语言中,万事万物皆为对象,使用什么来用于表示每一个 Java 类呢?在 Java 中使用 Class 这个类来表示所有的类。

Class 类

Class 类用于描述一个类的所有信息,例如构造方法,成员变量,成员方法等,类中每一个元素都有一个对应的类来表示。

  • 成员变量使用 Field 表示
  • 成员方法使用 Method 表示
  • 构造函数使用 Constructor 表示

Class 类常用的方法:

Class 类

反射的入口的第一步是首先获取类的 Class 对象

获取一个类的 Class 对象有三种方式

  • 通过类名.class 获取

  • 通过对象.getClass() 获取

  • 通过 Class.forName() 获取

Class<Person> personClass = Person.class;
Class<? extends Person> personClass2 = new Person().getClass();
//forName 还不知道具体的类
Class<?> personClass3 = Class.forName("com.example.reflect.Person");

Constructor

Constructor 代表某个类的构造方法。

获取构造类的 Construtor 对象

  • 获取类中所有的构造方法
Constructor<String>[] constructors = (Constructor<String>[]) Class.forName("java.lang.String").getConstructors();
System.out.println("---------------获取所有的构造器start---------------");
for (Constructor<?> constructor : constructors) {
    System.out.println(constructor);
}
System.out.println("---------------获取所有的构造器end---------------");
  • 获取类的某个构造方法
Constructor<byte[]> constructor = (Constructor<byte[]>) Class.forName("java.lang.String").getConstructor(byte[].class);

创建对象

  • 正常创建实例对象
String str = new String(new StringBuffer("Hello World"));
  • 反射方式创建对象的两种方式

方式一:

//拿到指定构造器Constructor对象
Constructor<?> constructor = Class.forName("java.lang.String").getConstructor(StringBuffer.class);
//通过 newInstance 创建对象
String str = (String) constructor.newInstance( new StringBuffer("Hello World"));

方式二:

该方法Class.forName内部会得到无参构造方法 Constroctor 实例,然后调用 Constructor.newInstance(),它内部会使用缓存机制来保存默认构造方法的示例对象。

Class.forName("java.lang.String").newInstance();

通过源码分析 Class.forName("java.lang.String").newInstance()内部是如何实现缓存的?

在 ① 处判断缓存的 cachedConstructor是否为空,它表示的是当前对象的一个无参构造,第一次使用时肯定是为空,那么通过 ② getConstructor0 得到一个 Constructor 无参实例。如果缓存的 cachedConstructor不为空,那么直接通过③直接创建对象 Constructor.newInstance()。

如果一个类没有无参构造,而直接去调用 Class.forName(…).newInstance() 则会报错。

//Class.java
public T newInstance()
    throws InstantiationException, IllegalAccessException
{
    //①
    if (cachedConstructor == null) {
        //代码省略...
        try {
            Class<?>[] empty = {};
            //② 
            final Constructor<T> c = getConstructor0(empty, Member.DECLARED);
            cachedConstructor = c;
        } catch (NoSuchMethodException e) {
            throw (InstantiationException)
                new InstantiationException(getName()).initCause(e);
        }
    }
    Constructor<T> tmpConstructor = cachedConstructor;
    //代码省略...
    // Run constructor
    try {
        //②创建对象
        return tmpConstructor.newInstance((Object[])null);
    } catch (InvocationTargetException e) {
        Unsafe.getUnsafe().throwException(e.getTargetException());
        // Not reached
        return null;
    }
}

下面是具体 Class.newInstance()内部调用逻辑

Class.newInstance()内部调用逻辑

Method

Method 代表某个类的一个方法。

获取 Method 的方式有两种

获取当前类和父类中所有的 public 方法。

public Method getMethod(String name, Class<?>... parameterTypes)

获取本类中所有方法,不包括父类方法。

public Method getDeclaredMethod(String name, Class<?>... parameterTypes)

注意:通过 invoke 方法调用私有方法时,需要设置 method.setAccessible(true);

实践:通过反射调用 DoSomething 的 main 方法。

//DoSomething.java
package com.example.reflect;
public class DoSomething {
    public static void main(String[] args) {
        System.out.println(DoSomething.class.getSimpleName());
        for (String arg : args) {
            System.out.println(arg);
        }
    }

    @Override
    public int hashCode() {
        return super.hashCode();
    }
}
  • 获取表示 main 方法的 Method 对象
Class<?> clazz = Class.forName("com.example.reflect.DoSomething");
//public static void main(String[] args)
Method mainMethod = clazz.getMethod("main", String[].class);
  • 反射调用 main 方法
//调用 main 方法。
mainMethod.invoke(null, new Object[]{new String[]{"Hello"}});

这里需要注意一点 JDK1.4 和 JDk1.5 关于 method#invoke 方法的区别:

JDK1.5 public Object invoke(Object obj,Object...args)
JDK1.4 public Object invoke(Object obj,Object[] args)

如果是以一个字符串数组传入给 invoke 方法,那么 javac 会按照哪种语法给处理呢?
因为 JDK1.5 需要兼容 JDk1.4 因此会按照 JDK1.4 的语法来执行。
表示传入的 method 的参数 new Object[]{},这个数组存放的元素才是真正我们要传入的参数,那么这个数组需要存放的是 String[]{"","",""}数组
所以最终表示为:method.invoke(null,new Object[]{new String[]{“Hello”}})

Field

Field 表示一个类的某一个成员变量。

public class Person {

    private int age;
    private String name;


    public Person() {
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

实践:反射获取一个对象的成员变量和给该成员变量赋值。

//Field 类:代表某个类的一个成员变量
Class<Person> clazz = (Class<Person>) Class.forName("com.example.reflect.Person");
Field ageField = clazz.getDeclaredField("age");
//私有属性需要设置ageField.setAccessible(true)
ageField.setAccessible(true);
Person person = clazz.newInstance();
person.setAge(11);
//获取 age 属性的值
int age = (int) ageField.get(person);
System.out.println(age);
//给 age 属性赋值
ageField.set(person,12);
int ageValue = (int) ageField.get(person);
System.out.println(ageValue);

记录于2019年4月3日

猜你喜欢

转载自blog.csdn.net/lwj_zeal/article/details/89001021
今日推荐