反射机制是Java动态性之一,而说到动态性首先得了解动态语言。那么何为动态语言?
一、动态语言
动态语言,是指程序在运行时可以改变其结构:新的函数可以引进,已有的函数可以被删除等结构上的变化。比如常见的JavaScript就是动态语言,除此之外Ruby,Python等也属于动态语言,而C、C++则不属于动态语言。
二、Java是动态语言吗?
从动态语言能在运行时改变程序结构结构或则变量类型上看,Java和C、C++一样都不属于动态语言。
但是JAVA却又一个非常突出的与动态相关的机制:反射机制。Java通过反射机制,可以在程序运行时加载,探知和使用编译期间完全未知的类,并且可以生成相关类对象实例,从而可以调用其方法或则改变某个属性值。所以JAVA也可以算得上是一个半动态的语言。
三、反射机制:
1.反射机制概念
在Java中的反射机制是指在运行状态中,对于任意一个类都能够知道这个类所有的属性和方法;并且对于任意一个对象,都能够调用它的任意一个方法;这种动态获取信息以及动态调用对象方法的功能成为Java语言的反射机制。
2.反射的应用场合
在Java程序中许多对象在运行是都会出现两种类型:编译时类型和运行时类型。
编译时的类型由声明对象时实用的类型来决定,运行时的类型由实际赋值给对象的类型决定
如:
Person p=new Student();
其中编译时类型为Person,运行时类型为Student。
除此之外,程序在运行时还可能接收到外部传入的对象,该对象的编译时类型为Object,但是程序有需要调用该对象的运行时类型的方法。为了解决这些问题,程序需要在运行时发现对象和类的真实信息。然而,如果编译时根本无法预知该对象和类属于哪些类,程序只能依靠运行时信息来发现该对象和类的真实信息,此时就必须使用到反射了。
四、Java反射API
反射API用来生成JVM中的类、接口或则对象的信息。
- Class类:反射的核心类,可以获取类的属性,方法等信息。
- Field类:Java.lang.reflec包中的类,表示类的成员变量,可以用来获取和设置类之中的属性值。
- Method类: Java.lang.reflec包中的类,表示类的方法,它可以用来获取类中的方法信息或者执行方法。
- Constructor类: Java.lang.reflec包中的类,表示类的构造方法。
五、使用反射的步骤
1.步骤
- 获取想要操作的类的Class对象
- 调用Class类中的方法
- 使用反射API来操作这些信息
2.获取Class对象的方法
- 调用某个对象的getClass()方法
Person p=new Person();
Class clazz=p.getClass();
- 调用某个类的class属性来获取该类对应的Class对象
Class clazz=Person.class;
- 使用Class类中的forName()静态方法; (最安全/性能最好)
Class clazz=Class.forName("类的全路径"); (最常用)
3.获取方法和属性信息
当我们获得了想要操作的类的Class对象后,可以通过Class类中的方法获取并查看该类中的方法和属性。
示例代码:
<<<<<<<<<<<<<<<<<<<<<<Person类<<<<<<<<<<<<<<<<<<<<<<<<<<
package reflection;
public class Person {
private String name;
private String gender;
private int age;
public Person() {
}
public Person(String name, String gender, int age) {
this.name = name;
this.gender = gender;
this.age = age;
}
//getter和setter方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String toString(){
return "姓名:"+name+" 性别:"+gender+" 年龄:"+age;
}
}
<<<<<<<<<<<<<<<<使用反射<<<<<<<<<<<<<<<<<<<
package reflection;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/*
* 通过用户输入类的全路径,来获取该类的成员方法和属性
* Declared获取全部不管是私有和公有
* 1.获取访问类的Class对象
* 2.调用Class对象的方法返回访问类的方法和属性信息
*/
public class Test {
public static void main(String[] args) {
try {
//获取Person类的Class对象
Class clazz=Class.forName("reflection.Person");
//获取Person类的所有方法信息
Method[] method=clazz.getDeclaredMethods();
for(Method m:method){
System.out.println(m.toString());
}
//获取Person类的所有成员属性信息
Field[] field=clazz.getDeclaredFields();
for(Field f:field){
System.out.println(f.toString());
}
//获取Person类的所有构造方法信息
Constructor[] constructor=clazz.getDeclaredConstructors();
for(Constructor c:constructor){
System.out.println(c.toString());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
输出结果:
方法信息:
public java.lang.String reflection.Person.toString()
private java.lang.String reflection.Person.getName()
private void reflection.Person.setName(java.lang.String)
public void reflection.Person.setAge(int)
public int reflection.Person.getAge()
public java.lang.String reflection.Person.getGender()
public void reflection.Person.setGender(java.lang.String)
属性信息:
private java.lang.String reflection.Person.name
private java.lang.String reflection.Person.gender
private int reflection.Person.age
构造方法信息
private reflection.Person()
public reflection.Person(java.lang.String,java.lang.String,int)
4.创建对象
当我们获取到所需类的Class对象后,可以用它来创建对象,创建对象的方法有两种:
- 使用Class对象的newInstance()方法来创建该Class对象对应类的实例,但是这种方法要求该Class对象对应的类有默认的空构造器。
- 先使用Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建 Class对象对应类的实例,通过这种方法可以选定构造方法创建实例。
示例代码:
package reflection;
import java.lang.reflect.Constructor;
public class Demo01 {
public static void main(String[] args) {
try {
//获取Person类的Class对象
Class clazz=Class.forName("reflection.Person");
/**
* 第一种方法创建对象
*/
//创建对象
Person p=(Person) clazz.newInstance();
//设置属性
p.setName("张三");
p.setAge(16);
p.setGender("男");
System.out.println(p.toString());
/**
* 第二种方法创建
*/
//获取构造方法
Constructor c=clazz.getDeclaredConstructor(String.class,String.class,int.class);
//创建对象并设置属性
Person p1=(Person) c.newInstance("李四","男",20);
System.out.println(p1.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
输出结果:
姓名:张三 性别:男 年龄: 16
姓名:李四 性别:男 年龄: 20
应用——Annotation(注解)
如果没有用来读取注解的方法和工作,那么注解也就不会比注释更有用处了。使用注解的过程中,很重要的一部分就是创建于使用注解处理器。Java SE5扩展了反射机制的API,以帮助程序员快速的构造自定义注解处理器。
注解处理器类库(java.lang.reflect.AnnotatedElement):
Java使用Annotation接口来代表程序元素前面的注解,该接口是所有Annotation类型的父接口。除此之外,Java在java.lang.reflect 包下新增了AnnotatedElement接口,该接口代表程序中可以接受注解的程序元素,该接口主要有如下几个实现类:
- Class:类定义 - Constructor:构造器定义
- Field:类的成员变量定义
- Method:类的方法定义
- Package:类的包定义
java.lang.reflect 包下主要包含一些实现反射功能的工具类,实际上,java.lang.reflect 包所有提供的反射API扩充了读取运行时Annotation信息的能力。当一个Annotation类型被定义为运行时的Annotation后,该注解才能是运行时可见,当class文件被装载时被保存在class文件中的Annotation才会被虚拟机读取。
AnnotatedElement 接口是所有程序元素(Class、Method和Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象之后,程序就可以调用该对象的如下四个方法来访问Annotation信息:
方法1: T getAnnotation(Class annotationClass): 返回该程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null。
方法2:Annotation[] getAnnotations():返回该程序元素上存在的所有注解。
方法3:boolean is AnnotationPresent(Class< ?extends Annotation> annotationClass):判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false.
方法4:Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注释。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组。)该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响。
一个简单的注解处理器:
/***********注解声明***************/
/**
* 水果名称注解
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitName {
String value() default "";
}
/**
* 水果颜色注解
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitColor {
/**
* 颜色枚举
*/
public enum Color {
BULE, RED, GREEN
};
/**
* 颜色属性
*/
Color fruitColor() default Color.GREEN;
}
/**
* 水果供应者注解
*
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitProvider {
/**
* 供应商编号
*/
public int id() default -1;
/**
* 供应商名称
*/
public String name() default "";
/**
* 供应商地址
*/
public String address() default "";
}
/*********** 注解使用 ***************/
public class Apple {
@FruitName("Apple")
private String appleName;
@FruitColor(fruitColor = Color.RED)
private String appleColor;
@FruitProvider(id = 1, name = "陕西红富士集团", address = "陕西省西安市延安路89号红富士大厦")
private String appleProvider;
public void setAppleColor(String appleColor) {
this.appleColor = appleColor;
}
public String getAppleColor() {
return appleColor;
}
public void setAppleName(String appleName) {
this.appleName = appleName;
}
public String getAppleName() {
return appleName;
}
public void setAppleProvider(String appleProvider) {
this.appleProvider = appleProvider;
}
public String getAppleProvider() {
return appleProvider;
}
public void displayName() {
System.out.println("水果的名字是:苹果");
}
}
/*********** 注解处理器 ***************/
public class FruitInfoUtil {
public static void getFruitInfo(Class<?> clazz) {
String strFruitName = " 水果名称:";
String strFruitColor = " 水果颜色:";
String strFruitProvicer = "供应商信息:";
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(FruitName.class)) {
FruitName fruitName = (FruitName) field
.getAnnotation(FruitName.class);
strFruitName = strFruitName + fruitName.value();
System.out.println(strFruitName);
} else if (field.isAnnotationPresent(FruitColor.class)) {
FruitColor fruitColor = (FruitColor) field
.getAnnotation(FruitColor.class);
strFruitColor = strFruitColor
+ fruitColor.fruitColor().toString();
System.out.println(strFruitColor);
} else if (field.isAnnotationPresent(FruitProvider.class)) {
FruitProvider fruitProvider = (FruitProvider) field
.getAnnotation(FruitProvider.class);
strFruitProvicer = " 供应商编号:" + fruitProvider.id() + " 供应商名称:"
+ fruitProvider.name() + " 供应商地址:"
+ fruitProvider.address();
System.out.println(strFruitProvicer);
}
}
}
}
/***********main方法***************/
public class FruitRun {
public static void main(String[] args) {
FruitInfoUtil.getFruitInfo(Apple.class);
}
}
/***********输出结果***************/
水果名称:Apple
水果颜色:RED
供应商编号:1 供应商名称:陕西红富士集团 供应商地址:陕西省西安市延安路89号红富士大厦
Java注解的基础知识点(见下面导图):[图片上传失败...(image-c6dc6d-1531036897498)]
通过反射操作泛型,注解
上篇文章我介绍了Java反射的基本知识,如果没看过的同学可以去看我的上一篇文章 反射概念与基础,今天这篇文章主要介绍一下反射地具体应用实例,分别是通过Java反射操作泛型,和反射操作注解(不了解”注解”的同学可以看我的另一篇文章java注解)。
一、反射操作泛型(Generic)
Java采用泛型擦除机制来引入泛型。Java中的泛型仅仅是给编译器Javac使用的,确保数据的安全性和免去强制类型转换的麻烦。但是编译一旦完成,所有和泛型有关的类型全部被擦除。
为了通过反射操作这些类型以迎合实际开发的需要,Java新增了ParameterizedType,GenericArrayType,TypeVariable和WildcardType几种类型来代表不能被归一到Class类中的类型但是又和原始类型齐名的类型。
- ParameterizedType:表示一种参数化的类型,比如Collection< String >
- GenericArrayType:表示一种元素类型是参数化类型或者类型变量的数组类型
- TypeVariable:是各种类型变量的公共父接口
- WildcardType:代表一种通配符类型表达式,比如?、? extends Number、? super Integer。(wildcard是一个单词:就是”通配符“)
代码示例:
package reflection;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
/**
* 通过反射获取泛型信息
*
*/
public class Demo{
//定义两个带泛型的方法
public void test01(Map<String,Person> map,List<Person> list){
System.out.println("Demo.test01()");
}
public Map<Integer,Person> test02(){
System.out.println("Demo.test02()");
return null;
}
public static void main(String[] args) {
try {
//获得指定方法参数泛型信息
Method m = Demo.class.getMethod("test01", Map.class,List.class);
Type[] t = m.getGenericParameterTypes();
for (Type paramType : t) {
System.out.println("#"+paramType);
if(paramType instanceof ParameterizedType){
//获取泛型中的具体信息
Type[] genericTypes = ((ParameterizedType) paramType).getActualTypeArguments();
for (Type genericType : genericTypes) {
System.out.println("泛型类型:"+genericType);
}
}
}
//获得指定方法返回值泛型信息
Method m2 = Demo.class.getMethod("test02", null);
Type returnType = m2.getGenericReturnType();
if(returnType instanceof ParameterizedType){
Type[] genericTypes = ((ParameterizedType) returnType).getActualTypeArguments();
for (Type genericType : genericTypes) {
System.out.println("返回值,泛型类型:"+genericType);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
输出结果:
java.util.Map< java.lang.String, reflection.Person >
泛型类型:class java.lang.String
泛型类型:class reflection.Person
java.util.List< reflection.Person >
泛型类型:class reflection.Person
返回值,泛型类型:class java.lang.Integer
返回值,泛型类型:class reflection.Person
二、反射操作注解(Annotation)
具体使用可以就看我的之前的文章 注解处理器
好了,介绍了两个简单的反射的应用,在顺便讲一下Java反射机制的性能问题。
三、反射性能测试
Method/Constructor/Field/Element 都继承了 AccessibleObject , AccessibleObject 类中有一个 setAccessible 方法:
public void setAccessible(booleanflag)throws SecurityException
{
...
}
该方法有两个作用:
- 启用/禁用访问安全检查开关:值为true,则指示反射的对象在使用时取消Java语言访问检查; 值为false,则指示应该实施Java语言的访问检查;
- 可以禁止安全检查, 提高反射的运行效率.
测试代码:
package reflection;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class TestReflect {
public static void testNoneReflect() {
Person user = new Person();
long start = System.currentTimeMillis();
for (long i = 0; i < Integer.MAX_VALUE; ++i) {
user.getName();
}
long count = System.currentTimeMillis() - start;
System.out.println("没有反射, 共消耗 <" + count + "> 毫秒");
}
public static void testNotAccess() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Person user = new Person();
Method method = Class.forName("reflection.Person").getMethod("getName");
long start = System.currentTimeMillis();
for (long i = 0; i < Integer.MAX_VALUE; ++i) {
method.invoke(user, null);
}
long count = System.currentTimeMillis() - start;
System.out.println("没有访问权限, 共消耗 <" + count + "> 毫秒");
}
public static void testUseAccess() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Person user = new Person();
Method method = Class.forName("reflection.Person").getMethod("getName");
method.setAccessible(true);
long start = System.currentTimeMillis();
for (long i = 0; i < Integer.MAX_VALUE; ++i) {
method.invoke(user, null);
}
long count = System.currentTimeMillis() - start;
System.out.println("有访问权限, 共消耗 <" + count + "> 毫秒");
}
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
testNoneReflect();
testNotAccess();
testUseAccess();
}
}
输出结果:
没有反射, 共消耗 <912> 毫秒
没有访问权限, 共消耗 <4366> 毫秒 有访问权限, 共消耗 <2843> 毫秒
可以看到使用反射会比直接调用慢2000 毫秒 ,但是前提是该方法会执行20E+次(而且服务器的性能也肯定比我的机器要高),因此在我们的实际开发中,其实是不用担心反射机制带来的性能消耗的,而且禁用访问权限检查,也会有性能的提升。