Java从入门到精通十八(反射)

简介: 反射的概念以及机制反射机制是什么?是通过字节码文件找到其中的一个类,然后也可以找到类中的相关属性等。我们正常一般的思维是编写java代码,然后代码会被编译为字节码文件。而我们的方法都是自己在代码中写的,现在这个机制可以通过字节码找到代码中的属性。这就是反射机制的特点。一: 获取字节码文件对象的三种方式1:通过getClass() 方法获取到Class对象。这个方法是在Object类中的一个方法,去api查找详细的说明。

反射的概念以及机制


反射机制是什么?是通过字节码文件找到其中的一个类,然后也可以找到类中的相关属性等。


我们正常一般的思维是编写java代码,然后代码会被编译为字节码文件。而我们的方法都是自己在代码中写的,现在这个机制可以通过字节码找到代码中的属性。这就是反射机制的特点。


一: 获取字节码文件对象的三种方式

1:通过getClass() 方法获取到Class对象。

这个方法是在Object类中的一个方法,去api查找详细的说明。



这个方法的返回类型是Object的运行时类,Object是所有类的父类。所以我们可以认为是返回一个类的对象。这个方法需要我们用实例化的对象去调用。这是基本的常识,所以我们任何情况下,想要用这个方法获取到字节码文件对象,就需要Object类的实例化对象或者继承自Object类的实例化对象。


2:通过类名.class获取到Class对象

似乎这种是一种更加简单的方式。


Class<Refelct_demo01> re_1 = Refelct_demo01.class;


3:通过Class.forName()获取到Class对象

根据api的说明举例就很容易明白了。



Class 提供了两个这样的重载方法。



注意返回是一个static修饰的类对象。

简单举例一下使用


Class<?> aClass = Class.forName("java_practice.Student");


三种获取Class 对象的区别(该段在语雀的公开文档引用)


getClass() 接在对象的后面。getClass() 是 Object 类中的方法,而 Object 类是所有 Java 类的父类。

.class 接在任意一个 Class 类的后面,在编译期加载(静态加载)。

Class.forName 是 Class 类中的一个静态方法,从指定的 classloader 中装载类,返回与给定字符串对应类或接口的 Class 对象,在运行期加载(动态加载)。

关于静态加载和动态加载:


静态加载的类的源程序在编译时期加载(必须存在),而动态加载的类在编译时期可以缺席(源程序不存在时编译器也不会报错)。

当类找不到的时候,静态加载方式会抛出异常"NoClassDefFoundError",而动态加载方式则抛出"ClassNotFoundException"异常。

Class.forName() 和 ClassLoader 的 loadClass() 之间的区别:


Class.forName() 加载类是将类进了初始化,而 ClassLoader 的 loadClass() 方法并没有对类进行初始化,只是把类加载到了虚拟机中。

Spring 框架中 IOC 容器的实现就是使用的 ClassLoader 的 loadClass() 方法。

而在我们使用 JDBC 时通常是使用 Class.forName() 方法来加载数据库连接驱动。这是因为在 JDBC 规范中明确要求 Driver (数据库驱动)类必须向 DriverManager 注册自己。


二 : 实例化


我们要通过反射的方式进行实例化


现在想想,如果不用反射的话,我们怎么样实例化对象?通过new进行实例化。


Refelct_demo01 re = new Refelct_demo01();


但是反射是什么?我们先通过Class对象获得构造器。为什么要这样做?你可以理解为它是倒着来的。

我们正常的是有i了构造器,然后实例化。现在我们这样去做。


构造器的获取在Class类中一个给了两个,一个是指定构造器,一个是返回一个数组。我们来看。



注意这两个只能拿到public修饰的构造器,要想拿到全部。我们可以用



举例子


指定构造器


Class<?> aClass = Class.forName("java_practice.Student");
        //基本数据类型也可以通过.class 得到对应的Class 类型
 Constructor<?> con = aClass.getConstructor(int.class, String.class); //填入指定构造方法中的类型.class即可
 System.out.println(con);//public java_practice.Student(int,java.lang.String)
