1、反射概述
动态语言
-
动态语言:是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码都可以被引进,已有的函数可以被删除,或是其他结构上的变化。通俗来讲就是在运行时代码可以根据某些条件改变自身结构
-
主要的动态语言有:C#、JavaScript,PHP、Python
静态语言
- 静态语言运行时结构不可变的就是静态语言。如Java、C、C++。
- Java不是动态语言,但是Java可以称之为“准动态语言”。即Java有一定的动态性,我们可以利用Java的的反射机制,或得类似动态语言的特性。Java的动态性让编程的时候更加灵活。
1.1、反射的概念
Reflection(反射)是Java被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API 取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
1.2、反射的优点与缺点
**优点:**可以实现动态创建对象和编译,体现出很大的灵活性
**缺点:**对性能有影响。使用反射基本上是一种解释性的操作,我么可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于直接执行相同的操作。
1.3、反射相关的主要API
java.lang.Class:代表一个类
java.lang.reflect,Method:代表类的方法
java.lang.reflect.Filed:代表类的成员变量
java.lang.reflect.Constructor:代表类的构造器
1.4、Class类
1.4.2、Class类概述
在Object中定义了以下的方法,由于所有类都继承了Object类,那么此方法将被所有的类继承
public final native Class<?> getClass();
该方法返回值的类型是一个Class类,Class类是Java反射的源头,
Class类是描述类的类
- Class本身也是一个类
- Class对象只能由系统建立对象
- 一个加载的类在JVM中只会有一个Class实例
- 一个Class对象对应的是一个加载到JVM中的一个.class文件
- 每个类的实例都会记得自己由那个Class实例所生成
- Class类是Reflection的根源,针对任何你想动态加载、运行的类、唯有先获得相应的Class对象
1.4.2、Class类的常用方法
1.4.3、获取类的Class对象的几种方式
-
若已知具体的类,通过类的class属性获取,该方法最为可靠,程序性能最高。
//1.通过类名.class获取该类的Class对象 Class userClass = User.class; System.out.println(userClass.getName());
-
一直某个类的实例,调用该实例的getClass()方法获取Class对象
//2.已知某个类的实例,通过该实例的getClass()方法(注:该方法继承自Object类)获取到Class对象 User user = new User(); Class userClass1 = user.getClass(); System.out.println(userClass1.getName());
-
已知一个类的全类名,且该类在类路径下,可以通过Class类的静态方法forName()获取,但可能抛出ClassNotFoundException
//3.使用类的全限定名,通过使用Class类中的静态方法forName()获取 Class aClass = Class.forName("com.haha.demo.User"); System.out.println(aClass.getName());
-
内置基本数据类型可以直接用封装的类名.Type
//4.内置基本数据类型的封装类可以用类名.Type Class aClass1 = Integer.TYPE; System.out.println(aClass1.getName());
-
可以用ClassLoader
2、反射的底层分析
2.1、底层分析的背景
Java内存分析
在Java的内存中,分为堆、栈、方法区三块
堆:
- 存放new出来的对象和数组
- 可以被所有的线程所共享,不会存放别的对象引用
栈:
- 存放基本变量类型(会包含这个基本类型的具体数值)
- 引用对象的变量(会存放这个引用在堆里边的具体地址)
方法区(特殊的堆):
- 可以被所有的线程共享
- 包含了所有的class和static变量
2.2、类的加载
当程序主动使用某个类时,如果该类还未被加载到内存这种,则会通过以下三步对该类进行初始化。
-
加载
首先所有的 .java程序经过Java complier的编译生成 .class文件,当我们需要使用某些类时,Java会将.class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象(Class类在加载阶段就已经生成,且加载的每个类都有一个单独的Class对象)!
-
链接:是将Java类的二进制代码合并到JVM的运行状态之中的过程
- 验证:确保加载的类信息符合JVM的规范,没有安全方面的问题
- 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。
- 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程
-
初始化:
- 执行类构造器()方法的过程,类构造器()方法是由编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)。
- 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化
- 虚拟机会保证一个类的()方法在多线程环境中正确的加锁和同步。
2.3、什么时候发生类初始化
- 类的主动引用(一定会发生类的初始化)
- 当虚拟机启动时,先初始化main方法所在的类
- new一个类的对象
- 调用类的静态成员(除final常量)和静态方法
- 使用java.lang.reflect包的方法对类进行反射调用
- 当初始化一个类,如果其父类没有被初始化,则会先初始化其父类
- 类的被动引用(不会发生类的初始化)
- 当我们访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导致子类初始化。
- 通过数组定义类的引用,不会出发此类的初始化
- 引用常量(final)不会出发此类的初始化(常量在链接阶段就存入调用类的常量池中了)
2.4、类加载器的作用
类加载的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。
类缓存:标准的JavaSE类加载器可以按照要求查找类,但一旦某个类被加载到类加载器中,它将维持加载一段时间。不过JVM垃圾回收机制可以回收这些Class对象。
JVM定义了如下类型的类的加载器
- 引导类加载器:用C++进行编写的,是JVM自带的类加载器,负责Java平台核心库,用来装载核心类库,该加载器无法直接获取。
- 扩展类加载器:负责jre/lib/ext目录下的jar包装入工作库
- 系统类加载器:负责java-classpath所指目录下的类与jar包装入工作,是最常用的加载器。
3、反射的应用
3.1、获取类的信息
-
获取类名
可以通过创造的反射对象中forName()方法
Class c1 = Class.forName("com.haha.demo.User"); //获得类的名字 System.out.println(c1.getName()); //获得类的全类名,带路径 System.out.println(c1.getSimpleName()); //获得类的简单类名
-
获取类的属性
- getFields方法只能找到public属性
- getDeclaredFields可以找到全部属性
//获得类的属性 Field[] fields = c1.getFields(); //getFields方法只能找到public属性 for (Field field : fields) { System.out.println(field); } fields=c1.getDeclaredFields(); //getDeclaredFields可以找到全部属性 for (Field field : fields) { System.out.println(field); } //获得指定属性的名字 Field name = c1.getDeclaredField("name"); System.out.println("name="+name);
-
获得类的方法
- getMethods:获得本类及其父类的全部public方法
- getDeclaredMethods:获得本类的全部方法(包括私有的方法)
Method[] methods = c1.getMethods(); //获得本类及其父类的全部public方法 for (Method method : methods) { System.out.println(method); } methods=c1.getDeclaredMethods(); //获得本类的全部方法(包括私有的方法) for (Method method : methods) { System.out.println(method); } //通过类的方法名获得指定的方法 Method getName = c1.getMethod("getName", null); System.out.println(getName); Method setName = c1.getMethod("setName", String.class); System.out.println(setName);
-
获得类的构造器
//获得所有的构造器 Constructor[] constructors = c1.getConstructors(); for (Constructor constructor : constructors) { System.out.println(constructor); } //获得指定的构造器 Constructor constructor = c1.getDeclaredConstructor(int.class, String.class, String.class); System.out.println(constructor);
3.2、使用反射动态使用对象
-
通过反射动态获取类的Class对象
//获得类的Class对象 Class clazz = Class.forName("com.haha.demo.User");
-
使用类的Class对象,创建相应的对象
通过生成的类的Class对象中的newInstance()方法可以生成对应的对象
- 要创建的类必须要有一个无参构造器
- 类的构造器的访问权限需要足够
//使用类的Class对象,创建相应的对象 User user = (User) clazz.newInstance(); //该方法默认使用无参构造器 System.out.println(user);
-
通过反射获取类的构造器创建对象
当要创建对象的类中没有无参构造器时,我们此时需要获取类的有参构造器,通过类的有参构造器调用newInstance()方法。
//通过反射获取类的构造器创建对象 Constructor constructor = clazz.getConstructor(int.class, String.class, String.class); User user2 = (User) constructor.newInstance(1, "wanghaha", "111"); System.out.println(user2);
-
通过反射调用相应类的普通方法
- 首先通过Class类的getMethod方法取得一个Method对象,并设置此方法操作时所需要的参数类型
- 之后通过返回的方法调用invoke()方法,并向方法中传递设置的obj对象的参数信息
- 如果方法的声明为private,则需要在调用此invoke()方法前,显示的调用方法的**setAccessible(true)**方法,将可以访问private方法。
User user3 = (User) clazz.newInstance(); //通过反射获取一个方法 Method setName = clazz.getDeclaredMethod("setName", String.class); //invoke:激活,(对象,方法的值); setName.invoke(user3,"王哈哈"); System.out.println(user3.getName());
-
通过反射操作相应类的属性
User user4 = (User) clazz.newInstance(); Field name = clazz.getDeclaredField("name"); //不能直接操作私有属性,我们需要关闭程序的安全检测,设置属性或者方法的setAccessible(true); name.setAccessible(true); name.set(user4,"王哈哈"); System.out.println(user4.getName());
-
setAccessible
setAccessible的作用是启动和禁止访问安全检查的开关。
- 参数值为true则指示反射的对象在使用时应该取消Java语言的访问检查
- 为提高反射的效率,如果代码中必须使用反射,而该句代码需要频繁的被调用,那么要设置为true
- 使得原本无法访问的私有成员也可以被访问
- 参数值为false则指示反射的对象应该实施Java语言的访问检查
- 参数值为true则指示反射的对象在使用时应该取消Java语言的访问检查
4、反射的性能分析
-
采用普通方式new出一个对象使用其方法
//普通方法调用 public static void test01(){ User user = new User(); long startTime = System.currentTimeMillis(); for (int i = 0; i < 1000000000; i++) { user.getName(); //普通方式执行getName方法 } long endTime = System.currentTimeMillis(); System.out.println("普通方法执行十亿次的时间:"+(endTime-startTime)+"ms"); }
-
通过反射获取类的Class对象,获取相应Class对象中要调用的方法
//反射方式调用 public static void test02() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { User user = new User(); Class userClass = user.getClass(); Method getName = userClass.getDeclaredMethod("getName"); long startTime = System.currentTimeMillis(); for (int i = 0; i < 1000000000; i++) { getName.invoke(user,null); //反射的方式执行getName方法 } long endTime = System.currentTimeMillis(); System.out.println("反射方式执行十亿次的时间:"+(endTime-startTime)+"ms"); }
-
反射方式调用,关闭权限检测
public static void test03() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { User user = new User(); Class userClass = user.getClass(); Method getName = userClass.getDeclaredMethod("getName"); getName.setAccessible(true); long startTime = System.currentTimeMillis(); for (int i = 0; i < 1000000000; i++) { getName.invoke(user,null); //反射的方式执行getName方法 } long endTime = System.currentTimeMillis(); System.out.println("关闭权限检测执行十亿次的时间:"+(endTime-startTime)+"ms"); }
-
执行结果
4、反射操作泛型
-
Java采用泛型擦除的机制来引入泛型,Java中的泛型仅仅是给编译器Java Complier使用的,确保数据的安全性和免去强制类型转换的问题。但是,一旦编译完成,所有和泛型有关的类型全部被擦除。
-
为了采用反射操作这些类型Java新增了ParameterizedType(参数类型),GenericArrayType(泛型化参数数组),TypeVariable和WildcardType几种类型来代表不能被归一到Class类中的类型但是又和原始类型齐名的类型。
- ParameterizedType:表示是一种参数化类型,比如Collection
- GenericArrayType:表示一种元素是参数化类型或者类型变量的数组类型
- TypeVariable:是各种类型变量的公共父接口
- WildcardType:代表一种通配符类型表达式
Method method = Test08.class.getMethod("test01", Map.class, List.class); Type[] genericParameterTypes = method.getGenericParameterTypes(); for (Type genericParameterType : genericParameterTypes) { System.out.println("#:"+genericParameterType); if (genericParameterType instanceof ParameterizedType){ Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments(); for (Type actualTypeArgument : actualTypeArguments) { System.out.println(actualTypeArgument); } } }
5、反射操作注解
- getAnnotations
- getAnnocation
引入:ORM(Object relationship Mapping)模型——>对象关系映射模型
class Student {
private int id;
private String name;
private int age;
}
将以上代码区的对象,映射到以下的数据库表中
id | name | age |
---|---|---|
1 | 王哈哈 | 18 |
2 | 张三 | 26 |
3 | 李四 | 23 |
存在以下映射关系
- 类与数据库表对应
- 属性与字段对应
- 对象与表中的一条记录对应
我们尝试用注解和反射完成类和数据库表结构的映射关系
-
类名注解与属性注解的定义
//类名的注解 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @interface TableS{ String value(); } //属性的注解 @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @interface FiledS{ String columnName(); String type(); int length(); }
-
在类中使用创建的注解
@TableS("db_student") class Student { @FiledS(columnName = "db_id",type = "int",length = 10) private int id; @FiledS(columnName = "db_name",type = "varchar",length = 100) private String name; @FiledS(columnName = "db_age",type = "int",length = 3) private int age; }
-
获取类的注解
- 先通过反射获取类的Class对象
- 通过生成的Class对象调用getAnnotations()方法,即可返回作用于该类的全部注解
Class clazz = Class.forName("com.haha.demo.Student"); //通过反射获取类的全部注解 Annotation[] annotations = clazz.getAnnotations(); for (Annotation annotation : annotations) { System.out.println(annotation); }
-
获得类中属性的注解
Field field = clazz.getDeclaredField("name"); FiledS filedS = field.getAnnotation(FiledS.class); //获取到作用在属性上的注解fileds
-
获得注解中设置的值
- 调用反射生成对应的Class对象
- 通过生成的Class对象调用getAnnotation()方法,获取Class对象中指定的注解
- 调用注解中的属性值即可
//获取作用在类上的Tables注解的值,并打印 TableS tableS = (TableS) clazz.getAnnotation(TableS.class); String value = tableS.value(); System.out.println("value为:"+value);
获取作用在属性上的fileds注解的值,并打印 Field field = clazz.getDeclaredField("name"); FiledS filedS = field.getAnnotation(FiledS.class); //获取指定类的注解 System.out.println("属性的注解:"+filedS); System.out.println(filedS.columnName()); System.out.println(filedS.type()); System.out.println(filedS.length());
解的值,并打印
TableS tableS = (TableS) clazz.getAnnotation(TableS.class);
String value = tableS.value();
System.out.println(“value为:”+value);
```java
获取作用在属性上的fileds注解的值,并打印
Field field = clazz.getDeclaredField("name");
FiledS filedS = field.getAnnotation(FiledS.class); //获取指定类的注解
System.out.println("属性的注解:"+filedS);
System.out.println(filedS.columnName());
System.out.println(filedS.type());
System.out.println(filedS.length());