Java反射是一种非常重要的机制,初学者刚看时可能觉得反射机制太过于晦涩难懂直接跳过去了,但对于要掌握Spring等框架的程序员来说,必须要深入理解Java反射机制。本文将系统的介绍Java反射机制并同时详细记录我在学反射过程中的疑问。

1, 为什么会有反射?

在我们平时的项目开发过程中,基本上很少会直接使用到反射机制,但这不能说明反射机制没有用,实际上有很多设计、开发都与反射机制有关,例如模块化的开发,通过反射去调用对应的字节码动态代理设计模式也采用了反射机制,还有我们日常使用的 Spring/ Hibernate等框架,也是利用cGLB反射机制才得以实现。
引用于该博文:为什么需要反射机制? 假如你写了一段代码:Object o=new Object(); 运行上面这行创建Object对象的代码时,JVM发生了什么?

首先JVM会启动,你的代码会编译成一个.class文件,然后被类加载器加载进jvm的内存中,你的类Object加载到方法区中,创建了Object类的class对象到堆中,注意这个不是new出来的对象,而是类的类型对象,每个类只有一个class对象,作为方法区类的数据结构的接口。JVM创建对象前,会先检查类是否加载,寻找类对应的class对象,若加载好,则为你的对象分配内存,初始化也就是代码:new Object()。

上面的流程就是我们自己写好的代码扔给JVM去跑,跑完就结束了,JVM关闭,程序也停止了。
假如某天,服务器上遇到某个请求需要应用到某个类,但是该类没有加载到JVM,怎么办?我们是需要写一段代码去new需要的class对象吗(停下来意味着关闭虚拟机然后重新编译代码)?
答案大家也知道!我们可以使用反射机制

反射机制可以做到,我们的程序正在运行时,某一些类可能之前用不到,而是在运行的时候才需要加载。举例:我们的项目底层有时是用mysql,有时用oracle,需要动态地根据实际情况加载驱动类,这个时候反射就有用了,假设 com.java.dbtest.myqlConnection,com.java.dbtest.oracleConnection这两个类我们要用,这时候我们的程序就写得比较动态化,通过Class tc = Class.forName (“com.java.dbtest.TestConnection”);通过类的全类名让jvm在服务器中找到并加载这个类,而如果是oracle则传入的参数就变成另一个了。这时候就可以看到反射的好处了,这个动态性就体现出java的特性了!举多个例子,大家如果接触过spring,会发现当你配置各种各样的bean时,是以配置文件的形式配置的,你需要用到哪些bean就配哪些,spring容器就会根据你的需求去动态加载,你的程序就能健壮地运行。

java的反射机制就是增加程序的灵活性,避免将程序写死到代码里, 例如: 实例化一个 person()对象, 不使用反射, new person(); 如果想变成 实例化 其他类, 那么必须修改源代码,并重新编译。 使用反射: class.forName (“person”).newInstance(); 而且这个类描述可以写到配置文件中,如 **.xml, 这样如果想实例化其他类,只要修改配置文件的"类描述"就可以了,不需要重新修改代码并编译。

补充知识

Java反射真的慢吗_Java反射真的慢吗

方法区存的是类的信息,不是存类对象的,建议看一下JVM内存分配,类加载器加载类是通过方法区上类的信息在堆上创建一个类的Class对象,这个Class对象是唯一的,由JVM保证唯一,之后对这个类的创建都是根据这个Class对象来操作的。
你可以理解成类存在方法区中,类的class对象存在于堆中,这个class对象会作为运行时创建该类对象的模版。这个class对象是唯一对应该类的,要区分所谓的实例和class对象。为什么需要class对象,你想下如果一个加载进方法区的类,在jvm运行时是动态加载进来的,没有这个class对象你思考该如何访问一个未知的类并创建对象呢?没错就是这个class作为访问接口。

2, 什么是反射机制?

反射是一种强大且复杂的机制,使用它的主要是工具的构造者(可以理解为框架程序员),而不是应用程序员(使用框架的可以理解为应用程序员)。核心是JVM在运行的时候才动态地加载它所需要的类,并且对于任意一个类,都能够知道该类的所有属性以及方法,调用方法/访问属性,不需要提前在编译期知道运行的对象是谁。当我们的程序在运行时,可能需要动态的加载一些类,这些类因为之前用不到,所以没有加载到JVM,这时,使用Java反射机制可以在运行期动态的创建对象并调用其属性,它是在运行时根据需要才加载。
反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。

3, 反射机制有什么用?

3.1 用处

反射应用场景

  • 反编译:.class–>.java
  • 可以在运行中分析类的能力(即获取类的所有方法及属性)
  • 在运行中查看对象,比如编写一个toString方法供所有所有类使用
  • 实现通用的数组操作代码(未懂)
  • 利用Method对象(未懂)
  • 当我们在使用IDE,比如Ecplise时,当我们输入一个对象或者类,并想调用他的属性和方法是,一按点号,编译器就会自动列出他的属性或者方法,这里就是用到反射。
  • 反射最重要的用途就是开发各种通用框架。比如很多框架(Spring)都是配置化的(比如通过XML文件配置Bean),为了保证框架的通用性,他们可能需要根据配置文件加载不同的类或者对象,调用不同的方法,这个时候就必须使用到反射了,运行时动态加载需要的加载的对象。

