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

文章目录

  • 动态语言
  • 反射的应用场合
    • 编译时类型和运行时类型
    • 编译时类型无法获取具体方法
  • 为什么需要反射
  • 反射的基本使用
    • 获取类的 Class 对象
    • 构造类的实例化对象
    • 获取一个类的所有信息
      • 获取类中的变量(Field)
      • 获取类中的方法(Method)
      • 获取类的构造器(Constructor)
    • 通过反射调用方法
    • 获取注解
  • 反射的优势及缺陷
  • 反射小结

动态语言

动态语言,是指程序在运行时可以改变其结构:新的函数可以引进,已有的函数可以被删除等结构上的变化。比如常见的 JavaScript 就是动态语言,除此之外 Ruby,Python 等也属于动态语言,而 C、C++则不属于动态语言。从反射角度说 JAVA 属于半动态语言。


反射的应用场合

编译时类型和运行时类型

在Java程序中许多对象在运行是都会出现两种类型:编译时类型和运行时类型。 编译时的类型由声明对象时使用的类型来决定,运行时的类型由实际赋值给对象的类型决定 。如:

Person p = new Student();

其中编译时类型为 Person,运行时类型为 Student


编译时类型无法获取具体方法

程序在运行时还可能接收到外部传入的对象,该对象的编译时类型为 Object,但是程序有需要调用该对象的运行时类型的方法。为了解决这些问题,程序需要在运行时发现对象和类的真实信息。然而,如果编译时根本无法预知该对象和类属于哪些类,程序只能依靠运行时信息来发现该对象和类的真实信息,此时就必须使用到反射了。


为什么需要反射

在学习Java基础的时候,一般都会学过反射。我在初学反射的时候,并不能理解反射是用来干嘛的。学了一些API发现:“明明我自己能直接new一个对象,为什么它要绕一个圈子,先拿到Class对象,再调用Class对象的方法来创建对象呢,这不是多余吗?”

我现在认为用反射主要有两个原因

  1. 提高程序的灵活性
  2. 屏蔽掉实现的细节,让使用者更加方便好用

我一直在文章中都在强调,学某一项技术之前,一定要理解为什么要学这项技术,所以我的文章一般会花比较长的幅度上讲为什么。

下面我来举几个例子来帮助大家理解

案例一(JDBC)

相信大家都写过jdbc的代码,我贴一小段,大家回顾一下:

Class.forName("com.mysql.jdbc.Driver");//获取与数据库连接的对象-Connetcion
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/java3y", "root", "root");//获取执行sql语句的statement对象
statement = connection.createStatement();//执行sql语句,拿到结果集
resultSet = statement.executeQuery("SELECT * FROM users");

后来为什么要变成下面这种形式呢?

//获取配置文件的读入流
InputStream inputStream = UtilsDemo.class.getClassLoader().getResourceAsStream("db.properties");Properties properties = new Properties();
properties.load(inputStream);//获取配置文件的信息
driver = properties.getProperty("driver");
url = properties.getProperty("url");
username = properties.getProperty("username");
password = properties.getProperty("password");//加载驱动类
Class.forName(driver);

理由很简单,人们不想修改代码。只要存在有变动的地方,我写在配置里边,不香吗?但凡有一天,我的username,password,url甚至是数据库都改了,我都能够通过修改配置的方式去实现。

不需要动我丝毫的代码,改下配置就完事了,这就能提供程序的灵活性。

有人可能会问:“那还是要改啊,我改代码也很快啊,你改配置不也是要改吗”。

改代码的风险要比改配置大,而且代码逻辑可能比较复杂,而修改配置相对比较简单,容易操作还不容易出错,即便不知道代码的实现都能通过改配置来完成要做的事

案例二(SpringMVC)

相信大家学SpringMVC之前都学过Servlet的吧,如果没学过,建议看我的文章再复习复习。

我当时学MVC框架的时候给我带来印象最深的是什么,本来需要各种getParameter(),现在只要通过约定好JavaBean的字段名,就能把值填充进去了。

还是上代码吧,这是我们当时学Servlet的现状:

//通过html的name属性,获取到值
String username = request.getParameter("username");
String password = request.getParameter("password");
String gender = request.getParameter("gender");//复选框和下拉框有多个值,获取到多个值
String[] hobbies = request.getParameterValues("hobbies");
String[] address = request.getParameterValues("address");//获取到文本域的值
String description = request.getParameter("textarea");//得到隐藏域的值
String hiddenValue = request.getParameter("aaa");

我们学到SpringMVC的时候是怎么样的:

