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类的三种方式
@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
);
System
.out
.println(name1
== name3
);
}
结论:同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个
先创建一个test类
package reflection
.bean
;
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对象功能
3.1、获取成员变量
@Test
public void test01() throws Exception
{
Class
pclass = Person
.class;
Field
[] fields
= pclass
.getFields();
for (Field field
: fields
) {
System
.out
.println("field = " + field
);
}
Person person
= (Person
)pclass
.newInstance();
Field a
= pclass
.getField("a");
Object value
= a
.get(person
);
System
.out
.println("value = " + value
);
a
.set(person
,"张三");
System
.out
.println(person
);
}
3.2、暴力反射
setAccessible
启动和禁用访问安全检查的开关,可以访问私有属性值为true,代表反射的对象再使用时应该取消Java语言访问检查。值为false,代表反射对象应该实施jaba语言访问检查。并不是为true就能访问,为false就不能访问禁止安全检查,可以提高反射的运行速度
@Test
public void testDeclaredField() throws NoSuchFieldException
, IllegalAccessException
{
Class
pclass = Person
.class;
Person person
= new Person();
Field
[] fields
= pclass
.getDeclaredFields();
for (Field field
: fields
) {
System
.out
.println("field = " + field
);
}
System
.out
.println("=========获取private修饰的属性=========");
Field name
= pclass
.getDeclaredField("name");
name
.setAccessible(true);
Object nameVal
= name
.get(person
);
System
.out
.println("name = " + nameVal
);
name
.set(person
,"周冉");
System
.out
.println(person
);
}
3.3、获取构造方法
@Test
public void testConstructor() throws Exception
{
Class
personClass = Person
.class;
Constructor constructor
= personClass
.getConstructor(String
.class, int.class);
System
.out
.println(constructor
);
Object person1
= constructor
.newInstance("zhouran", 24);
System
.out
.println("person1 = " + person1
);
Object person2
= personClass
.newInstance();
System
.out
.println(person2
);
}
3.4 、获取成员方法
@Test
public void testMethod() throws Exception
{
Class
personClass = Person
.class;
System
.out
.println("全类名>>>"+personClass
.getName());
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");
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
));
}
二、反射(安全检查)
@Test
public void test01() throws Exception
{
Class
clazz = Class
.forName("reflection.bean.User");
User user
= (User
)clazz
.newInstance();
Method getName
= clazz
.getDeclaredMethod("getName", null
);
long start
= System
.currentTimeMillis();
for (int i
= 0; i
< 2000000000L
; i
++) {
getName
.invoke(user
,null
);
}
long end
= System
.currentTimeMillis();
System
.out
.println("反射安全检查运行时间:"+ (end
-start
));
}
三、反射(不进行安全检查)
@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
));
}
以上对比可见,如果必须使用反射,并且需要频繁调用的时候,对机器性能要求比较高的情况下,将安全检查设置为true,可以程序的运行速度
五、反射操作泛型(Generic)
这个知识点以后用到再回来看,不用记。
java采用泛型擦除的机制来引入泛型。java中的泛型仅仅是给编译器 javac 使用的,确保数据安全的安全性和免去强制类型转换的麻烦。
但是,一旦编译完成,所有的和泛型有关的类型全部擦除
因为反射操作的是加载好以后的类,操作的是class对象
为了通过反射操作这些类型以迎合实际开发的需要,java新增了ParameterizedType,GenericArrayType,TypeVariable 和 WildcardType 集中类型来代表不能被归一到Class类中的类型但是又和原始类型齐名的类型。
ParameterizedType:表示一种参数化的类型,比如Collection
GenericArrayType:表示一种元素类型是参数化类型或者类型变量的数组类型
TypeVariable :是各种类型变量的公共父接口
WildcardType :代表一种通配符类型表达式,比如 ?, ? extends Number, ? super Integer
【Wildcard是一个单词:就是 “ 通配符 ”】
public void test1(Map
<String,User> map
, List
<User> list
){
System
.out
.println("ReflectDemo1.test1");
}
@Test
public void testParamType() throws Exception
{
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
);
}
}
}
}
public Map
<String,User> test2(){
System
.out
.println("ReflectDemo1.test");
return null
;
}
@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
);
}
}
}
六、反射操作注解
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();
}
@table("123")
public void test(){}
4、元注解
元注解的作用就是负责注释其他注解。Java定义了4个标准的meta-annotation类型,他们被用来提供对其他annotation类型做说明。这些类型和它们所支持的类在java.lang.annotation包中可以找到
@Target@Retention@Documented@Inherited
@Target
作用:用于描述注解的适用范围(即:被描述的注解可以用在什么地方)
所修饰范围取值ElementType
Package包PACKAGE类、接口、枚举、annotation类型TYPE类型成员(方法、构造方法、成员变量、枚举值)CONSTRUCTOR:用于描述构造器FIELD:描述域METHOD:描述方法方法参数和本地变量LOCAL_VARIABLE:描述局部变量PARAMETER:描述参数 源码:
@Documented
@Retention(RetentionPolicy
.RUNTIME
)
@Target(ElementType
.ANNOTATION_TYPE
)
public @
interface Target {
ElementType
[] value();
}
@Retention
作用:表示需要在什么级别保存该注释信息,用于描述注解的生命周期
取值RetentionPoloicy作用
SOURCES在原文件中有效(即源文件保留)CLASS在class文件中有效(即class保留)RUNTIME在运行时有效(即运行时保留)为Rruntime可以被反射机制读取 注意:如果想使用反射读取注解,那么一定要写RUNTIME
源码:
@Documented
@Retention(RetentionPolicy
.RUNTIME
)
@Target(ElementType
.ANNOTATION_TYPE
)
public @
interface Target {
ElementType
[] value();
}
@Document
作用:说明该注解将被包含在javadoc中
@Inherited
说明子类可以继承父类中的该注解
5、反射对注解的操作
方法总览
创建一个描述表的注解
@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
<?> clazz
= Class
.forName("annotation.Student");
Annotation
[] annotations
= clazz
.getAnnotations();
for (Annotation annotation
: annotations
) {
System
.out
.println("annotation = " + annotation
);
}
zrTable annotation
= clazz
.getAnnotation(zrTable
.class);
System
.out
.println("annotation = " + annotation
);
Field name
= clazz
.getDeclaredField("name");
zrField nameAnnotation
= name
.getAnnotation(zrField
.class);
System
.out
.println(nameAnnotation
.columName() +"---"+ nameAnnotation
.type() +"---"+ nameAnnotation
.length());
Field
[] fields
= clazz
.getDeclaredFields();
for (Field field
: fields
) {
zrField fieldAnnotation
= field
.getAnnotation(zrField
.class);
System
.out
.println(fieldAnnotation
.columName() +"---"
+ fieldAnnotation
.type() +"---"+ fieldAnnotation
.length());
}
if(clazz
.isAnnotationPresent(zrTable
.class)){
}
七、总结
核心就是Class对象每一个类被虚拟机加载,都会有一个Class对象,这个Class对象包含了类的所有信息反射就是通过反射的API反复来操作这个对象,通过这个对象,可以
调用类的方法调用类的属性调用类的构造器调用类的注解调用类里面的泛型信息 反射会降低程序的性能,可以禁止安全检查setAccessible