Java反射
反射机制的概述
Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期 借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内 部属性及方法。
加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个 类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可 以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。
正常方式:引入需要的“包类”名称----》通过new实例化----》取得实例化对象
反射方式:实例化对象----》getClass()方法-----》得到完整的包类名称
Java反射机制提供的功能
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时获取泛型信息
- 在运行时调用任意一个对象的成员变量和方法 在运行时处理注解
- 生成动态代理
Java反射的优点与缺点
优点:可以实现动态创建对象和编译,体现出很大的灵活性
缺点:对性能有影响。使用反射基本是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于直接执行相同的操作
反射相关的主要API
java.lang.Class
代表一个类
java.lang.reflect.Method
代表类的方法
java.lang.reflect.Field:
代表类的成员变量
java.lang.reflect.Constructor:
代表类的构造器
Class 类
在Object类中定义了以下的方法,此方法 将被所有子类继承:public final Class getClass()
以上的方法返回值的类型是一个Class类, 此类是Java反射的源头,实际上所谓反射 从程序的运行结果来看也很好理解,即: 可以通过对象反射求出类的名称。
- Class本身也是一个类
- Class 对象只能由系统建立对象
- 一个加载的类在 JVM 中只会有一个Class实例
- 一个Class对象对应的是一个加载到JVM中的一个.class文件
- 每个类的实例都会记得自己是由哪个 Class 实例所生成
- 通过Class可以完整地得到一个类中的所有被加载的结构
- Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的 Class对象
常用方法
方法名 | 功能说明 |
---|---|
static Class forName(String name) |
返回指定类名 name 的 Class 对象 |
Object newInstance() |
调用缺省构造函数,返回该Class对象的一个实例;创建对应的运行时类的对象 |
getName() |
返回此Class对象所表示的实体(类、接口、数组类、基本类型 或void)名称 |
Class getSuperClass() |
返回当前Class对象的父类的Class对象 |
Class [] getInterfaces() |
获取当前Class对象的接口 |
ClassLoader getClassLoader() |
返回该类的类加载器 |
Class getSuperclass() |
返回表示此Class所表示的实体的超类的Class |
Constructor[] getConstructors() |
返回一个包含某些Constructor对象的数组 |
Field[] getDeclaredFields() |
返回Field对象的一个数组 |
Method getMethod(String name,Class … paramTypes) |
返回一个Method对象,此对象的形参类型为paramType |
反射的应用举例
String str = "test4.Person";
Class clazz = Class.forName(str);
Object obj = clazz.newInstance();
Field field = clazz.getField("name");
field.set(obj, "Peter"); • Object name = field.get(obj);
System.out.println(name);
test4.Person是test4包下的Person类
获取Class类的实例(四种方法)
- 前提:若已知具体的类,通过类的class属性获取,该方法最为安全可靠, 程序性能最高
- 实例:
Class clazz = String.class;
- 实例:
- 前提:已知某个类的实例,调用该实例的getClass()方法获取Class对象
- 实例:
Class clazz = “dwdwq”.getClass();
- 实例:
- 前提:已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法
forName()
获取,可能抛出ClassNotFoundException
- 实例:
Class clazz = Class.forName(“java.lang.String”);
- 实例:
- 其他方式(不做要求)
ClassLoader cl = this.getClass().getClassLoader()
;Class clazz4 = cl.loadClass(“类的全类名”);
package com.shao.reflection;
public class Test2 {
public static void main(String[] args) throws ClassNotFoundException {
Person person = new Student();
System.out.println(person.name);
// 方式1:通过对象获得
Class c1 = person.getClass();
System.out.println(c1.hashCode()); // 356573597
// 方式2:forname获得
Class c2 = Class.forName("com.shao.reflection.Student");
System.out.println(c2.hashCode()); // 356573597
// 方式3:通过类名.class获得
Class c3 = Student.class;
System.out.println(c3.hashCode()); // 356573597
// 方式4:基本内置类型的包装类都有一个Type属性
Class c4 = Integer.TYPE;
System.out.println(c4); // int
// 获得父类类型
Class c5 = c1.getSuperclass();
System.out.println(c5); // class com.shao.reflection.Person
}
}
class Person {
String name;
// 有参、无参、toString
}
class Student extends Person{
public Student(){
this.name = "学生";
}
}
哪些类型可以有Class对象?
- class: 外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
- interface:接口
- []:数组
- enum:枚举
- annotation:注解@interface
- primitive type:基本数据类型
- void
Class c1 = Object.class; // 类
Class c2 = Comparable.class; // 接口
Class c3 = String[].class; // 一维数组
Class c4 = int[][].class; // 二维数组
Class c5 = ElementType.class; // 枚举
Class c6 = Override.class; // 注解
Class c7 = int.class;
Class c8 = void.class; // void
Class c9 = Class.class; // class
int[] a = new int[10];
int[] b = new int[100];
Class c10 = a.getClass();
Class c11 = b.getClass();
// 只要元素类型与维度一样,就是同一个Class
System.out.println(c10 == c11);
类的加载与ClassLoader的理解
- 加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时 数据结构,然后生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问 入口(即引用地址)。所有需要访问和使用类数据只能通过这个Class对象。这个加载的过程需要类加载器参与。
- 链接:将Java类的二进制代码合并到JVM的运行状态之中的过程。
- 验证:确保加载的类信息符合JVM规范,例如:以cafe开头,没有安全方面的问题
- 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存 都将在方法区中进行分配。
- 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
- 初始化:
- 执行类构造器()方法的过程。类构造器()方法是由编译期自动收集类中 所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信 息的,不是构造该类对象的构造器)。
- 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类 的初始化。
- 虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步。
public class ClassLoadingTest {
public static void main(String[] args) {
A a = new A();
System.out.println(A.m);// 100
}
}
class A {
static {
System.out.println(“A类静态代码块初始化”);
m = 300;
}