java反射
java反射
一、反射机制概述
- Reflection(反射): 是java被视作动态语言的关键,反射机制允许程序在执行期借助Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
Class c = Class.forName(java.lang.String);
- 类加载完之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子可以看到类的结构,故称之为:反射
- 优点: 可以实现动态创建对象和编译,灵活性高。
- 缺点: 对性能有影响,使用反射基本上就是一种解释操作,我们可以告诉JVM,我们希望做什么并且他满足我们的要求。这类操作总是慢于直接执行相同的操作。
- 反射相关的API:
- java.lang.Class: 代表一个类
- java.lang.reflect.Method: 代表类的方法
- java.lang.reflect.Field: 代表类的成员变量
- java.lang.reflect.Constructor: 代表类的构造器
- …
public class User implements Serializable {
private String name;
private int age;
private boolean sex;
public User() { }
public User(String name, int age, boolean sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
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;
}
public boolean isSex() {
return sex;
}
public void setSex(boolean sex) {
this.sex = sex;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", sex=" + sex +
'}';
}
}
public class MyReflect {
public static void main(String[] args) throws ClassNotFoundException {
// 通过反射获取类的class对象
Class c1 = Class.forName("com.studio.User");
System.out.println(c1.hashCode());
Class c2 = Class.forName("com.studio.User");
Class c3 = Class.forName("com.studio.User");
Class c4 = Class.forName("com.studio.User");
/**
* 一个类在内存中只有一个Class对象
* 一个类被加载后,这个类的整个结构都会被封装在Class对象中
*/
System.out.println(c2.hashCode());
System.out.println(c3.hashCode());
System.out.println(c4.hashCode());
}
}
运行结果: 460141958 460141958 460141958 460141958
二、Class类
反射可以得到的信息:某个类的属性、方法和构造器、某个类实现了哪些接口。对于每个类而言,JRE都为其保留了一个不变的Class类型的对象。一个Class类包含了特定的某个结构(class/interface/enum/annotation/primitive type/void/[])的有关信息。
Ⅰ、Class类
- Class本身也是一个类。
- Class对象只能由系统创建
- 一个加载的类在JVM中只会有一个Class实例
- 一个Class对象对应的是一个加载到JVM中的一个.class文件
- 每个类的实例都会记得自己是由哪个Class实例所生成
- 通过Class可以完整的得到一个类中的所有被加载的结构
- Class类是Reflection的根源,针对任何想动态加载、运行的类,唯有先获得相应的Class对象
Ⅱ、Class类的常用方法
方法名 | 功能说明 |
---|---|
ClassforName(String name) | 返回指定类名name的Class对象 |
newInstance() | 创建此 Class 对象所表示的类的一个新实例 |
getSuperclass() | 返回表示此 Class 所表示的实体(类、接口、基本类型或 void)的超类的 Class。 |
getName() | 以 String 的形式返回此 Class 对象所表示的实体(类、接口、数组类、基本类型或 void)名称。 |
getInterfaces() | 确定此对象所表示的类或接口实现的接口。 |
getClassLoader() | 返回该类的类加载器。 |
getConstructors() | 返回一个包含某些 Constructor 对象的数组,这些对象反映此 Class 对象所表示的类的所有公共构造方法。 |
getMethod(String name, Class<?>… parameterTypes) | 返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。 |
getDeclaredFields() | 返回 Field 对象的一个数组,这些对象反映此 Class 对象所表示的类或接口所声明的所有字段。 |
Ⅲ、获取Class类的实例
- 若已知具体的类,通过类的class属性获取,该方法最为安全可靠,程序性能高。
Class clazz = Object.class;
- 已知某个类的实例,调用该实例的getClass()方法获取Class对象。
Class clazz = Object.getClass();
- 已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException
Class clazz = Class.forName("xxx.xxx.xxx.Object");
- 内置基本数据类型可以直接用类名.Type
// 基本内置类型的包装类都有一个Type属性
Integer.Type
- 利用ClassLoader
三、类加载器ClassLoader
Ⅰ、java内存分析
Ⅱ、类的加载与ClassLoader的理解
- 当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过三个步骤来对该类进行初始化
①:类的加载(Load)
- 将类的class文件读入内存,并为之创建一个java.lang.Class对象。此过程由类加载器完成。
②:类的链接(Link)
- 将类的二进制数据合并到JRE中。
- 验证: 确保加载的类信息符合JVM规范,没有安全方面的问题。
- 准备: 正式为变量(static)分配内存并设置类变量默认初始值阶段,这些内存都将在方法区中进行分配
- 解析: 虚拟机常量池中的符号引用(常量名)替换为直接引用(地址)的过程
③:类的初始化(Initialize)
- JVM负责对类进行初始化
- 执行类构造器()方法的过程,类构造器()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)
- 当初始化一个类的时候,如果发现其父类还没有被初始化,则需要先触发其父类的初始化。
- 虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步。
Ⅲ、类的初始化
①:类的主动引用(一定会发生类的初始化)
- 当虚拟机启动,先初始化main方法所在类
- new一个类的对象
- 调用类的静态成员(除final常量)和静态方法
- 使用java.lang.reflect包的方法对类进行反射调用
- 当初始化一个类,其父类没有被初始化,则会先初始化父类
②:类的被动引用(不会发生类的初始化)
- 当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导致子类初始化。
- 通过数组定义类引用,不会触发此类的初始化。
- 引用常量不会触发此类的初始化(常量在链接节点就存入调用类的常量池中了)
Ⅳ、类加载器的作用
①:类加载的作用
- 将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。
②:类缓存
- 标准的javaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象。
public class MyReflect2 {
public static void main(String[] args) throws ClassNotFoundException {
// 获取系统类的加载器
ClassLoader loader = ClassLoader.getSystemClassLoader();
System.out.println(loader);
// 获取系统类加载器的父类加载器-->扩展类加载器
ClassLoader parent = loader.getParent();
System.out.println(parent);
// 获取扩展类加载器的父类加载器-->根加载器(C/C++)
ClassLoader parent1 = parent.getParent();
System.out.println(parent1);
// 测试当前类是哪个加载器加载的
ClassLoader classLoader = Class.forName("com.studio.MyReflect2").getClassLoader();
System.out.println(classLoader);
// 测试JDK内置类的加载器
ClassLoader classLoader1 = Class.forName("java.lang.Object").getClassLoader();
System.out.println(classLoader1);
}
}
运行结果:
sun.misc.Launcher$AppClassLoader@18b4aac2 sun.misc.Launcher$ExtClassLoader@1b6d3586 null sun.misc.Launcher$AppClassLoader@18b4aac2 null
③:双亲委派机制
- 当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。
- BootstrapClassLoader(启动类加载器)
c++编写,加载java核心库 java.*,构造ExtClassLoader和AppClassLoader。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作
- ExtClassLoader (标准扩展类加载器)
java编写,加载扩展库,如classpath中的jre ,javax.*或者java.ext.dir 指定位置中的类,开发者可以直接使用标准扩展类加载器
- AppClassLoader(系统类加载器)
java编写,加载程序所在的目录,如user.dir所在的位置的class
- CustomClassLoader(用户自定义类加载器)
java编写,用户自定义的类加载器,可加载指定路径的class文件
④:双亲委派机制作用
- 确保Java核心类库的安全:所有的Java应用都至少会引用java.lang.Object类,也就是说在运行期,java.lang.Object类会被记载到Java虚拟机当中;如果这个加载过程是由Java应用自己的类加载器所完成的,那么可能会在JVM中存在多个版本的java.lang.Object类,而且这些类还是不兼容的、相互不可见的(因为命名空间的原因)。借助父亲委托机制,Java核心类库中的类的加载工作都是由启动类加载器来统一完成的,从而确保了Java应用所使用的都是同一个版本的Java核心类库,他们之间是互相兼容的。
- 确保Java核心类库提供的类不会被自定义的类所替代。
- 不同的类加载器可以为相同名称(binary name)的类创建额外的命名空间。相同名称的类可以并存在Java虚拟机中,只需要用不同的类加载器来加他们即可,不同类加载器所加载的类是不兼容的,这就相当于在Java虚拟机内部创建了一个又一个相互隔离的Java类空间。
四、运行时类对象的创建
public class MyReflect3 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
Class c1 = Class.forName("com.studio.User");
// 获取类的名字
System.out.println(c1.getName());// 包名+类名
System.out.println(c1.getSimpleName());// 类名
// 获取类的属性
Field[] fields = c1.getFields(); // 只能找到public属性
Field[] fields1 = c1.getDeclaredFields();// 全部属性
for (Field field : fields1) { System.out.println(field); }
Field name = c1.getDeclaredField("name"); // 执行属性
// 获取类方法
Method[] methods = c1.getMethods(); // 本类与父类所有public方法
Method[] declaredMethods = c1.getDeclaredMethods(); // 本类所有方法
// 获得指定方法 重载
Method getName = c1.getMethod("getName", null);
Method setName = c1.getMethod("setName", String.class);
// 获取构造器
Constructor[] constructors = c1.getConstructors();
Constructor[] declaredConstructors = c1.getDeclaredConstructors();
// 获取指定构造器
Constructor declaredConstructor = c1.getDeclaredConstructor(String.class, int.class, boolean.class);
}
}
五、运行时类完整结构的获取
public class MyReflect4 {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
// 获取class对象
Class c1 = Class.forName("com.studio.User");
// 构造一个对象
User user = (User) c1.newInstance(); // 本质是调用类的无参构造器
// 通过构造器创建对象
Constructor declaredConstructor = c1.getDeclaredConstructor(String.class, int.class, boolean.class);
User user2 = (User) declaredConstructor.newInstance("Admin", 18, true);
//通过反射调用普通方法
User user3 = (User) c1.newInstance();
//通过反射获取一个方法
Method setName = c1.getDeclaredMethod("setName", String.class);
setName.invoke(user3,"Admin1"); // invoke(操作对象,方法值)
// 通过反射操作属性,不能直接操作私有属性,要关闭程序的安全检测
User user4 = (User) c1.newInstance();
Field name = c1.getDeclaredField("name");
name.setAccessible(true); // 私有属性要去除安全校验
name.set(user,"Admin2");
}
}