//可获取到私有构造方法
 Constructor<?> dd = aClass.getDeclaredConstructor(String.class);
 System.out.println(dd);//private java_practice.Student(java.lang.String)


获取构造器数组对象


Class<?> aClass = Class.forName("java_practice.Student");
  Constructor<?>[] cons = aClass.getConstructors();//拿到public类型的构造方法
  for(Constructor con : cons)
        {
            System.out.println(con);
        }
  Constructor<?>[] dc = aClass.getDeclaredConstructors();//拿到所有的构造方法
  for(Constructor dcc: dc)
        {
            System.out.println( dcc);
        }


在获取到构造器之后呢,我们可以利用得到的构造器对象创建实例对象。


举个·例子,通过无参构造器来创建一个实例对象/类对象


三: 获取一般方法和成员变量

同样可以获取到方法和成员变量


1:获取一般方法(成员方法)

首先,我们还是可以先获取到Class对象,然后获取成员方法


api提供了两个获取一般方法的方法



同样一个是返回指定的方法对象,一个是返回一个数组


//获取单个方法公共的方法对象
Method m = aClass.getMethod("getName");
//获取到成员方法对象(包括了继承的方法)
Method[] methods = aClass.getMethods();
for(Method method:methods)
      {
         System.out.println(method);
      }


但是这样获取的一般只是公共的方法,私有化的方法是获取不到的。Class 类也提供了获取所有的方法包括私有方法的方法。



Method speak__ = aClass.getDeclaredMethod("speak__");//static 修饰的方法
Method kk = aClass.getDeclaredMethod("kk");//kk是私有化方法
Method[] mm = aClass.getDeclaredMethods();
for(Method m : mm)
    {
       System.out.println(m );
    }


2: 获取到成员变量

与此相关的Class提供的方法




Field field = aClass.getField("say");//public类型的
Field[] fields = aClass.getFields();
for(Field f : fields)
     {
         System.out.println(f);
     }
 Field name = aClass.getDeclaredField("name");//name是私有的
 Field[] ff = aClass.getDeclaredFields();
 for(Field f : ff)
      {
         System.out.println(f);
      }


三: 使用类属性


我们已经了解到如何获取到Class对象,构造器,方法,局部变量。那么我们如果去调用一个方法或者如何去给一个类的成员变量赋值?


1: 实现方法的调用

正常情况下,如果不是用到反射进行编程的话,我们可以用实例对象去调用一个方法。现在我们也实现实例方法对方法的人调用,只不过方式是不一样的。感受一下。


 

Class<?> aClass = Class.forName("java_practice.Student");//获取到Class 对象
        Constructor<?> con = aClass.getConstructor();//获取获取构造器
        Object o = con.newInstance();//获取实例化对象
        Method m = aClass.getMethod("speak");//获取指定的方法对象
        m.invoke(o);//调用实例化对象下的指定方法


注意对私有方法的调用的时候,还需要进行跳过访问检查,才可以调用到。具体参考。


 

Constructor<?> con = aClass.getConstructor();//获取获取构造器
        Object o = con.newInstance();//获取实例化对象
        Method kk = aClass.getDeclaredMethod("kk");//kk是私有化的
        kk.setAccessible(true);//跳过访问检查
        kk.invoke(o);


2: 给成员变量赋值

   

Field field = aClass.getField("say");
        //获取无参构造方法创建对象
        Constructor<?> con = aClass.getConstructor();
        Object obj = con.newInstance();
        field.set(obj,"hello");//给obj的成员变量say 赋值


 

Field field = aClass.getField("say");//获取成员变量对象
        Constructor<?> con = aClass.getConstructor();//获取构造器
        Object obj = con.newInstance();//获取类实例对象
        field.set(obj,"hello");//给obj的成员变量say 赋值
        Object o = field.get(obj);//获取obj对象下的成员say的对象
        System.out.println(o);//获取到成员变量的值


