前言
有两种方法获取Java运行时的类型信息,分别是:
- 传统的RTTI(Run-Time Type Identification)
- Java反射机制
了解学习RTTI:浅谈 RTTI
Java反射机制的定义
Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性。这种动态获取信息以及动态调用对象方法的功能称为Java反射机制。
Java类的三个阶段:
Java文件编译成字节码文件后,由AppClassLoader(JVM内置的面向用户的类加载器)加载类的类型信息并保存到JVM管理内存的方法区,并创建一个Class对象保存到堆中。当我们需要new一个对象时,就是根据Class对象并调用其构造方法来创建实例。
Class类
Class类是Java反射机制中的最重要的类,每装载一个新类时,JVM就会在Java堆中,创建一个Class的实例,这个实例就代表了这个新类的类型,通过这个实例可以获取这个类型的信息:包括属性,方法,注解等。
获取Class对象的方式
下面是获取Class对象的三种方式:
- Class.forName(“全限定名”):将字节码文件加载进内存,返回Class对象。多用于配置文件,将类名定义在配置文件中。读取文件,加载类。
- 类名.class:通过类名的属性class获取。多用于参数的传递。
- 对象.getClass():getClass()方法在Object类中定义着。多用于对象的获取字节码的方式。
下面使用分别使用这三种方式来获取Class对象:
创建一个Person类用作测试:
public class Person {
private int id;
private String name;
//省略getter,setter,toString,构造方法
测试三种方式获取的Class对象是否是同一个
/*
获取Class对象的方式
*/
public class reflectDemo01 {
public static void main(String[] args) throws ClassNotFoundException {
//1.Class.forName(“全限定名”),将字节码文件加载到内存
Class c1 = Class.forName("learn.bean.Person");
System.out.println(c1);
//2.类名.class,通过类名的属性class获取
Class c2 = Person.class;
System.out.println(c2);
//3.对象.getClass(),getClass方法在Object类中定义
Class c3 = new Person().getClass();
System.out.println(c3);
//验证这三个Class对象是否相同
System.out.println(c1==c2?(c1==c3?"yes":"no"):"no");//结果为yes
}
}
结论:同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个。
Class对象的功能
一些常用的作用为获取的方法:
- 获取成员变量
- Field[] getFields() //获取所有public修饰的成员变量
- Field getField(String name)//获取指定名称的 public修饰的成员变量
- Field[] getDeclaredFields()//获取所有的成员变量,不考虑修饰符
- Field getDeclaredField(String name) //获取指定名称的成员变量
下面的命名规则都相似,带有“Declared”的方法才是获取所有声明的。
- 获取构造方法们
- Constructor<?>[] getConstructors()
- Constructor<T> getConstructor(类<?>... parameterTypes)
- Constructor<T> getDeclaredConstructor(类<?>... parameterTypes)
- Constructor<?>[] getDeclaredConstructors()
- 获取成员方法们:
- Method[] getMethods()
- Method getMethod(String name, 类<?>... parameterTypes)
- Method[] getDeclaredMethods()
- Method getDeclaredMethod(String name, 类<?>... parameterTypes)
- 获取全类名
- String getName()
- 获取类加载器
- ClassLoader getClassLoader()
- 获取资源
- URL getResource()
- 获取父类
- Class getSuperclass()
- 获取修饰符
字段,方法,构造器同样能使用这个方法
getModifiers()
//返回修饰该方法对象修饰符的整数形式,使用 Modifier 类对其进行解码
String Modifier.toString(clazz.getModifiers())
利用Class类来创建实例
Object newInstance()//利用Class类来创建实例
Class cls = Person.class;
Object o = cls.newInstance();
下面结合上面的Class类来学习Field类,Method类,Constructor类。
Field类
Field用于存放类的成员变量,下面是Field类的一些相关的操作:
- 设置值:
void set(Object obj, Object value)
- 获取值:
get(Object obj)
- 忽略访问权限修饰符的安全检查,用于访问被访问权限符限制的成员变量:
setAccessible(true)
。Constructor类,Method类同样能使用 setAccessible(true) - 明确属性可通过以下方法获取,那个基本类型都有对应的方法
xxx.getDouble(Object o);
xxx.getFloat(Object o);
xxx.getBoolean(Object o);
xxx.getChar(Object o);
下面来使用上面的方法:
public class reflectDemo02 {
public static void main(String[] args) throws Exception {
Class cls = Person.class;
Field[] fields = cls.getFields();
//因为getFields()修饰的成员变量所以无输出
for (Field field : fields) {
System.out.println(field);
}
System.out.println("获取private修饰的变量");
Field[] declaredFields = cls.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println(declaredField);
}
System.out.println("修改private修饰的变量");
Person person = new Person(1, "张三");
//获取指定的属性
Field nameField = cls.getDeclaredField("name");
//忽略访问权限
nameField.setAccessible(true);
//更改private修饰的变量
nameField.set(person,"法外狂徒");
System.out.println(nameField.get(person));
}
}
程序运行结果:
获取所有的变量
private int learn.bean.Person.id
private java.lang.String learn.bean.Person.name
修改private修饰的变量
张三
法外狂徒
Constructor类
Constructor用于存放类的构造方法
- 创建对象:
T newInstance(Object... initargs)
如果使用空参数构造方法创建对象,操作可以简化:Class对象的newInstance方法
public class reflectDemo03 {
public static void main(String[] args) throws Exception {
Class personClass = Person.class;
//获取无参构造函数来创建对象
Constructor c1 = personClass.getConstructor();
Person p1 = (Person) c1.newInstance();
System.out.println(p1);
//获取有参构造函数
Constructor c2 = personClass.getConstructor(int.class, String.class);
Object p2 = c2.newInstance(1, "李四");
System.out.println(p2);
}
}
程序运行结果:
Person{id=0, name='null'}
Person{id=1, name='李四'}
Method类
Method用于存放类的方法对象,下面是Method的一些方法:
- 执行方法:
Object invoke(Object obj, Object... args)
,args为参数 - 获取方法名称:
String getName(String name)
- 获取某个方法:
getMethod(String name,Object... args)
,参数分别是方法名和参数类型 - 获取方法的参数类型数组:
Class<?>[] getParameterTypes()
- 获取方法的返回值类型:
Class<?> getReturnType()
在Person类中添加几个方法:
public void add(){
System.out.println("nothing");
}
public void add(String s){
System.out.println(s);
}
public void add(String s,String to){
System.out.println("add "+s+"to "+to);
}
分别调用这几个方法:
public class reflectDemo04 {
public static void main(String[] args) throws Exception {
Class personClass = Person.class;
Person person = new Person();
Method m1 = personClass.getMethod("add");
m1.invoke(person);
Method m2 = personClass.getMethod("add", String.class);
m2.invoke(person,"had add");
Method m3 = personClass.getMethod("add",new Class[]{String.class,String.class} );
m3.invoke(person,"something","wallet");
}
}
程序运行结果:
nothing
had add
add somethingto wallet
注解
无论是自定义注解还是预定义的注解,注解的元注解中的@Retention的值必须是RetentionPolicy.RUNTIME才能获取到。
@Retention(RetentionPolicy.RUNTIME):当前被描述的注解,会保留到class字节码文件中,并被JVM读取到。
getAnnotation(Class<T> annotationClass)
//类上
Annotation annotation = clazz.getAnnotation(Bean.class);
Annotation[] annotations = clazz.getAnnotations();
//字段上
Annotation annotation = field.getAnnotation(Bean.class);
Annotation[] annotations = field.getAnnotations();
//方法上
Annotation annotation = method.getAnnotation(Bean.class);
Annotation[] annotations = method.getAnnotations();
一个应用Java反射机制的案例:
应用Java反射模拟实现Spring的IoC容器
参考:
B站的视频教程:【黑马程序员-Java语言高级部分9.2】Java 反射
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/44324.html