java反射机制

mac2025-01-25  34

java反射机制

java.lang.Class类十分特殊,用来表示java中类型(class/interface/enum/annotation/primitive type/void)本身

Class类的对象包含了某个被加载类的结构。一个被加载的类对应一个Class对象当一个class被加载,或当加载器(class loader)的defineClass(被JVM调用,JVM便自动产生一个Class对象)

Class类是Reflection的根源

针对任何想动态加载、运行的类,唯有先获得相应的class对象

程序在运行状态中,可以动态加载一个只有名称的类,对于任意一个已加载的类,都能狗知道这个类所有的属性和方法;对于任意一个对象,都能调用它的任意一个方法和属性。将类的各个组成部分封装为其他对象,这就是反射机制

Class<?> clazz = Class.forName(“reflection.User”);

加载完类之后,在堆内存中,就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就办函了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以称之为**<反射>**。

好处:

可以在程序运行过程中,操作这些对象。

可以解耦,提高程序的可扩展性。

一、Class类

/** * 对于Class类,源码的解释是这样的 * * Instances of the class {@code Class} represent classes and interfaces in a running Java application. * class类的实例在一个运行的java应用中代表了一个类和接口 * * An enum is a kind of class and an annotation is a kind of interface. * 枚举是一种类, 注解是一种接口 * * Every array also belongs to a class that is reflected as a {@code Class} object * that is shared by all arrays with the same element type and number * of dimensions. * * 每个数组也属于一个类(一个被反射成Class的对象),这个对象会被拥有同样元素类型以及维度,一维数组共享一个类,二维数组共享一个类 * * The primitive Java types ({@code boolean} * {@code byte}, {@code char}, {@code short}, * {@code int}, {@code long}, {@code float}, and * {@code double}), and the keyword {@code void} are also * represented as {@code Class} objects. * 基本数据类型 以及关键字void也会被Class对象来表示 * * * 对象是表示或封装一些数据。一个类被加载后,JVM会创建一个对应该类的Class对象,类的整个结构信息 * 会放到对应的Class对象中。 * 这个Class对象就像一面镜子,通过反射可以看到该类的所有信息 * */

二、获取Class类的三种方式

/** * 获取class的三种方式: * 1、Class.forName("全类名") :将字节码文件加载进内存,返回Class对象 * //多用于配置文件,将类名定义在配置文件中。读取文件,加载类 * 2、类名.class :通过类名的属性class获取 * //多用于参数的传递 * 3、对象.getClass() :getClass()方法在Object类中定义 * //多用于对象的获取字节码的方式 */ @Test public void testClass() throws Exception { //方法一、 Class name1 = Class.forName("reflection.bean.Person"); System.out.println("name1 = " + name1); //方法二、 Class name2 = Person.class; System.out.println("name2 = " + name2); //方法三、 Person person = new Person(); Class name3 = person.getClass(); System.out.println("name3 = " + name3); //是否为同一对象:是 System.out.println(name1 == name2); //true System.out.println(name1 == name3); //true } 结论:同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个

先创建一个test类

package reflection.bean; /** * @Author: zhour * @Date: Create in 8:52 2019/9/11 * @Function: */ public class Person { private String name; private int age; public String a; protected String protectedb; public String getA() { return a; } public void setA(String a) { this.a = a; } public String getProtectedb() { return protectedb; } public void setProtectedb(String protectedb) { this.protectedb = protectedb; } public Person() { } public Person(String name, int age) { this.name = name; this.age = age; } 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 void eat(String food){ System.out.println("eat "+food); } public void eat(){ System.out.println("eat..."); } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + ", a=" + a + '}'; } }

三、Class对象功能

/** * Class对象功能 * *获取功能: * 1、获取成员变量们 * Field[] getFields() 获取所有public修饰的成员变量 * Field getField(String name) * * Field[] getDeclaredFields() 获取所有的成员变量,不考虑修饰符 * Field getDeclaredField(String name) * * 2、获取构造方法们 * getConstructor(类<?>... parameterTypes) * Constructor<?>[] getConstructors() * * Constructor<T> getDeclaredConstructor(类<?>... parameterTypes) * Constructor<?>[] getDeclaredConstructors() * * 3、获取成员方法们 * Method getMethod(String name, 类<?>... parameterTypes) * Method[] getMethods() * * Method getDeclaredMethod(String name, 类<?>... parameterTypes) * Method[] getDeclaredMethods() * * 4、获取类名 * String getName() * * String getCanonicalName() */

3.1、获取成员变量