3.2 优点

使用反射,我们就可以在运行时获得类的各种内容,进行反编译,对于Java这种先编译再运行的语言,能够让我们很方便的创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象。

3.3 缺点

(1)反射会消耗一定的系统资源,因此,如果不需要动态地创建一个对象,那么就不需要用反射;(需要把class对象加载到方法区)
(2)反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。
补充:如果我们要查看某个private属性的值,由于受限于java的访问机制,我们需要调用Field、Method 或 Constructor 对象的 setAccessible 方法,来设置private的值的可访问性,x. setAccessible(true);,x为Field、Method 或 Constructor的对象。

Class em = s.getClass();
Object obj = em.newInstance();
Field f = em.getDeclaredField("name");//name为private属性
f.setAccessible(true);
Object val = f.get(obj);//获取属性的值

4, 如何使用反射?

4.1 什么是Class类

在程序运行期间,Java运行时系统始终为所有的对象维护一个被称为运行时的类型标识,这个信息追踪者每一个对象所属的类。虚拟机利用运行时类型信息选择相应的方法执行。
当一个类被加载以后,Java虚拟机就会自动产生一个Class对象。通过这个Class对象我们就能获得加载到虚拟机当中这个Class对象对应的方法、成员变量以及构造方法的声明和定义等信息。

4.2 获取Class对象的三种方法

  • 通过Object类中的getClass()方法将会返回一个Class类型的实例
PerSon e;
Class p = e.getClass()
  • 使用.class。如果T是任意的Java类型,T.class将代表对象T所属的类对象。(最简单)
Person e;
Class p = e.class'
  • 调用静态方法forName(类名)获取类名对应的Class对象(使用得最多,但需要检查异常)
try {
	Class tc = Class.forName("om.zhiwenwu.Student");//注意此字符串必须是真实路径,就是带包名的类路径,包名.类名
} 
catch (ClassNotFoundException e){
	e.printStackTrace();
}

4.3 利用反射创建对象的两种方法

  • 使用newInstance()方法快速创建一个类实例
try {
	Class tc = Class.forName("om.zhiwenwu.Student");//注意此字符串必须是真实路径,就是带包名的类路径,包名.类名
} 
catch (ClassNotFoundException e){
	e.printStackTrace();
}
Object s = tc.newInstance();//不知道创建的对象类型,必须使用Object,
  • 如果需要按照名称创建的类的构造器提供参数,则不能使用第一种方法,而必须使用Constructor类中的newInstance()方法。(Constructor 构造器)
try {
	Class tc = Class.forName("com.zhiwenwu.Student");//注意此字符串必须是真实路径,就是带包名的类路径,包名.类名
} 
catch (ClassNotFoundException e){
	e.printStackTrace();
}
Constructor conc = tc.getConstructor()
Object s = conc.newInstance("zhiwenwu")

5, 实例

package com.zhiwenwu;

public class Student {

    //---------------构造方法-------------------
    //(默认的构造方法)
    Student(String str){
        System.out.println("(默认)的构造方法 s = " + str);
    }

    //无参构造方法
    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 n){
        System.out.println("受保护的构造方法 n = " + n);
    }
    //私有构造方法
    private Student(int age){
        System.out.println("私有的构造方法   年龄:"+ age);
    }
}
package com.zhiwenwu;

import java.lang.reflect.Constructor;


/*
 * 通过Class对象可以获取某个类中的:构造方法、成员变量、成员方法;并访问成员;
 *
 * 1.获取构造方法:
 * 		1).批量的方法:
 * 			public Constructor[] getConstructors():所有"公有的"构造方法
            public Constructor[] getDeclaredConstructors():获取所有的构造方法(包括私有、受保护、默认、公有)

 * 		2).获取单个的方法,并调用:
 * 			public Constructor getConstructor(Class... parameterTypes):获取单个的"公有的"构造方法:
 * 			public Constructor getDeclaredConstructor(Class... parameterTypes):获取"某个构造方法"可以是私有的,或受保护、默认、公有;
 *
 * 			调用构造方法:
 * 			Constructor-->newInstance(Object... initargs)
 */


public class Constructors {

    public static void main(String[] args) throws Exception {
        //1.加载Class对象
        Class clazz = Class.forName("com.zhiwenwu.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("*****************获取公有、无参的构造方法*******************************");
        Constructor con = clazz.getConstructor(null);
        //1>、因为是无参的构造方法所以类型是一个null,不写也可以:这里需要的是一个参数的类型,切记是类型
        //2>、返回的是描述这个无参构造函数的类对象。

        System.out.println("con = " + con);
        //调用构造方法
        Object obj = con.newInstance();
        System.out.println("obj = " + obj.toString());
        Student stu = (Student)obj;
        System.out.println("stu = " + stu.toString());
        if(obj == stu){
            System.out.println("true");
        }
        System.out.println("******************获取私有构造方法,并调用*******************************");
        con = clazz.getDeclaredConstructor(char.class);
        System.out.println(con);
        //调用构造方法
        con.setAccessible(true);//暴力访问(忽略掉访问修饰符)
        obj = con.newInstance('男');
    }

}