Java反射机制

反射

反射允许对成员变量,成员方法和构造方法的信息进行编程访问

获取class对象

反射是通过class字节码来获取哪些信息的

类加载各阶段完成的功能

加载阶段:将类的 class 文件读入内存,并为之创建一个 java.lang.Class 对象,此过程由类加载器完成。
连接阶段:又分为验证、准备、解析三个小阶段,此阶段会将类的二进制数据合并到 JRE 中。
初始化阶段:JVM 负责对类的静态成员进行初始化。
如下图所示:

加载阶段

JVM 在该阶段的主要目的是将字节码从不同的数据源(可能是 class 文件、jar 包、甚至网络文件)转换为二进制字节流加载到内存中,并生成一个代表该类的 java.lang.Class 对象。

5 连接阶段——验证
5.3 连接阶段——准备

JVM 会在该阶段对静态变量分配内存并进行默认初始化(不同数据类型会有其默认初始值,如:int ---- 0,boolean ---- false 等)。这些变量的内存空间会在方法区中分配。

举例如下:

public class ClassLoad {
    public static void main(String[] args) {
        // 属性=成员变量=字段
        // 类加载的连接阶段-准备,属性是如何加载的
        public int n1 = 10;
        public static  int n2 = 20;
        public static final  int n3 = 30;
    }
}

代码说明:

n1 是实例属性, 不是静态变量,因此在准备阶段,是不会分配内存

n2 是静态变量,在该阶段 JVM 会为其分配内存,n2 默认初始化的值为 0 ,而不是 20

n3 被 static final 修饰,是常量, 它和静态变量不一样, 其一旦赋值后值就不变,因此其默认初始化 n3 = 30

连接阶段——解析

JVM 将常量池内的符号引用替换为直接引用的过程。

初始化阶段

在初始化阶段,JVM 才会真正执行类中定义的 Java程序代码,此阶段是执行<clinit>() 方法的过程。
<clinit>() 方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有静态变量的赋值操作和静态代码块中的语句,并进行合并的过程。
JVM 会保证一个类的 <clinit>() 方法 在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的 <clinit>() 方法,其他线程都要阻塞等待,直到活动线程执行 <clinit>() 方法完毕。
举例如下:

public class ClassLoad {
    public static void main(String[] args) throws ClassNotFoundException {
        System.out.println(B.num);// 直接使用类的静态属性,也会导致类的加载
    }
}

class B {
    static { // 静态代码块
        System.out.println("B 静态代码块被执行");
        num = 300;
    }
static int num = 100;// 静态变量

public B() {// 构造器
    System.out.println("B() 构造器被执行");
}

输出如下:

B 静态代码块被执行
100

注意没有使用到类,类不会被加载的

代码说明:

  1. 加载阶段:加载 B类,并生成 B的 class对象
  2. 连接阶段:进行默认初始化 num = 0
  3. 初始化阶段:执行 <clinit>() 方法,该方法会依次自动收集类中的所有静态变量的赋值操作和静态代码块中的语句,并合并。如下:
clinit() {
	System.out.println("B 静态代码块被执行");
    num = 300;
    num = 100;
}

  • 合并后: num = 100

注意:加载类的时候,具有同步机制控制。如下:

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
	//正因为有这个机制,才能保证某个类在内存中, 只有一份Class对象
    synchronized (getClassLoadingLock(name)) {
    	//....
    }
}

访问字段

对任意的一个Object实例,只要我们获取了它对应的Class类对象,就可以获取它的一切信息。

我们先看看如何通过Class类对象获取其对应的类定义的字段信息。Class类提供了以下几个方法来获取字段:

Field getField(name):根据字段名获取某个 public 的 field(包括父类)

Field getDeclaredField(name):根据字段名获取当前类的某个 field(不包括父类)

Field[] getFields():获取所有 public 的 field(包括父类)

Field[] getDeclaredFields():获取当前类的所有 field(不包括父类)

示例:

public class Main {
    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"));
    }
}

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

class Person {
    public String name;
}

  • 上述代码首先获取StudentClass实例,然后,分别获取public字段、继承的public字段以及private字段,打印出的Field类似下面:
public int Student.score
public java.lang.String Person.name
private int Student.grade

一个Field对象包含了一个字段的所有信息:

  • getName():返回字段名称,例如,“name”;
  • getType():返回字段类型,也是一个Class类对象,例如,String.class;
  • getModifiers():返回字段的修饰符,它是一个int,不同的 bit 表示不同的含义。

String类的value字段为例,它的定义是:

public final class String {
    private final byte[] value;
}

我们用反射获取该字段的信息,代码如下:

Field f = String.class.getDeclaredField("value");
f.getName(); // "value"
f.getType(); // class [B 表示byte[]类型