@RequestMapping(value = "/save")
@ResponseBody
public String taskSave(PushConfig pushConfig) {// 直接使用  String name= pushConfig.getName();
}

为什么SpringMVC能做到?其实就是通过反射来做的。

相信你也有过的经历

如果你的JavaBean的属性名跟传递过来的参数名不一致,那就“自动组装”失败了。因为反射只能根据参数名去找字段名,如果不一致,那肯定set不进去了。所以就组装失败了呀。如果在使用框架的时候,为什么我们往往写上JavaBean,保持字段名与参数名相同,就能“自动”得到对应的值呢。这就是反射的好处

屏蔽掉实现的细节,让使用者更加方便好用


反射的基本使用

Java 反射的主要组成部分有4个:

  • Class:任何运行在内存中的所有类都是该 Class 类的实例对象,每个 Class 类对象内部都包含了本来的所有信息。记着一句话,通过反射干任何事,先找 Class 准没错!
  • Field:描述一个类的属性,内部包含了该属性的所有信息,例如数据类型,属性名,访问修饰符······
  • Constructor:描述一个类的构造方法,内部包含了构造方法的所有信息,例如参数类型,参数名字,访问修饰符······
  • Method:描述一个类的所有方法(包括抽象方法),内部包含了该方法的所有信息,与Constructor类似,不同之处是 Method 拥有返回值类型信息,因为构造方法是没有返回值的。

我总结了一张脑图,放在了下面,如果用到了反射,离不开这核心的4个类,只有去了解它们内部提供了哪些信息,有什么作用,运用它们的时候才能易如反掌。


我们在学习反射的基本使用时,我会用一个SmallPineapple类作为模板进行说明,首先我们先来熟悉这个类的基本组成:属性,构造函数和方法

public class SmallPineapple {public String name;public int age;private double weight; // 体重只有自己知道public SmallPineapple() {}public SmallPineapple(String name, int age) {this.name = name;this.age = age;}public void getInfo() {System.out.print("["+ name + " 的年龄是:" + age + "]");}
}

反射中的用法有非常非常多,常见的功能有以下这几个

  • 在运行时获取一个类的 Class 对象
  • 在运行时构造一个类的实例化对象
  • 在运行时获取一个类的所有信息:变量、方法、构造器、注解

获取类的 Class 对象

在 Java 中,每一个类都会有专属于自己的 Class 对象,当我们编写完.java文件后,使用javac编译后,就会产生一个字节码文件.class,在字节码文件中包含类的所有信息,如属性,构造方法,方法······当字节码文件被装载进虚拟机执行时,会在内存中生成 Class 对象,它包含了该类内部的所有信息,在程序运行时可以获取这些信息。

获取 Class 对象的方法有3种:

类名.class:这种获取方式只有在编译前已经声明了该类的类型才能获取到 Class 对象

Class clazz = SmallPineapple.class;

实例.getClass():通过实例化对象获取该实例的 Class 对象

SmallPineapple sp = new SmallPineapple();
Class clazz = sp.getClass();

Class.forName(className):通过类的全限定名获取该类的 Class 对象

Class clazz = Class.forName("cn.wideth.entity.SmallPineapple");

拿到 Class对象就可以对它为所欲为了:剥开它的皮(获取类信息)、指挥它做事(调用它的方法),看透它的一切(获取属性),总之它就没有隐私了。

不过在程序中,每个类的 Class 对象只有一个,也就是说你只有这一个奴隶。我们用上面三种方式测试,通过三种方式打印各个 Class 对象都是相同的。

package cn.wideth.util;import cn.wideth.entity.SmallPineapple;public class MyClass {public static void main(String[] args) throws ClassNotFoundException {Class clazz1 = Class.forName("cn.wideth.entity.SmallPineapple");Class clazz2 = SmallPineapple.class;SmallPineapple instance = new SmallPineapple();Class clazz3 = instance.getClass();System.out.println("Class.forName() == SmallPineapple.class:" + (clazz1 == clazz2));System.out.println("Class.forName() == instance.getClass():" + (clazz1 == clazz3));System.out.println("instance.getClass() == SmallPineapple.class:" + (clazz2 == clazz3));}
}

程序结果

内存中只有一个 Class 对象的原因要牵扯到 JVM 类加载机制的双亲委派模型,它保证了程序运行时,加载类时每个类在内存中仅会产生一个Class对象。在这里我不打算详细展开说明,可以简单地理解为 JVM 帮我们保证了一个类在内存中至多存在一个 Class 对象。


构造类的实例化对象

通过反射构造一个类的实例方式有2种:

Class 对象调用newInstance()方法


package cn.wideth.util;import cn.wideth.entity.SmallPineapple;public class MyClass {public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {Class clazz = Class.forName("cn.wideth.entity.SmallPineapple");SmallPineapple smallPineapple = (SmallPineapple) clazz.newInstance();smallPineapple.getInfo();}
}

程序结果

即使 SmallPineapple 已经显式定义了构造方法,通过 newInstance() 创建的实例中,所有属性值都是对应类型的初始值,因为 newInstance() 构造实例会调用默认无参构造器

Constructor 构造器调用newInstance()方法

package cn.wideth.util;import cn.wideth.entity.SmallPineapple;
import java.lang.reflect.Constructor;public class MyClass {public static void main(String[] args) throws Exception {Class clazz = Class.forName("cn.wideth.entity.SmallPineapple");Constructor constructor = clazz.getConstructor(String.class, int.class);constructor.setAccessible(true);SmallPineapple smallPineapple2 = (SmallPineapple) constructor.newInstance("小菠萝", 21);smallPineapple2.getInfo();}
}

程序结果


通过 getConstructor(Object… paramTypes) 方法指定获取指定参数类型的 Constructor, Constructor 调用 newInstance(Object… paramValues) 时传入构造方法参数的值,同样可以构造一个实例,且内部属性已经被赋值。

通过Class对象调用 newInstance() 会走默认无参构造方法,如果想通过显式构造方法构造实例,需要提前从Class中调用getConstructor()方法获取对应的构造器,通过构造器去实例化对象。

这些 API 是在开发当中最常遇到的,当然还有非常多重载的方法,本文由于篇幅原因,且如果每个方法都一一讲解,我们也记不住,所以用到的时候去类里面查找就已经足够了。


获取一个类的所有信息

Class 对象中包含了该类的所有信息,在编译期我们能看到的信息就是该类的变量、方法、构造器,在运行时最常被获取的也是这些信息。


获取类中的变量(Field)

  • Field[] getFields():获取类中所有被public修饰的所有变量
  • Field getField(String name):根据变量名获取类中的一个变量,该变量必须被public修饰
  • Field[] getDeclaredFields():获取类中所有的变量,但无法获取继承下来的变量
  • Field getDeclaredField(String name):根据姓名获取类中的某个变量,无法获取继承下来的变量

获取类中的方法(Method)

  • Method[] getMethods():获取类中被public修饰的所有方法
  • Method getMethod(String name, Class…<?> paramTypes):根据名字和参数类型获取对应方法,该方法必须被public修饰
  • Method[] getDeclaredMethods():获取所有方法,但无法获取继承下来的方法
  • Method getDeclaredMethod(String name, Class…<?> paramTypes):根据名字和参数类型获取对应方法,无法获取继承下来的方法

获取类的构造器(Constructor)

  • Constuctor[] getConstructors():获取类中所有被public修饰的构造器
  • Constructor getConstructor(Class…<?> paramTypes):根据参数类型获取类中某个构造器,该构造器必须被public修饰
  • Constructor[] getDeclaredConstructors():获取类中所有构造器
  • Constructor getDeclaredConstructor(class…<?> paramTypes):根据参数类型获取对应的构造器

每种功能内部以 Declared 细分为2类

有Declared修饰的方法:可以获取该类内部包含的所有变量、方法和构造器,但是无法获取继承下来的信息
 
无Declared修饰的方法:可以获取该类中public修饰的变量、方法和构造器,可获取继承下来的信息

如果想获取类中所有的(包括继承)变量、方法和构造器,则需要同时调用getXXXs()和getDeclaredXXXs()两个方法,用Set集合存储它们获得的变量、构造器和方法,以防两个方法获取到相同的东西。

例如:要获取SmallPineapple获取类中所有的变量,代码应该是下面这样写