私有化的成员变量的一样要越过访问检查


 

Field name = aClass.getDeclaredField("name");
        Constructor<?> con_ = aClass.getConstructor();
        Object o1 = con_.newInstance();
        name.setAccessible(true);
        name.set(o1,"jgdabc");
        Object o = name.get(o1);
        System.out.println(o);


四: 反射越过泛型检查


这样的特点如何体现?


如果我定义一个集合,集合中的泛型是Integer的,我现在要给这个集合存放一个String类型的数据。

可以吗?用2反射的这个机制就可以实现。


package reflect_demo;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
//实现ArrayList<Integer>集合,给利用反射的特点给集合当中添加字符串数据
public class ReflectDemo08 {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        ArrayList<Integer> array = new ArrayList<Integer>();
        array.add(1);
//        通过反射实现正常情况下,无法实现的操作
        Class<? extends ArrayList> arr_cla = array.getClass();//还是获取到Class对象
        Method m = arr_cla.getMethod("add", Object.class);//调用到add方法,可以知道这个是Object类型的方法
         m.invoke(array, "hello"); //给array对象下的m里面的add方法传入值
         //反射可以越过泛型检查的,获取到原始的参数所需要的参数类型
        System.out.println(array);
    }
}



有没有觉得很有逼数。


五: 反射运行配置文件指定内容

创建两个类


package reflect_demo;
public class Teacher666 {
    public void teach()
    {
        System.out.println("love students");
    }
}


package reflect_demo;
public class Student666 {
    public void study()
    {
        System.out.println("study hard");
    }
}


给一个简单的配置文件Class.txt


className = reflect_demo.Student666
methodName = study


然后测试类·


package reflect_demo;
//通过配置文件运行类中的方法
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;
public class ReflectDemo09 {
    public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
        //加载数据
        Properties prop = new Properties();
        FileReader fileReader = new FileReader("E:\\java_doc\\src\\reflect_demo\\class.txt");
        prop.load(fileReader);
        fileReader.close();
        String className = prop.getProperty("className");//相当于根据键获取到值。
        String methodName = prop.getProperty("methodName");
        //通过反射来使用
        Class<?> c = Class.forName(className);
        Constructor<?> con = c.getConstructor();
        Object o = con.newInstance();
        Method m = c.getMethod(methodName);
        m.invoke(o);
    }
}


这样做的好处是,在我想要在该类指定运行对象的时候,我不需要在该类中进行指定类,或者像之前一样进行new对象,我想要运行对象的哪个方法,就直接在配置文件中进行简单修改就可以了。这里就体现了反射的这方面的特点。简单演示一下。反射是非常重要的一个知识点。在Spring中就会有用到反射的知识点,所以感觉基础扎实点会比较好,如果不是理解的就去用的话,很容易陷入迷茫,并且效率很低。


反射的优点,我们上面的简单说明体现了一部分。缺点在于动态解析这些方面,以及安全等等。其实你从代码上可以看的出来,从字节码进行找到这个类,然后获取相关的属性等等,这样其实是比较麻烦的,浪费效率,另外代码是无法和常规编程一样进行优化。反射可以做到一些我们常规方式下五发进行的操作,所以会存在安全的隐患。这样的实现很明显会破坏掉类的封装性等等。


但是我们会用到它,在工厂模式下会有非常大的用处。感觉有趣。