/** * 获得public修饰的成员变量 * * Field[] getFields() 获取所有public修饰的成员变量 * Field getField(String name) */ @Test public void test01() throws Exception { //获取Person的Class对象 Class pclass = Person.class; //Field[] getFields() Field[] fields = pclass.getFields(); for (Field field : fields) { System.out.println("field = " + field); } //结果: //field = public java.lang.String reflection.bean.Person.a //field = public int reflection.bean.Person.num //Field getField(String name) Person person = (Person)pclass.newInstance(); //通过反射实例化Person对象 Field a = pclass.getField("a"); //获取成员变量a的值 Object value = a.get(person); System.out.println("value = " + value); //value = null //设置成员变量a的值 a.set(person,"张三"); System.out.println(person); //Person{name='null', age=0, a=张三} }

3.2、暴力反射

setAccessible 启动和禁用访问安全检查的开关,可以访问私有属性值为true,代表反射的对象再使用时应该取消Java语言访问检查。值为false,代表反射对象应该实施jaba语言访问检查。并不是为true就能访问,为false就不能访问禁止安全检查,可以提高反射的运行速度 /* 获取所有的成员变量,不考虑修饰符,不进行安全检查,直接获取 Field[] getDeclaredFields() Field getDeclaredField(String name) */ @Test public void testDeclaredField() throws NoSuchFieldException, IllegalAccessException { /* * Field成员变量 * *操作 * 1、设置值 * void set(Object obj,Object value) * 2、获取值 * get(Object obj) * 3、忽略访问修饰符的安全检查 * name.setAccessible(true); 暴力反射 */ Class pclass = Person.class; Person person = new Person(); Field[] fields = pclass.getDeclaredFields(); for (Field field : fields) { System.out.println("field = " + field); } //field = private java.lang.String reflection.bean.Person.name //field = private int reflection.bean.Person.age //field = public java.lang.String reflection.bean.Person.a //field = protected java.lang.String reflection.bean.Person.protectedb System.out.println("=========获取private修饰的属性========="); Field name = pclass.getDeclaredField("name"); //忽略访问修饰符的安全检查:暴力反射 name.setAccessible(true); Object nameVal = name.get(person); System.out.println("name = " + nameVal); //name = null name.set(person,"周冉"); System.out.println(person); //Person{name='周冉', age=0, a=null} }

3.3、获取构造方法

/** * 测试构造方法 */ @Test public void testConstructor() throws Exception { /* * Constructor:构造方法 * 创建对象: * T newInstence(Object... initargs) * 如果使用空参构造方法创建对象,操作可以简化:Class对象的newInstance方法 * clazz.newInstence(); */ Class personClass = Person.class; Constructor constructor = personClass.getConstructor(String.class, int.class); System.out.println(constructor); //结果:public month1.reflect.bean.Person(java.lang.String,int) //创建对象 Object person1 = constructor.newInstance("zhouran", 24); System.out.println("person1 = " + person1); //person1 = Person{name='zhouran', age=24, a=null} //如果使用空参构造方法创建对象,操作可以简化:Class对象的newInstance方法 Object person2 = personClass.newInstance(); System.out.println(person2);//Person{name='null', age=0, a=null} }

3.4 、获取成员方法

/** * 获取成员方法 */ @Test public void testMethod() throws Exception { /* Method:方法对象 *执行方法: Object invoke(Object obj,Object...args) 传递方法名称和参数 */ Class personClass = Person.class; System.out.println("全类名>>>"+personClass.getName());//全类名>>>reflection.bean.Person Method[] methods = personClass.getMethods(); for (Method method : methods) { System.out.println("method = " + method); System.out.println("methodName = " + method.getName()); } Method eat = personClass.getMethod("eat",String.class); Person person = (Person)personClass.newInstance(); //执行方法 eat.invoke(person,"apple"); //eat apple Person user = (Person) clazz.newInstance(); Field name = clazz.getDeclaredField("name"); name.setAccessible(true); name.set(user,"周冉"); System.out.println(user.getName()); //周冉 }

3.5、注意事项

很多框架都是通过clazz.newInstance();来构建实例,这就需要我们在创建实例的时候 一定要加上无参的构造方法

四、反射机制性能问题

反射机制会对程序造成性能问题可以通过setAccessible提高性能 设置为true就不需要进行安全检查,可以提高反射的运行速度

做个对比:执行20亿次简单代码

一、正常new对象

@Test public void test(){ User user = new User(); long start = System.currentTimeMillis(); for (int i = 0; i < 2000000000L; i++) { user.getName(); } long end = System.currentTimeMillis(); System.out.println("正常new对象运行时间:"+ (end-start)); //结果:正常new对象运行时间:1006 }

二、反射(安全检查)

@Test public void test01() throws Exception { Class clazz = Class.forName("reflection.bean.User"); User user = (User)clazz.newInstance(); Method getName = clazz.getDeclaredMethod("getName", null); //getName.setAccessible(true); long start = System.currentTimeMillis(); for (int i = 0; i < 2000000000L; i++) { getName.invoke(user,null); } long end = System.currentTimeMillis(); System.out.println("反射安全检查运行时间:"+ (end-start)); //结果:反射安全检查运行时间:3492 }

三、反射(不进行安全检查)

@Test public void test01() throws Exception { Class clazz = Class.forName("reflection.bean.User"); User user = (User)clazz.newInstance(); Method getName = clazz.getDeclaredMethod("getName", null); getName.setAccessible(true); long start = System.currentTimeMillis(); for (int i = 0; i < 2000000000L; i++) { getName.invoke(user,null); } long end = System.currentTimeMillis(); System.out.println("反射不进行安全检查运行时间:"+ (end-start)); //结果:反射不进行安全检查运行时间:2377 } 以上对比可见,如果必须使用反射,并且需要频繁调用的时候,对机器性能要求比较高的情况下,将安全检查设置为true,可以程序的运行速度

五、反射操作泛型(Generic)

这个知识点以后用到再回来看,不用记。

java采用泛型擦除的机制来引入泛型。java中的泛型仅仅是给编译器 javac 使用的,确保数据安全的安全性和免去强制类型转换的麻烦。

但是,一旦编译完成,所有的和泛型有关的类型全部擦除

因为反射操作的是加载好以后的类,操作的是class对象

为了通过反射操作这些类型以迎合实际开发的需要,java新增了ParameterizedType,GenericArrayType,TypeVariable 和 WildcardType 集中类型来代表不能被归一到Class类中的类型但是又和原始类型齐名的类型。

ParameterizedType:表示一种参数化的类型,比如Collection

GenericArrayType:表示一种元素类型是参数化类型或者类型变量的数组类型

TypeVariable :是各种类型变量的公共父接口

WildcardType :代表一种通配符类型表达式,比如 ?, ? extends Number, ? super Integer

【Wildcard是一个单词:就是 “ 通配符 ”】

//参数泛型:Map<String,User> map, List<User> list public void test1(Map<String,User> map, List<User> list){ System.out.println("ReflectDemo1.test1"); } /** * function:获取参数泛型 */ @Test public void testParamType() throws Exception{ //获取test1方法 Method test1 = this.getClass().getDeclaredMethod("test1", Map.class,List.class); //获得方法里面的所有参数类型 Type[] parameterTypes = test1.getGenericParameterTypes(); for (Type type : parameterTypes) { //遍历每一个参数泛型 System.out.println("#"+type); if(type instanceof ParameterizedType){ Type[] typeArguments = ((ParameterizedType) type).getActualTypeArguments(); //参数不止一个,需要遍历 for (Type argument : typeArguments) { System.out.println(" 泛型类型:"+argument); } } } } /* 结果: #java.util.Map<java.lang.String, reflection.bean.User> 泛型类型:class java.lang.String 泛型类型:class reflection.bean.User #java.util.List<reflection.bean.User> 泛型类型:class reflection.bean.User */ //返回值泛型: Map<String,User> public Map<String,User> test2(){ System.out.println("ReflectDemo1.test"); return null; } /** * function:获取返回值泛型 */ @Test public void testReturnType() throws Exception{ Method test2 = this.getClass().getDeclaredMethod("test2",null); //返回值只有一个 Type returnType = test2.getGenericReturnType(); if (returnType instanceof ParameterizedType) { Type[] typeArguments = ((ParameterizedType) returnType).getActualTypeArguments(); for (Type argument : typeArguments) { System.out.println("返回值泛型:" + argument); } } } /* 结果: 返回值泛型:class java.lang.String 返回值泛型:class reflection.bean.User */

六、反射操作注解

1、什么是注解

Annotation是从jdk5.0开始引入的新技术作用 注解不是程序本身,他是对程序作出的解释可以被其他程序(比如编译器等)读取 格式 注解是以@注释名 在代码中存在的,还可以添加一些参数值,例如@SuppressWarning(value = “unchecked”) 使用 可以附加在package、class、method、field上面,相当于给他们添加了额外的辅助信息,我们可以通过反射机制实现对这些元数据的访问

2、内置注解

@Override

定义在java.lang.Override中,此注释只适用于修辞方法,表示一个方法声明打算重写超类中的另一个方法声明。

@Deprecated

定义在java.lang.Deprecated中,磁珠是可用于修辞方法、属性、类,表示不鼓励程序员使用这样的元素,通常是因为它很危险或存在更好的选择。

@SuppressWarnings

定义在java.lang.SuppressWarnings中,用来抑制编译时的警告信息

3、自定义注解

用@interface自定义注解时,自动继承了java.lang.annotation接口要点: @interface用来声明一个注解 格式为: public @interface 注解名{定义体} 其中的每一个方法实际上是声明了一个配置参数 方法的名称就是参数的名称返回值类型机遇是参数的类型(返回值类型只能是基本数据类型、Class、String、enum)可以通过deafult来声明参数的默认值如果有一个参数成员,参数名为value注解元素必须要有值。在定义注解元素时,经常使用空字符串、0作为默认值,也经常使用负数(比如:-1)表示不存在的含义 @Target(ElementType.TYPE) //标注这个注解只能写在类上 @Retention(RetentionPolicy.RUNTIME) //这个注解只在运行时有效 public @interface table1 { String value(); //如果只有一个参数 默认为value() } //使用时,直接写参数,如果是多个参数,就要写成k-v的形式 @table(value="123",name="zhouran") @table("123") public void test(){}

4、元注解

元注解的作用就是负责注释其他注解。Java定义了4个标准的meta-annotation类型,他们被用来提供对其他annotation类型做说明。这些类型和它们所支持的类在java.lang.annotation包中可以找到 @Target@Retention@Documented@Inherited

@Target

作用:用于描述注解的适用范围(即:被描述的注解可以用在什么地方)

所修饰范围取值ElementTypePackage包PACKAGE类、接口、枚举、annotation类型TYPE类型成员(方法、构造方法、成员变量、枚举值)CONSTRUCTOR:用于描述构造器FIELD:描述域METHOD:描述方法方法参数和本地变量LOCAL_VARIABLE:描述局部变量PARAMETER:描述参数

源码:

@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Target { /** * Returns an array of the kinds of elements an annotation type * can be applied to. * @return an array of the kinds of elements an annotation type * can be applied to */ ElementType[] value(); }

@Retention

作用:表示需要在什么级别保存该注释信息,用于描述注解的生命周期

取值RetentionPoloicy作用SOURCES在原文件中有效(即源文件保留)CLASS在class文件中有效(即class保留)RUNTIME在运行时有效(即运行时保留)为Rruntime可以被反射机制读取

注意:如果想使用反射读取注解,那么一定要写RUNTIME

源码:

@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Target { /** * Returns an array of the kinds of elements an annotation type * can be applied to. * @return an array of the kinds of elements an annotation type * can be applied to */ ElementType[] value(); }

@Document

作用:说明该注解将被包含在javadoc中

@Inherited

说明子类可以继承父类中的该注解

5、反射对注解的操作

方法总览 /** <A extends Annotation> A getAnnotation(Class<A> annotationClass) //获取指定名称的注解 Annotation[] getAnnotations() //或者所有注解,返回数组 boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)//判断目标上是否有某个注解 annotation.value() //获取注解上的value值 这里的value是注解类里面定义过的参数, 如果还有name参数,那么就是annotation.name() */ 创建一个描述表的注解 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface zrTable { String value(); } 创建一个描述字段的注解 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface zrField { String columName(); //列名 String type();//返回值类型 int length();//长度 } 创建实体类,并加上注解 @zrTable("tb_student") public class Student { @zrField(columName = "id",type = "int",length = 10) private int id; @zrField(columName = "sname",type = "varchar",length = 10) private String name; @zrField(columName = "age",type = "int",length = 3) private int age; public Student() { } public Student(int id, String name, int age) { this.id = id; this.name = name; this.age = age; } public int getId() { return id; } public void setId(int id) { this.id = id; } 开始操作注解 //获得学生类的Class对象 Class<?> clazz = Class.forName("annotation.Student"); //获得类的所有注解 Annotation[] annotations = clazz.getAnnotations(); for (Annotation annotation : annotations) { System.out.println("annotation = " + annotation); //结果:annotation = @annotation.zrTable(value=tb_student) } //获得类的指定的注解,入参注解类 zrTable annotation = clazz.getAnnotation(zrTable.class); System.out.println("annotation = " + annotation); //结果:annotation = @annotation.zrTable(value=tb_student) //获得指定属性的注解 Field name = clazz.getDeclaredField("name"); zrField nameAnnotation = name.getAnnotation(zrField.class); System.out.println(nameAnnotation.columName() +"---"+ nameAnnotation.type() +"---"+ nameAnnotation.length()); //结果:sname---varchar---10 //可以遍历所有属性上的注解 Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { zrField fieldAnnotation = field.getAnnotation(zrField.class); System.out.println(fieldAnnotation.columName() +"---" + fieldAnnotation.type() +"---"+ fieldAnnotation.length()); } //clazz.isAnnotationPresent(zrTable.class) 判断该类是否被@zrTable注解标注 if(clazz.isAnnotationPresent(zrTable.class)){ //结果为true }

七、总结

核心就是Class对象每一个类被虚拟机加载,都会有一个Class对象,这个Class对象包含了类的所有信息反射就是通过反射的API反复来操作这个对象,通过这个对象,可以 调用类的方法调用类的属性调用类的构造器调用类的注解调用类里面的泛型信息 反射会降低程序的性能,可以禁止安全检查setAccessible
最新回复(0)