JAVA反射与注解

心已赠人 2022-06-09 06:16 349阅读 0赞

JAVA反射与注解

前言
现在在我们构建自己或公司的项目中,或多或少都会依赖几个流行比较屌的第三方库,比如:Butter Knife、Retrofit 2、Dagger 2、GreenDao等,如果你没用过,那你需要找时间补一下啦;有时在使用后我们会好奇他们到底是怎么做到这种简洁、高效、松耦合等诸多优点的,当然这里我不探讨它们具体怎么实现的 (可以看看我之前写的几篇文章) ,而关心的是它们都用到同样的技术那就是本篇所讲的反射和注解,并实现的依赖注入。

阅读本篇文章有助于你更好的理解这些大形框架的原理和复习Java的知识点。为什么要把反射放在前面讲呢,实际上是因为我们学习注解的时候需要用到反射机制,所以,先学习反射有助于理解后面的知识。

JAVA反射


主要是指程序可以访问,检测和修改它本身状态或行为的一种能力,并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。

反射机制是什么

反射机制就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

用一句话总结就是反射可以实现在运行时可以知道任意一个类的属性和方法。

反射机制能做什么

  1. ***反射机制主要提供了以下功能:***
  2. - 在运行时判断任意一个对象所属的类; - 在运行时构造任意一个类的对象; - 在运行时判断任意一个类所具有的成员变量和方法; - 在运行时调用任意一个对象的方法; - 生成动态代理(ps:这个知识点也很重要)

Java 反射机制的应用场景

  • 逆向代码 ,例如反编译
  • 与注解相结合的框架 例如Retrofit
  • 单纯的反射机制应用框架 例如EventBus
  • 动态生成类框架 例如Gson

反射机制的优点与缺点

为什么要用反射机制?直接创建对象不就可以了吗,这就涉及到了动态与静态的概念

静态编译:在编译时确定类型,绑定对象,即通过。

动态编译:运行时确定类型,绑定对象。动态编译最大限度发挥了java的灵活性,体现了多态的应用,有以降低类之间的藕合性。

优点

可以实现动态创建对象和编译,体现出很大的灵活性,特别是在J2EE的开发中它的灵活性就表现的十分明显。比如,一个大型的软件,不可能一次就把把它设计的很完美,当这个程序编译后,发布了,当发现需要更新某些功能时,我们不可能要用户把以前的卸载,再重新安装新的版本,假如这样的话,这个软件肯定是没有多少人用的。采用静态的话,需要把整个程序重新编译一次才可以实现功能的更新,而采用反射机制的话,它就可以不用卸载,只需要在运行时才动态的创建和编译,就可以实现该功能。

缺点

对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于只直接执行相同的操作。

理解Class类和类类型

想要了解反射首先理解一下Class类,它是反射实现的基础。
类是java.lang.Class类的实例对象,而Class是所有类的类(There is a class named Class)
对于普通的对象,我们一般都会这样创建和表示:

Code code1 = new Code();

但是我们查看Class的源码时,是这样写的:

  1. private Class(ClassLoader loader) {
  2. classLoader = loader;
  3. }

可以看到构造器是私有的,只有JVM可以创建Class的对象,因此不可以像普通类一样new一个Class对象,虽然我们不能new一个Class对象,但是却可以通过已有的类得到一个Class对象,共有三种方式,如下:

  1. Class c1 = Code.class; 这说明任何一个类都有一个隐含的静态成员变量class,这种方式是通过获取类的静态成员变量class得到的
  2. Class c2 = code1.getClass(); code1Code的一个对象,这种方式是通过一个类的对象的getClass()方法获得的
  3. Class c3 = Class.forName("com.trigl.reflect.Code"); 这种方法是Class类调用forName方法,通过一个类的全量限定名获得
  4. 这里,c1c2c3都是Class的对象,他们是完全一样的,而且有个学名,叫做Code的类类型(class type)。
  5. 这里就让人奇怪了,前面不是说CodeClass的对象吗,而c1c2c3也是Class的对象,那么Codec1c2c3不就一样了吗?为什么还叫Code什么类类型?这里不要纠结于它们是否相同,只要理解类类型是干什么的就好了,顾名思义,类类型就是类的类型,也就是描述一个类是什么,都有哪些东西,所以我们可以通过类类型知道一个类的属性和方法,并且可以调用一个类的属性和方法,这就是反射的基础。

