1、概述
1.1、什么是动态语言?
程序运行时,可以改变程序的结构或变量类型,典型的动态语言有:Python,ruby和JavaScript等,比如如下的js代码:
function test(){
var s = "var a=3; var b=5;alert(a+b)";
eval(s);
}
C、C++和Java都不是动态语言,但是Java有一定的动态性,我们可以利用Java的反射机制,字节码操作获得类似动态语言的特性。Java的动态性让编程的时候更加灵活。
1.2 反射机制
反射机制:指的是可以在运行时加载、探知和使用 编译期间完全未知的类;
程序在运行状态中,可以动态的加载一个只知道名称的类,对于任意一个已加载的类,都能够知道这个类的所有属性和方法;对于任意一个对象,都可以调用它的任意一个方法和属性。
加载完类之后,在堆内存中就存在一个Class类型的对象(一个类只有一个Class对象),这个对象包含该类的完整的类的结构信息。我们可以通过这个对象看到类的结构,就像一面镜子一样,透过这个镜子看到类的结构,所以称之为反射。
2、反射
2.1 先创建一个类
public class User {
private int id;
private int age;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public User(int id, int age, String name) {
super();
this.id = id;
this.age = age;
this.name = name;
}
public User() {
}
}
该类中定义了三个私有属性并提供了对应的set和get方法,并且提供了满参和无参构造(记住一定要提供)。
2.2 Class对象的获取方式
Class对象,也称之为字节码对象,
1、被加载的每一个类都会生成一个Class对象;
2、枚举会被看成类,注解会看成接口;
3、同等维数的相同数据类型的数组会共享一个Class对象,比如:int[] a1 = new int[10]和int[] a2 = new int[30]两个数组,他们的:a1.getClass() 和 a2.getClass() 得到的是同一个对象。
4、基本数据类型也会对应一个Class对象。
Class对象的三种获取方式:
public class ReflectionTest {
public static void main(String[] args) throws ClassNotFoundException {
//类的Class对象 的三种获取方式
//1 通过类名类获取
Class clazz1 = User.class;
//2 通过对象的getClass()方法获取
User user = new User();
Class clazz2 = user.getClass();
//3 通过类的包名+类名获取
Class clazz3 = Class.forName("com.chinacreator.dzzz.dzzzkinterface.User");//会抛一个异常:ClassNotFoundException
System.out.println(clazz1==clazz2 && clazz2==clazz3);//结果是:true
}
}
一个类只有一个Class对象,所以我们通过以上三种方式获取的是同一个Class对象。
2.3 反射机制的常见作用
1、动态加载类,动态获取类的信息(属性、方法和构造器);
2、动态构造对象;
3、动态调用类和对象的任意方法、构造器和属性;
4、获取泛型信息;
5、处理注解。
3、反射操作:获取类、构造器、方法和属性
注意:
1、通过Class对象的newInstance() 创建对象时,调用的是目标类的无参构造,所以我们要养成一个好的习惯:每个类都要提供无参构造。(很多开源框架都使用了反射技术,都会用newInstance() 创建对象,如果我们没有提供 无参构造,那么就会报错);
2、使用Class对象去操作私有的方法(构造器)和属性的时候,必须先调用该Field(或Method、Constructor)的setAccessible(true),这个意思是忽略安全检查,可以访问,同时它还能提高效率。
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectionTest {
@SuppressWarnings("all")
public static void main(String[] args) throws Exception {
Class clazz = Class.forName("com.chinacreator.dzzz.dzzzkinterface.User");//会抛一个异常:ClassNotFoundException
//1 获取类信息
String name = clazz.getName();//获取包名+类名
String simpleName = clazz.getSimpleName();//获取类名
System.out.println("name:"+name+"========simpleName:"+simpleName);
//根据Class对象创建对象
User user = (User)clazz.newInstance();//这个方法调用的是无参构造创建对象,很多框架都是这么用的,所以切记要保留无参构造
System.out.println("------------------------------");
//2 属性操作
//2.1 获取属性信息
//Field[] fields = clazz.getFields();//获取所有的public的属性的数组
//Field field = clazz.getField("id");//根据名称获取 public的属性,如果找不到会抛异常
Field[] declaredFields = clazz.getDeclaredFields();//获取所有的属性,包括私有属性
Field declaredField = clazz.getDeclaredField("id");//根据属性名称获取属性
for (Field f : declaredFields) {
System.out.println(f.getName());
}
//2.2 属性操作:赋值等
declaredField.setAccessible(true);//设置为true,忽略检查,也可以提升效率
declaredField.set(user, 1001);
System.out.println(user.getId());
System.out.println("===============================================");
//3 方法操作
//3.1 获取方法信息
//Method[] methods = clazz.getMethods();//获取所有的public修饰的方法
//获取指定的方法:第一个参数:方法名,第二个参数:可变参数,是对应的方法的参数类型的class文件
//Method method1 = clazz.getMethod("setId", int.class);//获取指定的public修饰的方法,找不到会抛异常
//Method method2 = clazz.getMethod("getId", null);//获取指定的public修饰的方法
//获取所有的方法,包括私有方法
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method method : declaredMethods) {
System.out.println(method.getName());
}
Method setId = clazz.getDeclaredMethod("setId", int.class);
//运行方法
setId.setAccessible(true);//必须设置为true才能操作private方法
setId.invoke(user, 2002);//运行方法,进行赋值
Method getId = clazz.getDeclaredMethod("getId");
getId.setAccessible(true);
int id = (int)getId.invoke(user);
System.out.println(id);
System.out.println("--------------------------------------------------------");
//4 构造方法操作
//4.1 获取public构造信息
//Constructor[] constructors = clazz.getConstructors();//获取所有的public修饰的构造器的数组
//Constructor constructor = clazz.getConstructor(int.class,int.class,String.class);//根据参数类型获取构造方法
//4.2 获取包含private修饰的构造
Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
for (Constructor constructor : declaredConstructors) {
System.out.println(constructor);
}
Constructor declaredConstructor = clazz.getDeclaredConstructor(int.class,int.class,String.class);
//根据构造创建对象
User userNew = (User)declaredConstructor.newInstance(3003,23,"酱油哥");
System.out.println("userNew:"+userNew.getId()+"--"+userNew.getAge()+"--"+userNew.getName());
}
}
4、反射机制的性能、反射操作泛型和反射操作注解
4.1 反射机制的性能问题
setAccessible():我们在使用反射技术操作private修饰的方法和属性的时候,需要使用setAccessible()方法。该方法接收一个boolean参数:true则指示反射的对象在使用时取消Java语言的访问检查;false则指示应该进行Java语言的访问检查。一般设置为true,而且为true的时候可以提高反射的运行速度。
import java.lang.reflect.Method;
import junit.framework.Test;
public class ReflectionTest2 {
//测试反射机制对性能的影响
public static void main(String[] args) throws Exception {
test1();
test2();
test3();
}
//方法1:不使用反射操作方法20亿次
public static void test1(){
User user = new User();
long startTime = System.currentTimeMillis();
for(int i=0; i<2000000000L; i++){
user.getName();
}
long endTime = System.currentTimeMillis();
System.out.println("不使用反射操作1000000000次耗费时间:"+(endTime-startTime)+" 毫秒");
}
//方法2:使用反射,不跳过安全检查 操作方法20亿次
public static void test2() throws Exception{
User user = new User();
Method method = user.getClass().getDeclaredMethod("getName", null);
long startTime = System.currentTimeMillis();
for(int i=0; i<2000000000L; i++){
method.invoke(user, null);
}
long endTime = System.currentTimeMillis();
System.out.println("使用反射,不跳过安全检查,操作1000000000次耗费时间:"+(endTime-startTime)+" 毫秒");
}
//方法3:使用反射,跳过安全检查 操作方法20亿次
public static void test3() throws Exception{
User user = new User();
Method method = user.getClass().getDeclaredMethod("getName", null);
method.setAccessible(true);//跳过安全检查
long startTime = System.currentTimeMillis();
for(int i=0; i<2000000000L; i++){
method.invoke(user, null);
}
long endTime = System.currentTimeMillis();
System.out.println("使用反射,跳过安全检查,操作1000000000次耗费时间:"+(endTime-startTime)+" 毫秒");
}
}
相对来说:使用反射会降低速度,当取消安全检查后,会提升部分速度。
4.2 反射操作泛型
Java中的泛型仅仅是给编译器javac使用的,泛型是为了 确保数据的安全性和强制类型转换的麻烦,一旦编译完成,所有的和泛型有关的类型全部擦除。而我们的反射是运行时期操作的,所以反射是获取不到泛型的。
为了通过反射操作这些泛型以满足开发的需要,Java就新增了ParameterizedType、GenericArrayType、TypeVariable和WildcardType 四种类型来代表 不能被归一到Class类中的类型但是又和原始类型齐名的类型。
类型 | 说明 |
ParameterizedType | 表示一种参数化的类型,比如Collection<String> |
GenericArrayType | 表示一种元素类型是参数化类型或类型变量的数组类型 |
TypeVariable | 表示各种类型变量的公共父接口 |
WildcardType | 代表一种通配符类型表达式,比如:? , ? extends Number , ? super Integer 。wildcard单词就是通配符的意思。 |
接下来是ParameterizedType的使用示例:
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 ReflectionTest2 {
public void test1(Map<String, User> map, List<User> list){
System.out.println("这是参数有泛型的test1方法");
}
public Map<Integer, User> test2(){
System.out.println("这是返回值有泛型的test2方法");
return null;
}
//测试反射机制对性能的影响
public static void main(String[] args) throws Exception {
try{
//获取指定方法的参数的泛型信息
Method method = ReflectionTest2.class.getMethod("test1", Map.class,List.class);
Type[] types = method.getGenericParameterTypes();//获取参数的所有类型
//遍历获取每个参数类型的泛型信息
for(Type type : types){
System.out.println("参数类型:"+type);
if(type instanceof ParameterizedType){
ParameterizedType pType = (ParameterizedType)type;//强制转型
Type[] typeArguments = pType.getActualTypeArguments();
for (Type type2 : typeArguments) {
System.out.println(type+"参数的泛型类型:"+type2);
}
}
}
System.out.println("-----------------------------------------------------------");
//获取指定方法的返回值的泛型信息
Method method2 = ReflectionTest2.class.getMethod("test2", null);
Type returnType = method2.getGenericReturnType();
if(returnType instanceof ParameterizedType){
ParameterizedType rType = (ParameterizedType)returnType;//强制转型
Type[] typeArguments = rType.getActualTypeArguments();
for (Type type : typeArguments) {
System.out.println(returnType+"返回值的参数类型:"+type);
}
}
}catch(Exception e){
e.printStackTrace();
}
}
}
控制台打印:
参数类型:java.util.Map<java.lang.String, com.chinacreator.dzzz.dzzzkinterface.User>
java.util.Map<java.lang.String, com.chinacreator.dzzz.dzzzkinterface.User>参数的泛型类型:class java.lang.String
java.util.Map<java.lang.String, com.chinacreator.dzzz.dzzzkinterface.User>参数的泛型类型:class com.chinacreator.dzzz.dzzzkinterface.User
参数类型:java.util.List<com.chinacreator.dzzz.dzzzkinterface.User>
java.util.List<com.chinacreator.dzzz.dzzzkinterface.User>参数的泛型类型:class com.chinacreator.dzzz.dzzzkinterface.User
-----------------------------------------------------------
java.util.Map<java.lang.Integer, com.chinacreator.dzzz.dzzzkinterface.User>返回值的参数类型:class java.lang.Integer
java.util.Map<java.lang.Integer, com.chinacreator.dzzz.dzzzkinterface.User>返回值的参数类型:class com.chinacreator.dzzz.dzzzkinterface.User
4.3 反射操作注解
4.3.1 定义3个注解
自定义3个注解,分别用到类、方法和属性上:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)//只能用在类上
@Retention(RetentionPolicy.RUNTIME)//运行时有效
public @interface MyTable {
String value() default "";
String desc() default "";
}
@Target(ElementType.METHOD)//只能用在属性上
@Retention(RetentionPolicy.RUNTIME)
public @interface MyMethod {
String value();
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)//只能用在属性上
@Retention(RetentionPolicy.RUNTIME)
public @interface MyField {
String value();
String type() default "varchar";
int length() default 255;
}
4.3.2 定义实体类,加上自定义注解
@MyTable(value="t_person",desc="人员表")
public class Person {
@MyField(value="t_id",type="varchar",length=50)
private String id;
@MyField(value="t_name",type="varchar",length=200)
private String name;
@MyField(value="t_age",type="int",length=3)
private int age;
public Person() {
}
public Person(String id, String name, int age) {
super();
this.id = id;
this.name = name;
this.age = age;
}
@MyMethod("获取用户id的方法")
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
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;
}
}
4.3.3 使用反射操作注解
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectionAnnotationTest {
@SuppressWarnings("all")
public static void main(String[] args) throws Exception {
/**
* 使用反射操作注解的步骤:
* 1 获取类的Class对象
* 2 通过Class对象获取对应的构造,方法和属性
* 3 通过方法和属性的对象获取其对应的注解,根据主键获取主键的参数值
*/
Class clazz = Class.forName("com.chinacreator.dzzz.dzzzkinterface.Person");
// 1 获取类上面注解
Annotation[] annotations = clazz.getAnnotations();//获取类上的所有注解(类上可以指定多个注解)
MyTable myTable = (MyTable)clazz.getAnnotation(MyTable.class);//获取类上指定名称的注解对象
System.out.println("注解的value值:"+myTable.value()+" 注解的desc值:"+myTable.desc());
// 2 获取方法上的注解
Method method = clazz.getMethod("getId", null);
MyMethod myMethod = method.getAnnotation(MyMethod.class);
System.out.println("方法getId的注解内容是:"+myMethod.value());
System.out.println("---------------------------------------------");
// 3 获取属性上的注解
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
MyField myField = field.getAnnotation(MyField.class);
System.out.println("注解 "+myField+" 的描述:"+myField.value() +" "+myField.type()+" "+myField.length());
}
}
}