大家好,我是猿人(猿码天地创始人),今天给码农们或即将成为码农或想成为码农的朋友讲讲JAVA反射机制详解,看完还学不会你来捶我,现在是深夜23:00分,猿人最擅长熬夜,就是不怕掉头发!一切都是为了亲爱的粉丝朋友能学到知识,猿人熬夜也是值得的!
好,废话不多说,直接干,进入正题:Good Good Study,Day Day Up!
我是「猿码天地」,一个热爱技术、热爱编程的IT猿。技术是开源的,知识是共享的!
写作是对自己学习的总结和记录,如果您对 Java、分布式、微服务、中间件、Spring Boot、Spring Cloud等技术感兴趣,可以关注我的动态,我们一起学习,一起成长!
用知识改变命运,让家人过上更好的生活,互联网人一家亲!
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
在日常的第三方应用开发过程中,经常会遇到某个类的某个成员变量、方法或是属性是私有的或是只对系统应用开放,这时候就可以利用Java的反射机制通过反射来获取所需的私有成员或是方法。
一般而言,当用户使用一个类的时候,应该获取这个类,而后通过这个类实例化对象,但是使用反射则可以相反的通过对象获取类中的信息。
通俗的讲反射就是可以在程序运行的时候动态装载类,查看类的信息,生成对象,或操作生成的对象。它允许运行中的 Java 程序获取自身的信息。
类名 | 用途 |
---|---|
Class类 | 代表类的实体,在运行的Java应用程序中表示类和接口 |
Field类 | 代表类的成员变量(成员变量也称为类的属性) |
Method类 | 代表类的方法 |
Constructor类 | 代表类的构造方法 |
Class类
Class代表类的实体,在运行的Java应用程序中表示类和接口。在这个类中提供了很多有用的方法,这里对他们简单的分类介绍。
获得类相关的方法
获得类中属性相关的方法
获得类中注解相关的方法
获得类中构造器相关的方法
获得类中方法相关的方法
类中其他重要的方法
Field类
Field代表类的成员变量(成员变量也称为类的属性)
Method类
Method代表类的方法。
Constructor类
Constructor代表类的构造方法。
反射机制是程序在运行时能够获取自身的信息。在java中,只要给定类的名字,那么就可以通过反射机制来获得类的所有信息。
java反射的作用是什么?
反射机制是在运行时,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意个对象,都能够调用它的任意一个方法。在java中,只要给定类的名字,就可以通过反射机制来获得类的所有信息。这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
Java反射创建对象效率高还是通过new创建对象的效率高?
通过new创建对象的效率比较高。通过反射时,先找查找类资源,使用类加载器创建,过程比较繁琐,所以效率较低。
除了使用new创建对象之外,还可以用什么方法创建对象?
使用Java反射可以创建对象。
反射的实现方式都有什么?
获取Class对象,有4种方法:(1)Class.forName(“类的路径”);(2)类名.class;(3)对象名.getClass();(4)基本类型的包装类,可以调用包装类的Type属性来获得该包装类的Class对象。
实现java反射的类有什么?
(1)Class:表示正在运行的Java应用程序中的类和接口,注意所有获取对象的信息都需要Class类来实现;(2)Field:提供有关类和接口的属性信息,以及对它的动态访问权限;(3)Constructor:提供关于类的单个构造方法的信息以及它的访问权限;(4)Method:提供类或接口中某个方法的信息。
反射机制的优缺点
优点:(1)能够运行时动态获取类的实例,提高灵活性;(2)与动态编译结合Class.forName('com.mysql.jdbc.Driver.class');//加载MySQL的驱动类
缺点:使用反射性能较低,需要解析字节码,将内存中的对象进行解析。
其解决方案是:通过setAccessible(true)关闭JDK的安全检查来提升反射速度;多次创建一个类的实例时,有缓存会快很多;ReflflectASM工具类,通过字节码生成的方式加快反射速度。
Java反射API有几类?
反射 API 用来生成 JVM 中的类、接口或则对象的信息。
(1)Class 类:反射的核心类,可以获取类的属性,方法等信息。
(2)Field 类:Java.lang.reflec 包中的类,表示类的成员变量,可以用来获取和设置类之中的属性值。
(3)Method 类:Java.lang.reflec 包中的类,表示类的方法,它可以用来获取类中的方法信息或者执行方法。
(4)Constructor 类:Java.lang.reflec 包中的类,表示类的构造方法。
反射使用步骤(获取Class对象、调用对象方法)
(1)获取想要操作的类的Class对象,他是反射的核心,通过Class对象我们可以任意调用类的方法。
(2)调用 Class 类中的方法,既就是反射的使用阶段。
(3)使用反射 API 来操作这些信息。
Java反射机制的作用有什么?
(1)在运行时判断任意一个对象所属的类;(2)在运行时构造任意一个类的对象;(3)在运行时判断任意一个类所具有的成员变量和方法;(4)在运行时调用任意一个对象的方法。
反射中,Class.forName 和 ClassLoader 区别?
Class.forName(className)方法,其实调用的方法是 Class.forName(className,true,classloader); 第 2 个参数表示在 loadClass 后必须初始化。根据 JVM 加载类的知识,我们知道在执行过此方法后,目标对象的 static 块代码已经被执行,static 参数也已经被初始化。
而 ClassLoader.loadClass(className) 方法,实际调用的方法是 ClassLoader.loadClass(className,false); 第 2 个参数表示目标对象被装载后不进行链接,这就意味着不会去执行该类静态块中间的内容。
例如,在 JDBC 使用中,常看到这样的用法,Class.forName("com.mysql.jdbc.Driver") 如果换成了 getClass().getClassLoader().loadClass("com.mysql.jdbc.Driver"),就不行。根据 com.mysql.jdbc.Driver 的源代码,Driver 在 static 块中会注册自己到 java.sql.DriverManager。而 static 块就是在 Class 的初始化中被执行。所以这个地方就只能用 Class.forName(className)。
com.mysql.jdbc.Driver 的源代码:
// Register ourselves with the DriverManager
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
Java 类的加载过程
在 Java 中,类装载器把一个类装入 Java 虚拟机中,要经过三个步骤来完成:装载、链接和初始化,其中链接又可以分成校验、准备和解析三步:
装载:查找和导入类或接口的二进制数据;
链接:执行下面的校验、准备和解析步骤,其中解析步骤是可以选择的;
校验:检查导入类或接口的二进制数据的正确性;
准备:给类的静态变量分配并初始化存储空间;
解析:将符号引用转成直接引用;
初始化:激活类的静态变量的初始化 Java 代码和静态 Java 代码块。
描述动态代理的几种实现方式,分别说出相应的优缺点
JDK 动态代理和 CGLIB 动态代理。JDK 动态代理是由 Java 内部的反射机制来实现的,CGLIB 动态代理底层则是借助 ASM 框架来实现的,实现了无反射机制进行代理,利用空间来换取了时间,代理效率高于 JDK 。总的来说,反射机制在生成类的过程中比较高效,而 ASM 在生成类之后的相关执行过程中比较高效(可以通过将 ASM 生成的类进行缓存,这样解决 ASM 生成类过程低效问题)。还有一点必须注意:JDK 动态代理的应用前提,必须是目标类基于统一的接口。如果没有上述前提,JDK 动态代理不能应用。由此可以看出,JDK 动态代理有一定的局限性,CGLIB 这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势。
很多人认为反射在实际的 Java 开发应用中并不广泛,其实不然。我们常用的 JDBC 中有一行代码:
Class.forName('com.MySQL.jdbc.Driver.class').newInstance();
这里用到的就是 Java 反射机制,除此之外很多框架也都用到反射机制。
实际上反射机制就是非常规模式,如果说方法的调用是 Java 正确的打开方式,那反射机制就是 Java 的后门。为什么会有这个后门呢?这涉及到了静态和动态的概念:
静态编译:在编译时确定类型,绑定对象
动态编译:运行时确定类型,绑定对象
两者的区别在于,动态编译可以最大程度地支持多态,多态最大的意义在于降低类的耦合性,而 Java 语言确是静态编译(如:int a=3;要指定类型),Java 反射很大程度上弥补了这一不足:解耦以及提高代码的灵活性,所以 Java 也被称为是准动态语言。
尽管反射有诸多优点,但是反射也有缺点:
由于反射会额外消耗一定的系统资源,因此,反射操作的效率要比那些非反射操作低得多。
由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用(代码有功能上的错误)。所以能用其它方式实现的就尽量不去用反射。
package com.bowen.demo.demo005;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
/**
* java-study
* ReflectClass
* @author : zhang.bw
* @date : 2021-03-19 18:10
**/
@Slf4j
public class ReflectClass {
// 创建对象
public static void reflectNewInstance() {
try {
// 根据类名返回类的对象
Class> classBook = Class.forName("com.bowen.demo.demo005.Book");
// 创建类的实例
Object objectBook = classBook.newInstance();
Book book = (Book) objectBook;
book.setName("猿码天地");
book.setAuthor("猿人");
System.out.println(book.toString());
} catch (Exception ex) {
ex.printStackTrace();
}
}
// 反射私有构造方法
public static void reflectPrivateConstructor() {
try {
Class> classBook = Class.forName("com.bowen.demo.demo005.Book");
Constructor> declaredConstructorBook = classBook.getDeclaredConstructor(String.class,String.class);
declaredConstructorBook.setAccessible(true);
Object objectBook = declaredConstructorBook.newInstance("猿码天地","猿人");
Book book = (Book) objectBook;
System.out.println(book.toString());
} catch (Exception ex) {
ex.printStackTrace();
}
}
// 反射私有属性
public static void reflectPrivateField() {
try {
Class> classBook = Class.forName("com.bowen.demo.demo005.Book");
Object objectBook = classBook.newInstance();
Field fieldTag = classBook.getDeclaredField("TAG");
fieldTag.setAccessible(true);
String tag = (String) fieldTag.get(objectBook);
System.out.println(tag);
} catch (Exception ex) {
ex.printStackTrace();
}
}
// 反射私有方法
public static void reflectPrivateMethod() {
try {
Class> classBook = Class.forName("com.bowen.demo.demo005.Book");
Method methodBook = classBook.getDeclaredMethod("declaredMethod",int.class);
methodBook.setAccessible(true);
Object objectBook = classBook.newInstance();
String string = (String) methodBook.invoke(objectBook,0);
System.out.println(string);
} catch (Exception ex) {
ex.printStackTrace();
}
}
/**
* 将对象转换为Map键值对
* @param obj
* @return
*/
public static Map reflectKeyAndValue(Object obj) {
Map result = new HashMap();
// 得到类对象
Class cla = (Class) obj.getClass();
/* 得到类中的所有属性集合 */
Field[] fs = cla.getDeclaredFields();
for (int i = 0; i < fs.length; i++) {
Field f = fs[i];
// 设置哪些属性是可以访问的
f.setAccessible(true);
Object val = new Object();
try {
val = f.get(obj);
// 设置键值
result.put(f.getName(), val);
} catch (IllegalArgumentException e) {
log.error("非法参数异常", e);
} catch (IllegalAccessException e) {
log.error("非法访问异常", e);
}
}
log.debug("获取对象的所有键值: {}", result);
return result;
}
/**
* 使用 Java 反射获取 Class 对象(三种方式)
*
* 方式1:通过 Class 类的静态方法获取 Class 类对象 (推荐)
* 方式2:因为所有类都继承 Object 类。因而可通过调用 Object 类中的 getClass 方法来获取
* 方式3:任何数据类型(包括基本数据类型)都有一个“静态”的 class 属性
* @throws Exception
*/
public static void test1() throws Exception {
Class> class1 = null;
Class> class2 = null;
Class> class3 = null;
class1 = Class.forName("com.bowen.demo.demo005.Book");
class2 = new Book().getClass();
class3 = Book.class;
System.out.println("类名称 1: " + class1.getName());
System.out.println("类名称 2: " + class2.getName());
System.out.println("类名称 3: " + class3.getName());
}
/**
* 使用 Java 反射获取一个对象的父类和实现的接口
* @throws Exception
*/
public static void test2() throws Exception{
Class> clazz = Class.forName("com.bowen.demo.demo005.Book");
// 取得父类
Class> parentClass = clazz.getSuperclass();
System.out.println("父类为:" + parentClass.getName());
// 获取所有的接口
Class> intes[] = clazz.getInterfaces();
System.out.println("实现的接口有:");
for (int i = 0; i < intes.length; i++) {
System.out.println((i + 1) + ":" + intes[i].getName());
}
}
/**
* 使用 Java 反射获取某个类的全部构造函数并调用私有构造方法
*/
public static void test3() throws Exception{
//1.加载Class对象
Class clazz = Class.forName("com.bowen.demo.demo005.Student");
//2.获取所有公有构造方法
System.out.println("所有公有构造方法");
Constructor[] conArray = clazz.getConstructors();
for(Constructor c : conArray){
System.out.println(c);
}
System.out.println("所有的构造方法(包括:私有、受保护、默认、公有)");
conArray = clazz.getDeclaredConstructors();
for(Constructor c : conArray){
System.out.println(c);
}
System.out.println("获取公有、无参的构造方法");
//1、因为是无参的构造方法所以类型是一个null,不写也可以:这里需要的是一个参数的***类型***,
//2、返回的是描述这个无参构造函数的类对象。
Constructor con = clazz.getConstructor(null);
System.out.println("con = " + con);
//调用构造方法
Object obj = con.newInstance();
System.out.println("obj = " + obj);
System.out.println("获取私有构造方法,并调用");
con = clazz.getDeclaredConstructor(int.class);
System.out.println(con);
//调用构造方法
con.setAccessible(true);//暴力访问(忽略掉访问修饰符)
obj = con.newInstance(26);
}
/**
* 使用 Java 反射获取某个类的全部属性
*/
public static void test4() throws Exception{
//1.获取Class对象
Class stuClass = Class.forName("com.bowen.demo.demo005.Student");
//2.获取字段
System.out.println("获取所有公有的字段");
Field[] fieldArray = stuClass.getFields();
for(Field f : fieldArray){
System.out.println(f);
}
System.out.println();
System.out.println("获取所有的字段(包括私有、受保护、默认的)");
fieldArray = stuClass.getDeclaredFields();
for(Field f : fieldArray){
System.out.println(f);
}
System.out.println();
System.out.println("获取公有字段并调用");
Field f = stuClass.getField("name");
System.out.println(f);
//获取一个对象
Object obj = stuClass.getConstructor().newInstance();
f.set(obj, "laughitover");
//验证
Student stu = (Student)obj;
System.out.println("验证姓名:" + stu.name);
System.out.println();
System.out.println("获取私有字段并调用");
f = stuClass.getDeclaredField("phoneNum");
System.out.println(f);
f.setAccessible(true);//暴力反射,解除私有限定
f.set(obj, "13812345678");
System.out.println("验证电话:" + stu);
}
/**
* 使用 Java 反射获取某个类的全部方法
*/
public static void test5() throws Exception{
//1.获取Class对象
Class stuClass = Class.forName("com.bowen.demo.demo005.Student");
//2.获取所有公有方法
System.out.println("获取所有的”公有“方法");
stuClass.getMethods();
Method[] methodArray = stuClass.getMethods();
for(Method m : methodArray){
System.out.println(m);
}
System.out.println("获取所有的方法,包括私有的");
methodArray = stuClass.getDeclaredMethods();
for(Method m : methodArray){
System.out.println(m);
}
System.out.println("获取公有的show1()方法");
Method m = stuClass.getMethod("show1", String.class);
System.out.println(m);
//实例化一个Student对象
Object obj = stuClass.getConstructor().newInstance();
m.invoke(obj, "laughitover");
System.out.println("获取私有的show4()方法");
m = stuClass.getDeclaredMethod("show4", int.class);
System.out.println(m);
m.setAccessible(true);//解除私有限定
Object result = m.invoke(obj, 20);//需要两个参数,一个是要调用的对象(获取有反射),一个是实参
System.out.println("返回值:" + result);
}
/**
* 使用 Java 反射调用某个类的方法
* Java 反射获取 Class 对象并使用invoke()方法调用方法 reflect1() 和 reflect2()
*/
public static void test6() throws Exception{
Class> clazz = Class.forName("com.bowen.demo.demo005.ReflectClass");
Method method = clazz.getMethod("reflect1");
method.invoke(clazz.newInstance());
method = clazz.getMethod("reflect2", int.class, String.class);
method.invoke(clazz.newInstance(), 20, "张三");
}
public void reflect1() {
System.out.println("Java 反射机制 - 调用某个类的方法1.");
}
public void reflect2(int age, String name) {
System.out.println("Java 反射机制 - 调用某个类的方法2.");
System.out.println("age -> " + age + ". name -> " + name);
}
/**
* 通过反射越过泛型检查
* 泛型作用在编译期,编译过后泛型擦除(消失掉)。所以是可以通过反射越过泛型检查的
*/
public static void test7() throws Exception{
ArrayList list = new ArrayList();
list.add(100);
list.add(200);
Method method = list.getClass().getMethod("add", Object.class);
method.invoke(list, "Java反射机制实例。");
//遍历集合
for(Object obj : list){
System.out.println(obj);
}
}
/**
* 通过反射机制获得数组信息并修改数组的大小和值
* 通过反射机制分别修改int和String类型的数组的大小并修改int数组的第一个值
*/
public static void test8() throws Exception{
int[] temp = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int[] newTemp = (int[]) arrayInc(temp, 15);
print(newTemp);
Array.set(newTemp, 0, 100);
System.out.println("修改之后数组第一个元素为: " + Array.get(newTemp, 0));
print(newTemp);
String[] atr = { "a", "b", "c" };
String[] str1 = (String[]) arrayInc(atr, 8);
print(str1);
}
// 修改数组大小
public static Object arrayInc(Object obj, int len) {
Class> arr = obj.getClass().getComponentType();
Object newArr = Array.newInstance(arr, len);
int co = Array.getLength(obj);
System.arraycopy(obj, 0, newArr, 0, co);
return newArr;
}
// 打印
public static void print(Object obj) {
Class> c = obj.getClass();
if (!c.isArray()) {
return;
}
Class> arr = obj.getClass().getComponentType();
System.out.println("数组类型: " + arr.getName());
System.out.println("数组长度为: " + Array.getLength(obj));
for (int i = 0; i < Array.getLength(obj); i++) {
System.out.print(Array.get(obj, i) + " ");
}
System.out.println();
}
/**
* 通过 Java 反射机制获取 ClassLoader 类加载器
*/
public static void test9() throws Exception{
//1、获取一个系统的类加载器
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
System.out.println("系统的类加载器-->" + classLoader);
//2、获取系统类加载器的父类加载器(扩展类加载器(extensions classLoader))
classLoader = classLoader.getParent();
System.out.println("扩展类加载器-->" + classLoader);
//3、获取扩展类加载器的父类加载器
//输出为Null,引导类加载器无法被Java程序直接引用
classLoader = classLoader.getParent();
System.out.println("启动类加载器-->" + classLoader);
//4、测试当前类由哪个类加载器进行加载 ,结果就是系统的类加载器
classLoader = Class.forName("com.channelsoft.ssm.controller.ReflectTest").getClassLoader();
System.out.println("当前类由哪个类加载器进行加载-->"+classLoader);
//5、测试JDK提供的Object类由哪个类加载器负责加载的
classLoader = Class.forName("java.lang.Object").getClassLoader();
System.out.println("JDK提供的Object类由哪个类加载器加载-->" + classLoader);
}
public static void main(String[] args) {
try {
// 创建对象
ReflectClass.reflectNewInstance();
// 反射私有构造方法
ReflectClass.reflectPrivateConstructor();
// 反射私有属性
ReflectClass.reflectPrivateField();
// 反射私有方法
ReflectClass.reflectPrivateMethod();
// 将对象转换为Map键值对
Book book = new Book();
book.setName("猿码天地");
book.setAuthor("猿人");
Map map = ReflectClass.reflectKeyAndValue(book);
System.out.println(map);
// 使用 Java 反射获取 Class 对象(三种方式)
ReflectClass.test1();
// 使用 Java 反射获取一个对象的父类和实现的接口
ReflectClass.test2();
// 使用 Java 反射获取某个类的全部构造函数并调用私有构造方法
ReflectClass.test3();
// 使用 Java 反射获取某个类的全部属性
ReflectClass.test4();
// 使用 Java 反射获取某个类的全部方法
ReflectClass.test5();
// 使用 Java 反射调用某个类的方法
ReflectClass.test6();
// 通过反射越过泛型检查
ReflectClass.test7();
// 通过反射机制获得数组信息并修改数组的大小和值
ReflectClass.test8();
// 通过 Java 反射机制获取 ClassLoader 类加载器
ReflectClass.test9();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
package com.bowen.demo.demo005;
/**
* java-study
* Book
* @author : zhang.bw
* @date : 2021-03-19 18:08
**/
public class Book{
private final static String TAG = "BookTag";
private String name;
private String author;
@Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
", author='" + author + '\'' +
'}';
}
public Book() {
}
private Book(String name, String author) {
this.name = name;
this.author = author;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
private String declaredMethod(int index) {
String string = null;
switch (index) {
case 0:
string = "I am declaredMethod 1 !";
break;
case 1:
string = "I am declaredMethod 2 !";
break;
default:
string = "I am declaredMethod 3 1 !";
}
return string;
}
}
package com.bowen.demo.demo005;
/**
* java-study
* Student
* @author : zhang.bw
* @date : 2021-03-21 00:24
**/
public class Student {
public String name;
protected int age;
char sex;
private String phoneNum;
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + ", sex=" + sex
+ ", phoneNum=" + phoneNum + "]";
}
//--------------------------------------------
public Student(){
System.out.println("调用了公有、无参构造方法。。。");
}
public Student(char name){
System.out.println("姓名:" + name);
}
public Student(String name ,int age){
System.out.println("姓名:"+name+"年龄:"+ age);
}
//受保护的构造方法
protected Student(boolean b){
System.out.println("受保护的构造方法 b = " + b);
}
//私有构造方法
private Student(int age){
System.out.println("私有的构造方法,年龄:"+ age);
}
//--------------------------------------------
public void show1(String s){
System.out.println("调用了:公有的,String参数的show1(): s = " + s);
}
protected void show2(){
System.out.println("调用了:受保护的,无参的show2()");
}
void show3(){
System.out.println("调用了:默认的,无参的show3()");
}
private String show4(int age){
System.out.println("调用了,私有的,并且有返回值的,int参数的show4(): age = " + age);
return "abcd";
}
}
猿人于2021年3月20日 0点30分 于深圳整理,整理完这篇文章头发还有10万零547根,今天掉了1根头发,持续记录头发根数,加油!
你多学一样本事,就少说一句求人的话,现在的努力,是为了以后的不求别人,实力是最强的底气。记住,活着不是靠泪水博得同情,而是靠汗水赢得掌声。——《写给程序员朋友》
点赞&在看是最大的支持