举个简单例子代码:

  1. public class ReflectDemo {
  2. public static void main(String[] args) throws ClassNotFoundException {
  3. //第一种:Class c1 = Code.class;
  4. Class class1=ReflectDemo.class;
  5. System.out.println(class1.getName());
  6. //第二种:Class c2 = code1.getClass();
  7. ReflectDemo demo2= new ReflectDemo();
  8. Class c2 = demo2.getClass();
  9. System.out.println(c2.getName());
  10. //第三种:Class c3 = Class.forName("com.trigl.reflect.Code");
  11. Class class3 = Class.forName("com.tengj.reflect.ReflectDemo");
  12. System.out.println(class3.getName());
  13. }
  14. }

执行结果:

  1. 1.com.tengj.reflect.ReflectDemo
  2. 2.com.tengj.reflect.ReflectDemo
  3. 3.com.tengj.reflect.ReflectDemo

Java反射相关操作

在这里先看一下sun为我们提供了那些反射机制中的类:

java.lang.Class; java.lang.reflect.Constructor;
java.lang.reflect.Field; java.lang.reflect.Method;
java.lang.reflect.Modifier;

前面我们知道了怎么获取Class,那么我们可以通过这个Class干什么呢?
总结如下:

  1. 获取成员方法Method
  2. 获取成员变量Field
  3. 获取构造函数Constructor 下面来具体介绍

1.获取成员方法信息

两个参数分别是方法名和方法参数类的类类型列表。

  1. public Method getDeclaredMethod(String name, Class<?>... parameterTypes) // 得到该类所有的方法,不包括父类的 public Method getMethod(String name, Class<?>... parameterTypes) // 得到该类所有的public方法,包括父类的 //具体使用 Method[] methods = class1.getDeclaredMethods();//获取class对象的所有声明方法
  2. Method[] allMethods = class1.getMethods();//获取class对象的所有public方法 包括父类的方法
  3. Method method = class1.getMethod("info", String.class);//返回次Class对象对应类的、带指定形参列表的public方法
  4. Method declaredMethod = class1.getDeclaredMethod("info", String.class);//返回次Class对象对应类的、带指定形参列表的方法

举个例子:

例如类A有如下一个方法:

  1. public void fun(String name,int age) {
  2. System.out.println("我叫"+name+",今年"+age+"岁");
  3. }

现在知道A有一个对象a,那么就可以通过:

  1. Class c = Class.forName("com.tengj.reflect.Person"); //先生成class
  2. Object o = c.newInstance(); //newInstance可以初始化一个实例
  3. Method method = c.getMethod("fun", String.class, int.class);//获取方法
  4. method.invoke(o, "tengj", 10); //通过invoke调用该方法,参数第一个为实例对象,后面为具体参数值

完整代码如下:

  1. public class Person {
  2. private String name;
  3. private int age;
  4. private String msg="hello wrold";
  5. public String getName() {
  6. return name;
  7. }
  8. public void setName(String name) {
  9. this.name = name;
  10. }
  11. public int getAge() {
  12. return age;
  13. }
  14. public void setAge(int age) {
  15. this.age = age;
  16. }
  17. public Person() {
  18. }
  19. private Person(String name) {
  20. this.name = name;
  21. System.out.println(name);
  22. }
  23. public void fun() {
  24. System.out.println("fun");
  25. }
  26. public void fun(String name,int age) {
  27. System.out.println("我叫"+name+",今年"+age+"岁");
  28. }
  29. }
  30. public class ReflectDemo {
  31. public static void main(String[] args){
  32. try {
  33. Class c = Class.forName("com.tengj.reflect.Person");
  34. Object o = c.newInstance();
  35. Method method = c.getMethod("fun", String.class, int.class);
  36. method.invoke(o, "tengj", 10);
  37. } catch (Exception e) {
  38. e.printStackTrace();
  39. }
  40. }
  41. }