int m = f.getModifiers();
Modifier.isFinal(m); // true
Modifier.isPublic(m); // false
Modifier.isProtected(m); // false
Modifier.isPrivate(m); // true
Modifier.isStatic(m); // false

1. 获取字段值

利用反射拿到字段的一个Field类对象只是第一步,我们还可以拿到一个实例对象对应的该字段的值。

例如,对于一个Person类对象,我们可以先拿到其name字段对应的Field,再获取这个Person类对象的name字段的 值:

import java.lang.reflect.Field;
public class Main {
    public static void main(String[] args) throws Exception {
        Person p = new Person("Xiao Ming");
        Class c = p.getClass();
        Field f = c.getDeclaredField("name");// 获取 private String name;
        Object value = f.get(p);
        System.out.println(value); // "Xiao Ming"
    }
}

class Person {
    private String name;

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

上述代码先获取Person类对应的Class类对象,再通过该Class类对象获取Field类对象,然后,用Field.get(Object)获取指定Person类对象的指定字段的值。

运行代码,如果不出意外,会得到一个IllegalAccessException异常,这是因为name被定义为一个private字段,正常情况下,Main类无法访问Person类的private字段。要修复错误,可以将private改为public,或者,在调用Object value = f.get§;前,先写一句:

f.setAccessible(true);

调用Field.setAccessible(true)的意思是,别管这个字段是不是public,一律允许访问。

可以试着加上上述语句,再运行代码,就可以打印出private字段的值。

有童鞋会问:如果使用反射可以获取private字段的值,那么类的封装还有什么意义?

  • 答案是一般情况下,我们总是通过p.name来访问Person的name字段,编译器会根据public、protected和private这些访问权限修饰符决定是否允许访问字段,这样就达到了数据封装的目的。

  • 而反射是一种非常规的用法,使用反射,首先代码非常繁琐;其次,它更多地是给工具或者底层框架来使用,目的是在不知道目标对象任何信息的情况下,获取特定字段的值。

此外,setAccessible(true)可能会失败。 如果 JVM 运行期存在SecurityManager,那么它会根据规则进行检查,有可能阻止setAccessible(true)。例如,某个SecurityManager可能不允许对java和javax开头的package的类调用setAccessible(true),这样可以保证 JVM 核心库的安全。

2.设置字段值

通过 Field 类对象既然可以获取到指定对象的字段值,自然也可以设置字段的值。

设置字段值是通过Field.set(Object, Object)实现的,其中第一个Object参数是指定的对象,第二个Object参数是待修改的值。示例代码如下:

import java.lang.reflect.Field;
public class Main {
    public static void main(String[] args) throws Exception {
        Person p = new Person("Xiao Ming");
        Class c = p.getClass();
        Field f = c.getDeclaredField("people");
        f.setAccessible(true);// 获取 private String name;
//        Object value = f.get(p);
        f.set(p,new People());
        System.out.println(f.get(p)); // "Xiao Ming"
    }
}

class Person {
    protected String name;
    private    People people;

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




}
class  People{
    private String name;
    public People(){ this.name="qq";}

    @Override
    public String toString() {
        return name;
    }
}

可以通过反射给Person的字段赋值

但如果赋值的类型不是Person字段的类型的呢

这里我给People people 赋值的是字符串而不是People类型

public static void main(String[] args) throws Exception {
        Person p = new Person("Xiao Ming");
        Class c = p.getClass();
        Field f = c.getDeclaredField("people");
        f.setAccessible(true);// 获取 private String name;
//        Object value = f.get(p);
        f.set(p,"new People()");
        System.out.println(f.get(p)); // "Xiao Ming"
    }
3.小结

Java 的反射 API 提供的Field类封装了对应的类定义的全部字段的所有信息:
通过Class类对象的方法可以获取Field类对象:getField(),getFields(),getDeclaredField(),getDeclaredFields();
通过Field类对象可以获取类定义字段信息:getName(),getType(),getModifiers();
通过Field类对象可以读取或设置某个对象的字段的值,如果存在访问限制,则需要调用setAccessible(true)来访问非public字段。
通过反射读写字段是一种非常规的方法,它会破坏对象的封装。

调用方法

我们已经能通过Class类的Field类对象获取其对应的类class中定义的所有字段信息,同样的,可以通过Class类获取所有Method信息。Class类提供了以下几个方法来获取类class中定义的Method:

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

public class Main {
    public static void main(String[] args) throws Exception {
        Class stdClass = Student.class;
        // 获取 public方法 getScore,形参类型为 String:
        System.out.println(stdClass.getMethod("getScore", String.class));
        // 获取继承的 public方法 getName,无参数:
        System.out.println(stdClass.getMethod("getName"));
        // 获取 private方法 getGrade,形参类型为 int:
        System.out.println(stdClass.getDeclaredMethod("getGrade", int.class));
    }
}

class Student extends Person {
    public int getScore(String type) {
        return 99;
    }
    private int getGrade(int year) {
        return 1;
    }
}

class Person {
    public String getName() {
        return "Person";
    }
}

在这里插入图片描述

一个Method类对象包含一个方法的所有信息:

