-
概述
- Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期 借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内 部属性及方法。
- 加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个 类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可 以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看 到类的结构,所以,我们形象的称之为:反射。
-
Class类
Class类的理解
- 在Object类中定义了以下的方法,此方法 将被所有子类继承: public final Class getClass() 该方法返回值的类型是一个Class类, 此类是Java反射的源头,实际上所谓反射 从程序的运行结果来看也很好理解,即:可以通过对象反射求出类的名称。
- 对象通过反射(可以理解为照镜子)后可以得到的信息:某个类的属性、方法和构造器、某个类到底实现了哪些接口。对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含 了特定某个结构(class/interface/enum/annotation/primitive type/void/[])的有关信息。
- 一个加载的类在 JVM 中只会有一个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);
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
获取Class类的实例
方式1:通过运行时类的对象,调用getClass()
Person p = new Person();
Class<? extends Person> calzz = p.getClass();
System.out.println(calzz);
方式2:调用Class的静态方法:forName(String classPath)
Class<?> clazz1 = Class.forName("day10.Person");
System.out.println(clazz1);
方式3:调用运行时类的属性:.class
Class<Person> clazz2 = Person.class;
System.out.println(clazz2);
方式4:使用类的加载器:ClassLoader
ClassLoader classLoader = ClassModelTest.class.getClassLoader();
Class<?> calzz3 = classLoader.loadClass("day10.Person");
System.out.println(calzz3);
理解:万事万物皆对象
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;//空参
Class c9 = Class.class;
-
类的加载
类加载的过程
当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过 如下三个步骤来对该类进行初始化。
加载
- 程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)。接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中。此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此运行时类,就作为Class的一个实例。换句话说,Class的实例就对应着一个运行时类。
- 加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式来获取此运行时类。
- 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
链接
- 将Java类的二进制代码合并到JVM的运行状态之中的过程。
- 验证:确保加载的类信息符合JVM规范,例如:以cafe开头,没有安全方面的问题
- 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存 都将在方法区中进行分配。
初始化
- 执行类构造器<clinit>()方法的过程。类构造器<clinit>()方法是由编译期自动收集类中 所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)。
- 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初化。
- 虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步。
public class ClassLoadingTest {
public static void main(String[] args) {
System.out.println(A.m);
}
}
class A {
static {
m = 1;
}
static int m = 2;
}
//第二步:链接结束后m=0
//第三步:初始化后,m的值由<clinit>()方法执行决定
// 这个A的类构造器<clinit>()方法由类变量的赋值和静态代码块中的语句按照顺序合并
产生,类似于
// <clinit>(){
// m = 1;
// m = 2;
// }
了解类的初始化
会发生初始化的情况
类的主动引用(一定会发生类的初始化)
- 当虚拟机启动,先初始化main方法所在的类
- new一个类的对象
- 调用类的静态成员(除了final常量)和静态方法
- 使用java.lang.reflect包的方法对类进行反射调用
- 当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类
不会发生类的初始化
类的被动引用(不会发生类的初始化)
- 当访问一个静态域时,只有真正声明这个域的类才会被初始化;当通过子类引用父类的静态变量,不会导致子类初始化
- 通过数组定义类引用,不会触发此类的初始化
- 引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常 量池中了)
-
ClassLoader(类的加载器)的理解
类的加载器作用
- 类加载的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为 方法区中类数据的访问入口。
- 类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器 中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象。
类加载器的分类
- 引导类加载器(Bootstap ClassLoader):用C++编写的,是JVM自带的类 加载器,负责Java平台核心库,用来装载核心类 库。该加载器无法直接获取
- 扩展类加载器(Extension ClassLoader):负责jre/lib/ext目录下的jar包或 – D java.ext.dirs 指定目录下的jar包装入工作库
- 系统类加载器(System ClassLoader):负责java –classpath 或 –D java.class.path所指的目录下的类与jar包装入工作,是最常用的加载器
- 自定义类加载器
类加载器常用方法
getParent():获取父类的加载器
public static void main(String[] args) throws ClassNotFoundException {
//获取一个系统类加载器
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
System.out.println(classLoader);
//获取系统类加载器的父类加载器,即扩展类加载器
ClassLoader classLoader1 = classLoader.getParent();
System.out.println(classLoader1);
//获取扩展类加载器的父类加载器,即引导类加载器
ClassLoader classLoader2 = classLoader1.getParent();
System.out.println(classLoader2);//null
//获取类由哪个类加载器进行加载
ClassLoader classLoader4 = Class.forName("java.lang.Object").getClassLoader();
System.out.println(classLoader4);
}
getResourceAsStream(): 获取类路 径下的指定文件的输入流
应用场景:Properties:用来读取配置文件。
@Test
public void test2() throws IOException {
Properties pros = new Properties();
//读取配置文件的方式1;
//此时的文件默认在当前的module的src下下。
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
InputStream is = classLoader.getResourceAsStream("jdbc1.properties");
pros.load(is);
//读取配置文件的方式2:
//此时的文件默认在当前的module下。
// FileInputStream fis = new FileInputStream("jdbc.properties");
// pros.load(fis);
String user = pros.getProperty("user");
String possWord = pros.getProperty("passWord");
System.out.println("user=" + user + "\t" + "passWord=" + possWord);
}
-
创建运行时类的对象
通过调用Class对象的newInstance()方法
前提条件
- 类必须有一个无参数的构造器。
- 类的构造器的访问权限需要足够。(通常,设置为public)
小知识点:在javabean中为什么要求提供一个public的空参构造器?
- 便于通过反射,创建运行时类的对象
- 便于子类继承此运行时类时,默认调用super()时,保证父类有此构造器
代码演示
@Test
public void test4() throws Exception {
Class<Person> clazz = (Class<Person>) Class.forName("day10.Person");
Person person = clazz.newInstance();
System.out.println(person);//Person{name='null', age=0}
}
使用非空构造器创建对象
@Test
public void test5() throws Exception {
Person p = new Person();
Class<? extends Person> clazz = p.getClass();
//获取指定的构造器,声明构造器的参数列表
Constructor<? extends Person> constructor = clazz.getDeclaredConstructor(String.class);
//保证次构造器可以访问(构造器的权限为public时,该步可省略)
constructor.setAccessible(true);
//使用获取的该构造器创造对象
Person person = constructor.newInstance("嘻戏");
System.out.println(person);
}
-
获取运行时类的完整结构
运行时类的属性
获取允许时类的属性及相关数据
- getFields()获取当前运行时类及其父类中声明为public访问权限的属性
- getDeclaredFields()获取当前运行时类中声明的所有属性。(不包含父类中声明的属性)
- getModifiers()获取属性的权限修饰符
- getType()获取属性的数据类型
- getName()获取属性的名称
@Test
public void test6() throws ClassNotFoundException {
ClassLoader classLoader = ClassModelTest.class.getClassLoader();
Class<?> clazz = classLoader.loadClass("day10.Person");
Field[] fields = clazz.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
System.out.println(fields[i]);
//获取权限修饰符
int modifiers = fields[i].getModifiers();
System.out.print("权限修饰符:" + Modifier.toString(modifiers) + "\t");
//获取属性数据类型
Class<?> type = fields[i].getType();
System.out.print("数据类型:" + type.getName() + "\t");
//获取变量名称
String fName = fields[i].getName();
System.out.print("变量名" + fName);
System.out.println();
}
}
设置运行时类的属性
@Test
public void test6() throws Exception {
//实例化运行实例对象
Class<?> clazz1 = Class.forName("day10.Person");
//创建运行时类的对象
Person person = (Person) clazz1.newInstance();
//获取运行时类中指定变量名的属性
Field name = clazz1.getDeclaredField("name");
//保证当前属性是可访问的
name.setAccessible(true);
//设置属性的值
name.set(person, "Jane");
//获取属性的值
System.out.println(name.get(person));
}
运行时类方法
获取运行时类的方法及相关数据
- getMethods():获取当前运行时类及其所有父类中声明为public权限的方法
- getDeclaredMethods():获取当前运行时类中声明的所有方法。(不包含父类中声明的方法)
- getAnnotations():获取方法声明的注解
- getModifiers():获取方法权限修饰符
- getReturnType():获取方法返回值类型
- getName():获取方法名
- getParameterTypes():获取方法形参列表
- getExceptionTypes():获取方法抛出的异常
@Test
public void test7(){
Person p = new Person();
Class<? extends Person> clazz = p.getClass();
Method[] methods = clazz.getDeclaredMethods();
for (Method m : methods) {
//获取当前运行时类中声明的所有方法。(不包含父类中声明的方法)
System.out.println("运行时类方法:" + m + "\t");
//获取方法声明的注解
Annotation[] annotations = m.getAnnotations();
for (Annotation a : annotations) {
System.out.print("注解:" + a + "\t");
}
//获取方法权限修饰符
System.out.print("方法权限修饰符:" + Modifier.toString(m.getModifiers()) + "\t");
//获取方法返回值类型
System.out.print("返回值类型:" + m.getReturnType().getName() + "\t");
//获取方法名
System.out.print("方法名:" + m.getName() + "\t");
//获取方法形参列表
Class<?>[] parameterTypes = m.getParameterTypes();
for (int i = 0; i < parameterTypes.length; i++) {
if (i == parameterTypes.length -1) {
System.out.print("args_" + i + "=" + parameterTypes[i].getName() + "\t");
break;
}
if (i == 0)
System.out.print("形参列表:" + "args_" + i + "=" + parameterTypes[i].getName() + ",");
System.out.print("args_" + i + "=" + parameterTypes[i].getName() + ",");
}
//获取方法抛出的异常
Class<?>[] exceptionTypes = m.getExceptionTypes();
if (exceptionTypes.length > 0) {
System.out.print("异常:");
for (int i = 0; i < exceptionTypes.length; i++) {
if (i == exceptionTypes.length - 1) {
System.out.print(exceptionTypes[i].getName());
break;
}
System.out.print(exceptionTypes[i].getName() + ",");
}
}
System.out.println();
}
}
设置运行时类方法
方法有返回值则返回对应的返回值,如果调用的运行时类中的方法没有返回值,则此invoke()返回null
@Test
public void test7() throws Exception {
//实例化运行实例对象
Class<Person> clazz1 = Person.class;
//创建运行时类的对象
Person person = clazz1.newInstance();
//获取指定的某个方法
Method show = clazz1.getDeclaredMethod("show", String.class, int.class);
//保证当前方法是可访问的
show.setAccessible(true);
//设置方法的形参
Object returnValue = show.invoke(person, "Jane", 23);
System.out.println(returnValue);
Method voidModel = clazz1.getDeclaredMethod("voidModel", Person.class);
voidModel.setAccessible(true);
}
调用静态方法
Method model = clazz1.getDeclaredMethod("model");
model.setAccessible(true);
// Object o = model.invoke(null);
Object o = model.invoke(Person.class);//该处null/Person.class没有意义,因为invoke方法需要传入参数
System.out.println(o);
运行时类构造器
获取运行时类的构造器及相关数据
- getConstructors():获取当前运行时类中声明为public的构造器
- getDeclaredConstructors():获取当前运行时类中声明的所有的构造器
@Test
public void test8() throws ClassNotFoundException {
Class<?> clazz = Class.forName("day10.Person");
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
for (Constructor c : constructors) {
System.out.println(c);
}
}
调用运行时类中的指定的构造器
- getConstructor/getDeclaredConstructor(Class<?>... parameterTypes) 通过指明构造器的参数列表获取指定的构造器
@Test
public void test9() throws Exception {
Person p = new Person();
Class<? extends Person> clazz = p.getClass();
//获取指定的构造器
Constructor<? extends Person> constructor = clazz.getDeclaredConstructor(String.class);
//保证此构造器是可访问的
constructor.setAccessible(true);
//调用此构造器创建运行时类的对象
Person person = constructor.newInstance("Jane");
System.out.println(person);
}
运行时类父类
- getSuperClass():获取运行时类的父类
- getGenericSuperClass():获取运行时类的带泛型的父类
@Test
public void test10() throws Exception {
ClassLoader classLoader = ClassModelTest.class.getClassLoader();
Class<?> clazz = classLoader.loadClass("day10.Person");
//获取运行时类的父类
Class<?> superclass = clazz.getSuperclass();
System.out.println(superclass);
//获取运行时类的带泛型的父类
Type genericSuperclass = clazz.getGenericSuperclass();
System.out.println(genericSuperclass);
}
- getActualTypeArguments():获取运行时类的带泛型的父类的泛型
@Test
public void test11() throws ClassNotFoundException {
Class<?> clazz = Class.forName("day10.Person");
Type genericSuperclass = clazz.getGenericSuperclass();
ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
// System.out.println(actualTypeArguments[0].getTypeName());
System.out.println(((Class)actualTypeArguments[0]).getName());
}
运行时类实现的接口
- getInterfaces():获取运行时类实现的接口
- getSuperClass().getInterfaces():获取运行时类的父类实现的接口
@Test
public void test12(){
Person p = new Person();
Class<? extends Person> clazz = p.getClass();
//获取运行时类实现的接口
Class<?>[] interfaces = clazz.getInterfaces();
for (Class c: interfaces) {
System.out.println(c);
}
System.out.println();
//获取运行时类父类实现的接口
Class<?>[] interfaces1 = clazz.getSuperclass().getInterfaces();
for (Class c1 : interfaces1) {
System.out.println(c1);
}
}
获取运行时类所在的包
- getPackage():获取运行时类所在的包
@Test
public void test13() throws Exception {
ClassLoader classLoader = ClassModelTest.class.getClassLoader();
Class<?> clazz = classLoader.loadClass("day10.Person");
Package aPackage = clazz.getPackage();
System.out.println(aPackage);
}
获取运行时类声明的注解
- getAnnotations():获取运行时类声明的注解
@Test
public void test14(){
Class<Person> clazz = Person.class;
Annotation[] annotations = clazz.getAnnotations();
for (Annotation a : annotations) {
System.out.println(a);
}
}