执行结果:

  1. 我叫tengj,今年10

怎样,是不是感觉很厉害,我们只要知道这个类的路径全称就能玩弄它于鼓掌之间。

有时候我们想获取类中所有成员方法的信息,要怎么办。可以通过以下几步来实现:

1.获取所有方法的数组:

  1. Class c = Class.forName("com.tengj.reflect.Person");
  2. Method[] methods = c.getDeclaredMethods(); // 得到该类所有的方法,不包括父类的
  3. 或者:
  4. Method[] methods = c.getMethods();// 得到该类所有的public方法,包括父类的

然后循环这个数组就得到每个方法了:

  1. for (Method method : methods)

完整代码如下:
person类跟上面一样,这里以及后面就不贴出来了,只贴关键代码

  1. public class ReflectDemo {
  2. public static void main(String[] args){
  3. try {
  4. Class c = Class.forName("com.tengj.reflect.Person");
  5. Method[] methods = c.getDeclaredMethods();
  6. for(Method m:methods){
  7. String methodName= m.getName();
  8. System.out.println(methodName);
  9. }
  10. } catch (Exception e) {
  11. e.printStackTrace();
  12. }
  13. }
  14. }

执行结果:

  1. getName
  2. setName
  3. setAge
  4. fun
  5. fun
  6. getAge

这里如果把c.getDeclaredMethods();改成c.getMethods();执行结果如下,多了很多方法,以为把Object里面的方法也打印出来了,因为Object是所有类的父类:

  1. getName
  2. setName
  3. getAge
  4. setAge
  5. fun
  6. fun
  7. wait
  8. wait
  9. wait
  10. equals
  11. toString
  12. hashCode
  13. getClass
  14. notify
  15. notifyAll
  16. 获取成员变量信息

想一想成员变量中都包括什么:成员变量类型+成员变量名

类的成员变量也是一个对象,它是java.lang.reflect.Field的一个对象,所以我们通过java.lang.reflect.Field里面封装的方法来获取这些信息。

单独获取某个成员变量,通过Class类的以下方法实现:

参数是成员变量的名字

  1. public Field getDeclaredField(String name) // 获得该类自身声明的所有变量,不包括其父类的变量
  2. public Field getField(String name) // 获得该类自所有的public成员变量,包括其父类变量
  3. //具体实现
  4. Field[] allFields = class1.getDeclaredFields();//获取class对象的所有属性
  5. Field[] publicFields = class1.getFields();//获取class对象的public属性
  6. Field ageField = class1.getDeclaredField("age");//获取class指定属性
  7. Field desField = class1.getField("des");//获取class指定的public属性

举个例子:

例如一个类A有如下成员变量:

private int n;

如果A有一个对象a,那么就可以这样得到其成员变量:

  1. Class c = a.getClass();
  2. Field field = c.getDeclaredField("n");

完整代码如下:

  1. public class ReflectDemo {
  2. public static void main(String[] args){
  3. try {
  4. Class c = Class.forName("com.tengj.reflect.Person");
  5. //获取成员变量
  6. Field field = c.getDeclaredField("msg"); //因为msg变量是private的,所以不能用getField方法
  7. Object o = c.newInstance();
  8. field.setAccessible(true);//设置是否允许访问,因为该变量是private的,所以要手动设置允许访问,如果msg是public的就不需要这行了。
  9. Object msg = field.get(o);
  10. System.out.println(msg);
  11. } catch (Exception e) {
  12. e.printStackTrace();
  13. }
  14. }
  15. }

执行结果:

  1. hello wrold

同样,如果想要获取所有成员变量的信息,可以通过以下几步

1.获取所有成员变量的数组:

  1. Field[] fields = c.getDeclaredFields();