  • getName():返回方法名称,例如:“getScore”;
  • getReturnType():返回方法的返回值类型,也是一个Class实例,例如:String.class;
  • getParameterTypes():返回方法的参数类型,是一个Class数组,例如:{String.class, int.class};
  • getModifiers():返回方法的修饰符,它是一个int,不同的 bit 表示不同的含义。
1.调用方法

当我们获取到一个Method类对象时,就可以对它进行调用。我们以下面的代码为例:

// 一般情况下调用 String 类的 substring() 方法
String s = "Hello world";
String r = s.substring(6); // "world"
如果用反射来调用substring方法,需要以下代码:
import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws Exception {
        // String 对象:
        String s = "Hello world";
        // 获取 String substring(int)方法,形参为 int:
        Method m = String.class.getMethod("substring", int.class);
        // 在 s 对象上调用该方法并获取结果:
        String r = (String) m.invoke(s, 6);
        // 打印调用结果:
        System.out.println(r);
    }
}

import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws Exception {
        // String 对象:
        String s = "Hello world";
        // 获取 String substring(int)方法,形参为 int:
        Method m = String.class.getMethod("substring", int.class);
        //获得方法 的所属的类
        System.out.println(m.getDeclaringClass());
        // 在 s 对象上调用该方法并获取结果:
        String r = (String) m.invoke("qqqqqqqqq", 6);
        // 打印调用结果:
        System.out.println(r);
    }
}

  • 注意到substring()有两个重载方法,我们获取的是String substring(int)这个方法(即形参类型为 int,且只有一个)。思考一下如何获取String substring(int, int)方法。

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

2.调用静态方法

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

import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws Exception {
        // 获取 Integer.parseInt(String) 方法,参数为 String:
        Method m = Integer.class.getMethod("parseInt", String.class);
        // 调用该静态方法并获取结果:
        Integer n = (Integer) m.invoke(null, "12345");
        // 打印调用结果:
        System.out.println(n);// 12345
    }
}

经过测试,如果是静态方法invoke的第一个参数可以为任意对象

3.调用非 public方法

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

import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws Exception {
        Person p = new Person();
        Method m = p.getClass().getDeclaredMethod("setName", String.class);
        m.setAccessible(true);
        m.invoke(p, "Bob");
        System.out.println(p.name);// Bob
    }
}

class Person {
    String name;
    
    private void setName(String name) {
        this.name = name;
    }
}

  • 同样,setAccessible(true)可能会失败。如果 JVM 运行期存在SecurityManager,那么它会根据规则进行检查,有可能阻止setAccessible(true)。例如,某个SecurityManager可能不允许对java和javax开头的package的类调用setAccessible(true),这样可以保证 JVM 核心库的安全
4. 多态

我们来考率这样一种情况:一个Person类定义了hello()方法,并且它的子类Student也重写了hello()方法,那么,从Person.class获取的Method,作用于Student类对象时,调用的hello()方法到底是哪个?

import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws Exception {
        // 获取Person的 hello方法:
        Method h = Person.class.getMethod("hello");
        // 对 Student实例调用 hello方法:
        h.invoke(new Student());
    }
}

class Person {
    public void hello() {
        System.out.println("Person:hello");
    }
}

class Student extends Person {
    public void hello() {
        System.out.println("Student:hello");
    }
}

调用的是子类的

在这里插入图片描述

  • 运行上述代码,发现输出的是Student:hello,因此,使用反射调用方法时,仍然遵循多态原则:即总是调用实际类型的重写方法(如果存在)。 上述的反射代码:
Method m = Person.class.getMethod("hello");
m.invoke(new Student());

  • 实际相当于
Person p = new Student();
p.hello();
5.小结
  1. Java 的反射 API 提供的Method类对象封装了类定义的全部方法的所有信息:
  2. 通过Class类对象的方法可以获取Method类对象:getMethod(),getMethods(),getDeclaredMethod(),getDeclaredMethods();
  3. 通过Method类对象可以获取方法信息:getName(),getReturnType(),getParameterTypes(),getModifiers();
  4. 通过Method类对象可以调用某个对象的方法:Object invoke(Object instance, Object… parameters);
  5. 通过设置setAccessible(true)来访问非public方法;
  6. 通过反射调用方法时,仍然遵循多态原则。