package cn.wideth.util;import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;public class MyClass {public static void main(String[] args) throws Exception {Class clazz = Class.forName("cn.wideth.entity.SmallPineapple");// 获取 public 属性,包括继承Field[] fields1 = clazz.getFields();// 获取所有属性,不包括继承Field[] fields2 = clazz.getDeclaredFields();// 将所有属性汇总到 setSet<Field> allFields = new HashSet<>();allFields.addAll(Arrays.asList(fields1));System.out.println(allFields);allFields.addAll(Arrays.asList(fields2));System.out.println(allFields);}
}

程序结果

不知道你有没有发现一件有趣的事情,如果父类的属性用protected修饰,利用反射是无法获取到的。
 
protected 修饰符的作用范围:只允许同一个包下或者子类访问,可以继承到子类。
 
getFields() 只能获取到本类的public属性的变量值;
 
getDeclaredFields() 只能获取到本类的所有属性,不包括继承的;无论如何都获取不到父类的 protected 属性修饰的变量,但是它的的确确存在于子类中。


通过反射调用方法

通过反射获取到某个 Method 类对象后,可以通过调用invoke方法执行。

  • invoke(Oject obj, Object… args):参数``1指定调用该方法的对象,参数2`是方法的参数列表值。

如果调用的方法是静态方法,参数1只需要传入null,因为静态方法不与某个对象有关,只与某个类有关。

可以像下面这种做法,通过反射实例化一个对象,然后获取Method方法对象,调用invoke()指定SmallPineapple的getInfo()方法。

package cn.wideth.util;import cn.wideth.entity.SmallPineapple;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;public class MyClass {public static void main(String[] args) throws Exception {Class clazz = Class.forName("cn.wideth.entity.SmallPineapple");Constructor constructor = clazz.getConstructor(String.class, int.class);constructor.setAccessible(true);SmallPineapple sp = (SmallPineapple) constructor.newInstance("小菠萝", 21);Method method = clazz.getMethod("getInfo");if (method != null) {method.invoke(sp, null);}}
}

程序结果


获取注解

获取注解单独拧了出来,因为它并不是专属于 Class 对象的一种信息,每个变量,方法和构造器都可以被注解修饰,所以在反射中,Field,Constructor 和 Method 类对象都可以调用下面这些方法获取标注在它们之上的注解。

  • Annotation[] getAnnotations():获取该对象上的所有注解
  • Annotation getAnnotation(Class annotaionClass):传入注解类型,获取该对象上的特定一个注解
  • Annotation[] getDeclaredAnnotations():获取该对象上的显式标注的所有注解,无法获取继承下来的注解
  • Annotation getDeclaredAnnotation(Class annotationClass):根据注解类型,获取该对象上的特定一个注解,无法获取继承下来的注解

只有注解的@Retension标注为RUNTIME时,才能够通过反射获取到该注解,@Retension 有3种保存策略:

  • SOURCE:只在**源文件(.java)**中保存,即该注解只会保留在源文件中,编译时编译器会忽略该注解,例如 @Override 注解
  • CLASS:保存在字节码文件(.class)中,注解会随着编译跟随字节码文件中,但是运行时不会对该注解进行解析
  • RUNTIME:一直保存到运行时,用得最多的一种保存策略,在运行时可以获取到该注解的所有信息

像下面这个例子,SmallPineapple 类继承了抽象类Pineapple,getInfo()方法上标识有 @Override 注解,且在子类中标注了@Transient注解,在运行时获取子类重写方法上的所有注解,只能获取到@Transient的信息。

public abstract class Pineapple {public abstract void getInfo();
}
public class SmallPineapple extends Pineapple {@Transient@Overridepublic void getInfo() {System.out.print("小菠萝的身高和年龄是:" + height + "cm ; " + age + "岁");}
}

启动类Bootstrap获取 SmallPineapple 类中的 getInfo() 方法上的注解信息:

public class Bootstrap {/*** 根据运行时传入的全类名路径判断具体的类对象* @param path 类的全类名路径*/public static void execute(String path) throws Exception {Class obj = Class.forName(path);Method method = obj.getMethod("getInfo");Annotation[] annotations = method.getAnnotations();for (Annotation annotation : annotations) {System.out.println(annotation.toString());}}public static void main(String[] args) throws Exception {execute("com.pineapple.SmallPineapple");}
}
// @java.beans.Transient(value=true)

反射的优势及缺陷

反射的优点

增加程序的灵活性:面对需求变更时,可以灵活地实例化不同对象

但是,有得必有失,一项技术不可能只有优点没有缺点,反射也有两个比较隐晦的缺点:

破坏类的封装性:可以强制访问 private 修饰的信息
性能损耗:反射相比直接实例化对象、调用方法、访问变量,中间需要非常多的检查步骤和解析步骤,JVM无法对它们优化。


反射小结

反射的思想:反射就像是一面镜子一样,在运行时才看到自己是谁,可获取到自己的信息,甚至实例化对象。
反射的作用:在运行时才确定实例化对象,使程序更加健壮,面对需求变更时,可以最大程度地做到不修改程序源码应对不同的场景,实例化不同类型的对象。
反射的3个特点:增加程序的灵活性、破坏类的封装性以及性能损耗

深入理解Java中的反射技术相关推荐

  1. 深入理解Java中的反射机制和使用原理!详细解析invoke方法的执行和使用

    反射的概念 反射:Refelection,反射是Java的特征之一,允许运行中的Java程序获取自身信息,并可以操作类或者对象的内部属性 通过反射,可以在运行时获得程序或者程序中的每一个类型的成员活成 ...

  2. 理解java中的反射

    就是通过对象来得到类的类类型(class type)以及它所声明的方法啊参数之类的东西.举个例子,通过反射,你可以得到类所声明的方法,可以不用通过这个类的实例对象来执行方法.与反射密切相关的是动态加载 ...

  3. Java中的反射如何理解——精简

    目录 引言 反射概念 反射获取类对象 反射获取构造器对象 获取构造器对象并使用 反射获取成员变量对象 反射获取方法对象 反射获取成员方法并使用 引言 经过前面的学习,相信大家已经能够对网络编程有了一定 ...

  4. 浅说Java中的反射机制(一)

    在学习传智播客李勇老师的JDBC系列时,会出现反射的概念,由于又是第一次见,不免感到陌生.所以再次在博客园找到一篇文章,先记录如下: 引用自java中的反射机制,作者bingoideas.(()为我手 ...

  5. java中demo接人_return的用法_如何理解java中return的用法?

    C语言中return用法?(请熟练者进) return是返回值,这个返回值是和函数的类型有关的,函数的类型是什么,他的返回值就是什么 比方主函数intmain() {}这里就必须有一个return,只 ...

  6. 深入理解Java中的逃逸分析

    转载自  深入理解Java中的逃逸分析 在Java的编译体系中,一个Java的源代码文件变成计算机可执行的机器指令的过程中,需要经过两段编译,第一段是把.java文件转换成.class文件.第二段编译 ...

  7. [转载] Java内存管理-你真的理解Java中的数据类型吗(十)

    参考链接: Java中的字符串类String 1 做一个积极的人 编码.改bug.提升自己 我有一个乐园,面向编程,春暖花开! 推荐阅读 第一季 0.Java的线程安全.单例模式.JVM内存结构等知识 ...

  8. formdata 接受参数中带有class 对象_浅析JAVA中的反射机制及对Servlet的优化

    今天来聊聊java中的反射机制,工作以后发现很多东西动不动就要使用反射或者动态代理,如果不能很好的理解反射,那么对于动态代理等一些重要的设计模式就会有种不够通透的感觉. 所谓的反射,就是在运行状态中, ...

  9. java中的反射机制是什么

    给大家介绍一下java中的反射机制,java中反射机制更体现出了java的灵活性.多态.和类之间的耦合性. 1:反射是一种间接操作目标对象的机制,只要给定类的名字,就可以通过反设机制获取所有的类信息. ...

最新文章

  1. 了解大脑的“小情绪”,轻松成为“效率达人”
  2. Unity3D各平台路径(包括手机内置存储路径、SD卡等等)
  3. 背包问题九讲 v1.0
  4. Javascript继承机制的设计思想
  5. 【机器学习基础】时间序列基本概念
  6. eclipse启动失败:An internal error occurred during: reload maven project
  7. python中不需要函数重载的原因
  8. 使用c#对xml文件进行解析 功能演示 161483724
  9. android 拍照 图片剪切
  10. C++ 数组与指针详解之终极无惑
  11. 修改mysql 表的字符编码
  12. windows内核提权漏洞发现与利用
  13. 《SOA中国路线图》可圈可点之处
  14. COUNTIFS函数
  15. MATLAB实现语音信号的读取
  16. CSDN20181211博客黑板报
  17. python计算十年平均录取率_如何在Python中使用Pandas计算多年平均值
  18. 领芯微基于LCM32F037吹风筒方案
  19. 《圣斗士》黄金圣斗士美图赏
  20. Python今日编程——判断水仙花数然后求水仙花数

热门文章

  1. 源码安装 MariaDB
  2. EIGRP单边邻居——认证
  3. 创建一个简单的ArcGIS Server ASP.NET网页
  4. 人力资源社会保障部关于公布国家职业资格目录的通知
  5. 淘汰Hyper-V replication 拥抱Storage Replica
  6. BZOJ1841 : 蚂蚁搬家
  7. [再寄小读者之数学篇](2014-05-23 递增函数的右极限)
  8. 360互联网训练营第十四期——大数据技术开放日
  9. 一个简单的Kafka Flink Rabbitmq Demo
  10. td 超出宽度隐藏_table中td文字超出长度用省略号隐藏超出内容,鼠标点击内容全部显示...