2.遍历变量数组,获得某个成员变量field

  1. for (Field field : fields)

完整代码:

  1. public class ReflectDemo {
  2. public static void main(String[] args){
  3. try {
  4. Class c = Class.forName("com.tengj.reflect.Person");
  5. Field[] fields = c.getDeclaredFields();
  6. for(Field field :fields){
  7. System.out.println(field.getName());
  8. }
  9. } catch (Exception e) {
  10. e.printStackTrace();
  11. }
  12. }
  13. }

执行结果:

  1. name
  2. age
  3. msg

1.获取构造函数

最后再想一想构造函数中都包括什么:构造函数参数
同上,类的成构造函数也是一个对象,它是java.lang.reflect.Constructor的一个对象,所以我们通过java.lang.reflect.Constructor里面封装的方法来获取这些信息。

单独获取某个构造函数,通过Class类的以下方法实现:

这个参数为构造函数参数类的类类型列表

  1. public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) // 获得该类所有的构造器,不包括其父类的构造器 public Constructor<T> getConstructor(Class<?>... parameterTypes) // 获得该类所以public构造器,包括父类 //具体 Constructor<?>[] allConstructors = class1.getDeclaredConstructors();//获取class对象的所有声明构造函数
  2. Constructor<?>[] publicConstructors = class1.getConstructors();//获取class对象public构造函数
  3. Constructor<?> constructor = class1.getDeclaredConstructor(String.class);//获取指定声明构造函数
  4. Constructor publicConstructor = class1.getConstructor(String.class);//获取指定声明的public构造函数

举个例子:

例如类A有如下一个构造函数:

  1. public A(String a, int b) {
  2. // code body
  3. }

那么就可以通过:

  1. Constructor constructor = a.getDeclaredConstructor(String.class, int.class);

来获取这个构造函数。

完整代码:

  1. public class ReflectDemo {
  2. public static void main(String[] args){
  3. try {
  4. Class c = Class.forName("com.tengj.reflect.Person");
  5. //获取构造函数
  6. Constructor constructor = c.getDeclaredConstructor(String.class);
  7. constructor.setAccessible(true);//设置是否允许访问,因为该构造器是private的,所以要手动设置允许访问,如果构造器是public的就不需要这行了。
  8. constructor.newInstance("tengj");
  9. } catch (Exception e) {
  10. e.printStackTrace();
  11. }
  12. }
  13. }

执行结果:

tengj

注意:Class的newInstance方法,只能创建只包含无参数的构造函数的类,如果某类只有带参数的构造函数,那么就要使用另外一种方式:

  1. fromClass.getDeclaredConstructor(String.class).newInstance("tengj");

获取所有的构造函数,可以通过以下步骤实现:

1.获取该类的所有构造函数,放在一个数组中:

  1. Constructor[] constructors = c.getDeclaredConstructors();

2.遍历构造函数数组,获得某个构造函数constructor:

  1. for (Constructor constructor : constructors)

完整代码:

  1. public class ReflectDemo {
  2. public static void main(String[] args){
  3. Constructor[] constructors = c.getDeclaredConstructors();
  4. for(Constructor constructor:constructors){
  5. System.out.println(constructor);
  6. }
  7. } catch (Exception e) {
  8. e.printStackTrace();
  9. }
  10. }
  11. }

执行结果:

  1. public com.tengj.reflect.Person()
  2. public com.tengj.reflect.Person(java.lang.String)

其他方法
注解需要用到的

Annotation[] annotations = (Annotation[]) class1.getAnnotations();//获取class对象的所有注解

  1. Annotation annotation = (Annotation) class1.getAnnotation(Deprecated.class);//获取class对象指定注解
  2. Type genericSuperclass = class1.getGenericSuperclass();//获取class对象的直接超类的
  3. Type Type[] interfaceTypes = class1.getGenericInterfaces();//获取class对象的所有接口的type集合