相关文章
|
10天前
|
Java
java入门代码示例
本文介绍Java入门基础,包含Hello World、变量类型、条件判断、循环及方法定义等核心语法示例,帮助初学者快速掌握Java编程基本结构与逻辑。
158 0
|
2月前
|
安全 Java 数据库连接
2025 年最新 Java 学习路线图含实操指南助你高效入门 Java 编程掌握核心技能
2025年最新Java学习路线图,涵盖基础环境搭建、核心特性(如密封类、虚拟线程)、模块化开发、响应式编程、主流框架(Spring Boot 3、Spring Security 6)、数据库操作(JPA + Hibernate 6)及微服务实战,助你掌握企业级开发技能。
268 3
|
4月前
|
Java API 微服务
2025 年 Java 从入门到精通学习笔记全新版
《Java学习笔记:从入门到精通(2025更新版)》是一本全面覆盖Java开发核心技能的指南,适合零基础到高级开发者。内容包括Java基础(如开发环境配置、核心语法增强)、面向对象编程(密封类、接口增强)、进阶技术(虚拟线程、结构化并发、向量API)、实用类库与框架(HTTP客户端、Spring Boot)、微服务与云原生(容器化、Kubernetes)、响应式编程(Reactor、WebFlux)、函数式编程(Stream API)、测试技术(JUnit 5、Mockito)、数据持久化(JPA、R2DBC)以及实战项目(Todo应用)。
224 5
|
27天前
|
前端开发 Java 数据库连接
帮助新手快速上手的 JAVA 学习路线最详细版涵盖从入门到进阶的 JAVA 学习路线
本Java学习路线涵盖从基础语法、面向对象、异常处理到高级框架、微服务、JVM调优等内容,适合新手入门到进阶,助力掌握企业级开发技能,快速成为合格Java开发者。
291 3
|
2月前
|
NoSQL Java 关系型数据库
Java 从入门到进阶完整学习路线图规划与实战开发最佳实践指南
本文为Java开发者提供从入门到进阶的完整学习路线图,涵盖基础语法、面向对象、数据结构与算法、并发编程、JVM调优、主流框架(如Spring Boot)、数据库操作(MySQL、Redis)、微服务架构及云原生开发等内容,并结合实战案例与最佳实践,助力高效掌握Java核心技术。
221 0
|
2月前
|
Java 测试技术 API
Java IO流(二):文件操作与NIO入门
本文详解Java NIO与传统IO的区别与优势,涵盖Path、Files类、Channel、Buffer、Selector等核心概念,深入讲解文件操作、目录遍历、NIO实战及性能优化技巧,适合处理大文件与高并发场景,助力高效IO编程与面试准备。
|
2月前
|
Java 编译器 API
Java Lambda表达式与函数式编程入门
Lambda表达式是Java 8引入的重要特性,简化了函数式编程的实现方式。它通过简洁的语法替代传统的匿名内部类,使代码更清晰、易读。本文深入讲解Lambda表达式的基本语法、函数式接口、方法引用等核心概念,并结合集合操作、线程处理、事件回调等实战案例,帮助开发者掌握现代Java编程技巧。同时,还解析了面试中高频出现的相关问题,助你深入理解其原理与应用场景。
|
18天前
|
Java API 数据库
2025 年最新 Java 实操学习路线,从入门到高级应用详细指南
2025年Java最新实操学习路线,涵盖从环境搭建到微服务、容器化部署的全流程实战内容,助你掌握Java 21核心特性、Spring Boot 3.2开发、云原生与微服务架构,提升企业级项目开发能力,适合从入门到高级应用的学习需求。
265 0
|
28天前
|
监控 Java API
2025 年全新出炉的 Java 学习路线:从入门起步到实操精通的详细指南
2025年Java学习路线与实操指南,涵盖Java 21核心特性、虚拟线程、Spring Boot 3、微服务、Spring Security、容器化部署等前沿技术,助你从入门到企业级开发进阶。
224 0
|
2月前
|
前端开发 Java 数据库
Java 项目实战从入门到精通 :Java Web 在线商城项目开发指南
本文介绍了一个基于Java Web的在线商城项目,涵盖技术方案与应用实例。项目采用Spring、Spring MVC和MyBatis框架,结合MySQL数据库,实现商品展示、购物车、用户注册登录等核心功能。通过Spring Boot快速搭建项目结构,使用JPA进行数据持久化,并通过Thymeleaf模板展示页面。项目结构清晰,适合Java Web初学者学习与拓展。
187 1