调用构造方法

一般情况下,我们通常使用new操作符创建新的对象:

Person p = new Person();

如果通过反射来创建新的对象,可以调用Class提供的newInstance()方法:

Person p = Person.class.newInstance();
  • 调用Class.newInstance()的局限是,它只能调用该类的public无参构造方法。如果构造方法带有参数,或者不是public,就无法直接通过Class.newInstance()来调用。

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

import java.lang.reflect.Constructor;

public class Main {
    public static void main(String[] args) throws Exception {
        // 获取构造方法 Integer(int),形参为 int
        Constructor cons1 = Integer.class.getConstructor(int.class);
        // 调用构造方法:
        // 传入的形参必须与构造方法的形参类型相匹配
        Integer n1 = (Integer) cons1.newInstance(123);
        System.out.println(n1);

        // 获取构造方法Integer(String),形参为 String
        Constructor cons2 = Integer.class.getConstructor(String.class);
        Integer n2 = (Integer) cons2.newInstance("456");
        System.out.println(n2);
    }
}

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

  1. getConstructor(Class…):获取某个public的Constructor 参数是构造器参数的Class对象;
  2. getDeclaredConstructor(Class…):获取某个Constructor;
  3. getConstructors():获取所有public的Constructor;
  4. getDeclaredConstructors():获取所有Constructor。

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

同样,调用非public的Constructor时,必须首先通过setAccessible(true)设置允许访问。但setAccessible(true)也可能会失败。

小结

Constructor类对象封装了其对应的类定义的构造方法的所有信息;
通过Class类对象可以获取Constructor类对象:getConstructor(),getConstructors(),getDeclaredConstructor(),getDeclaredConstructors();
通过``Constructor类对象可以创建一个对应类的实例对象:newInstance(Object… parameters); 通过设置setAccessible(true)`来访问非public

六、获取继承方法

当我们获取到某个Class类对象时,实际上就获取到了一个类的类型:

Class cls = String.class; // 获取到 String 的 Class类对象

1
还可以用类对象的getClass()方法获取:

String s = "";
Class cls = s.getClass(); // s是String,因此获取到String的Class

最后一种获取Class的方法是通过Class.forName(“”),传入Class的完整类名获取:

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

1
这三种方式获取的Class类对象都是同一个对象,因为 JVM 对每个加载的Class只创建一个Class类对象来表示它的类型。

  1. 获取父类的Class
    有了Class类对象,我们还可以获取它的父类的Class类对象:
public class Main {
    public static void main(String[] args) throws Exception {
        Class i = Integer.class;
        Class n = i.getSuperclass();
        System.out.println(n);
        Class o = n.getSuperclass();
        System.out.println(o);
        System.out.println(o.getSuperclass());
    }
}

运行上述代码,可以看到,Integer的父类类型是Number,Number的父类是Object,Object的父类是null。除Object外,其他任何非接口interface的Class类对象都必定存在一个父类类型。

  1. 获取interface
    由于一个类可能实现一个或多个接口,通过Class我们就可以查询到实现的接口类型。例如,查询Integer实现的接口:
import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws Exception {
        Class s = Integer.class;
        Class[] is = s.getInterfaces();
        for (Class i : is) {
            System.out.println(i);
        }
    }
}

运行上述代码可知,Integer实现的接口有:

java.lang.Comparable
java.lang.constant.Constable
java.lang.constant.ConstantDesc
要特别注意:getInterfaces()方法只返回当前类直接实现的接口类型,并不包括其父类实现的接口类型:

// reflection
import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws Exception {
        Class s = Integer.class.getSuperclass();
        Class[] is = s.getInterfaces();
        for (Class i : is) {
            System.out.println(i);
        }
    }
}

Integer的父类是Number,Number类实现的接口是java.io.Serializable。
此外,对所有接口interface的Class类对象调用getSuperclass()返回的是null,获取接口的父接口要用getInterfaces():

System.out.println(java.io.DataInputStream.class.getSuperclass()); 
// 输出 java.io.FilterInputStream。因为 DataInputStream 继承自 FilterInputStream

System.out.println(java.io.Closeable.class.getSuperclass()); 
// 输出 null。因为对接口调用 getSuperclass()总是返回 null,获取接口的父接口要用 getInterfaces()

如果一个类没有实现任何interface,那么getInterfaces()返回空数组。

  1. 继承关系
    当我们判断一个对象是否是某个类型时,正常情况下,使用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
**小结**

通过Class对象可以获取继承关系:

Class getSuperclass():获取父类类型;

Class[] getInterfaces():获取当前类实现的所有接口。

通过Class对象的isAssignableFrom()方法可以判断一个向上转型是否可以实现。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值