获取class对象的信息

  1. boolean isPrimitive = class1.isPrimitive();//判断是否是基础类型
  2. boolean isArray = class1.isArray();//判断是否是集合类
  3. boolean isAnnotation = class1.isAnnotation();//判断是否是注解类
  4. boolean isInterface = class1.isInterface();//判断是否是接口类
  5. boolean isEnum = class1.isEnum();//判断是否是枚举类
  6. boolean isAnonymousClass = class1.isAnonymousClass();//判断是否是匿名内部类
  7. boolean isAnnotationPresent = class1.isAnnotationPresent(Deprecated.class);//判断是否被某个注解类修饰
  8. String className = class1.getName();//获取class名字 包含包名路径
  9. Package aPackage = class1.getPackage();//获取class的包信息
  10. String simpleName = class1.getSimpleName();//获取class类名
  11. int modifiers = class1.getModifiers();//获取class访问权限
  12. Class<?>[] declaredClasses = class1.getDeclaredClasses();//内部类
  13. Class<?> declaringClass = class1.getDeclaringClass();//外部类
  14. getSuperclass():获取某类的父类
  15. getInterfaces():获取某类实现的接口

通过反射了解集合泛型的本质

扩展的知识点,了解就可以了。后续会为大家写一篇关于泛型的文章。

首先下结论:

Java中集合的泛型,是防止错误输入的,只在编译阶段有效,绕过编译到了运行期就无效了。

下面通过一个实例来验证:

  1. /** * 集合泛型的本质 */
  2. public class GenericEssence {
  3. public static void main(String[] args) {
  4. List list1 = new ArrayList(); // 没有泛型
  5. List<String> list2 = new ArrayList<String>(); // 有泛型
  6. /* * 1.首先观察正常添加元素方式,在编译器检查泛型, * 这个时候如果list2添加int类型会报错 */
  7. list2.add("hello");
  8. // list2.add(20); // 报错!list2有泛型限制,只能添加String,添加int报错
  9. System.out.println("list2的长度是:" + list2.size()); // 此时list2长度为1
  10. /* * 2.然后通过反射添加元素方式,在运行期动态加载类,首先得到list1和list2 * 的类类型相同,然后再通过方法反射绕过编译器来调用add方法,看能否插入int * 型的元素 */
  11. Class c1 = list1.getClass();
  12. Class c2 = list2.getClass();
  13. System.out.println(c1 == c2); // 结果:true,说明类类型完全相同
  14. // 验证:我们可以通过方法的反射来给list2添加元素,这样可以绕过编译检查
  15. try {
  16. Method m = c2.getMethod("add", Object.class); // 通过方法反射得到add方法
  17. m.invoke(list2, 20); // 给list2添加一个int型的,上面显示在编译器是会报错的
  18. System.out.println("list2的长度是:" + list2.size()); // 结果:2,说明list2长度增加了,并没有泛型检查
  19. } catch (Exception e) {
  20. e.printStackTrace();
  21. }
  22. /* * 综上可以看出,在编译器的时候,泛型会限制集合内元素类型保持一致,但是编译器结束进入 * 运行期以后,泛型就不再起作用了,即使是不同类型的元素也可以插入集合。 */
  23. }
  24. }

执行结果:

  1. list2的长度是:1
  2. true
  3. list2的长度是:2

思维导图

有助于理解上述所讲的知识点
这里写图片描述

拓展阅读
Java反射机制深入详解 - 火星十一郎 - 博客园
Java反射入门 - Trigl的博客 - CSDN博客
Java反射机制 - ①块腹肌 - 博客园
Java 反射机制浅析 - 孤旅者 - 博客园
反射机制的理解及其用途 - 每天进步一点点! - ITeye博客
Java动态代理与反射详解 - 浩大王 - 博客园

发表评论

表情:
评论列表 (有 0 条评论,349人围观)

还没有评论,来说两句吧...

相关阅读

    相关 JAVA反射注解

    JAVA反射与注解 前言 现在在我们构建自己或公司的项目中,或多或少都会依赖几个流行比较屌的第三方库,比如:Butter Knife、Retrofit 2、Dagge