Java中的反射

在Java框架的学习中,我们会接触到反射,例如接触非常多的spring框架,它的IOC原理就是运用了反射的机制以及动态代理的思想,使得对象的创建并不需要自己完成,而是交给spring容器来完成。还有JDBC中加载数据库的驱动时,也必然使用到反射等等。可以说,反射机制是我们在框架设计和学习中非常重要的一个机制和思想。

什么是反射

反射机制就是程序在运行时,对于任意一个类,都能知道这个类的所有属性和方法;对于任意一个对象,都能调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能成为Java语言的反射机制。也就是说,只要给定类的名字,我们就可以通过反射机制来获得类的所有信息。

反射机制主要提供了以下功能:

1.在运行时可以判定任意一个对象所属的类

2.在运行时创建对象

3.在运行时判定任意一个类所具有的成员变量和方法

4.在运行时调用任意一个对象的方法

5.生成动态代理

上面说了这么多,用我们自己理解的话说,什么是反射?反射就是把Java中的各个成分映射成一个个的Java对象。我们知道,在Java中,一个类的包含类的成员变量以及方法(包括普通方法和构造方法)以及包等信息,我们可以利用反射机制,把这些组成部分映射成一个个的对象。

学习反射机制的前提——类的加载

在说类的加载以前,我们要知道程序的运行的过程。从源文件创建到程序运行,Java程序要经过两大步骤:编译,运行;

1、源文件由编译器编译成字节码(ByteCode) 。创建完源文件后,程序会被编译器编译成.class文件,Java编译一个类时,如果这个类所依赖的类还没有被编译,编译器会先编译它所依赖的类,然后再引用,否则直接引用它依赖的类。编译后的字节码文件格式主要分为两个部分:常量池和方法字节码。

2、字节码由java虚拟机解释运行。主要分为两个过程:类的加载和执行

什么是类的加载呢?类的加载就是Java程序经过编译成*.class文件,通过类加载器将字节码(*.class)加载到JVM内存中。JVM将类加载过程分为加载、连接、初始化三个阶段,其中连接阶段又可以分为验证、准备、解析三个阶段。

类加载器

JVM的加载是通过ClassLoader (类加载器)及其子类来完成的,类加载器可以由下图来描述(也可成为类加载器的双亲委派模型)


1)Bootstrap ClassLoader启动类加载器

负责加载$JAVA_HOME中jre/lib/里所有的 class(JDK 代表 JDK 的安装目录,下同),或被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库(如 rt.jar,所有的java.*开头的类均被 Bootstrap ClassLoader 加载)。启动类加载器由 C++ 实现,不是 ClassLoader 子类。无法被 Java 程序直接引用的。

2)Extension ClassLoader扩展类加载器

该加载器由sun.misc.Launcher$ExtClassLoader实现,负责加载Java平台中扩展功能的一些jar包,包括JAVA_HOME中jre/lib/ext目录中的或被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。
ExtClassLoa被Java。extderExtClassLoader实现,负责加载Java平台中扩展功能的一些jar包,包括JAVA_HOME中jre/lib/.jar或-Djava.ext.dirs指定目录下的 jar 包。即JDK\jre\lib\ext目录中,或者由 java.ext.dirs 系统变量指定的路径中的所有类库(如javax.开头的类),开发者可以直接使用扩展类加载器

3)App ClassLoader应用程序类加载器

。该类加载器由 sun.misc.Launcher$AppClassLoader 来实现,负责记载 classpath 中指定的 jar 包及目录中 class,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

启动类加载器:它使用 C++ 实现(这里仅限于 Hotspot,也就是 JDK1.5 之后默认的虚拟机,有很多其他的虚拟机是用 Java 语言实现的),是虚拟机自身的一部分。 
所有其他的类加载器:这些类加载器都由 Java 语言实现,独立于虚拟机之外,并且全部继承自抽象类 java.lang.ClassLoader,这些类加载器需要由启动类加载器加载到内存中之后才能去加载其他的类。 
应用程序都是由这三种类加载器互相配合进行加载的,我们还可以加入自定义的类加载器。

类的加载

JVM加载类分为三个阶段:加载   连接(验证 准备 解析) 初始化,在第一步,Java虚拟机完成三件事

1.通过一个类的全限定名来获取其定义的二进制字节流

2.将这个字节流所带变的静态结构转化为方法区的运行是数据结构

3.在Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区中这些数据的访问路口

相对于类加载过程的其他阶段,一个非数组类的加载阶段(准确地说,是加载阶段中获取类的二进制字节流文件的动作)是开发人员可控性最强的,因为加载阶段既可以使用系统提供的引导类加载器来控制字节流的获取方式(即重写一个类加载器的loadClass()方法)

反射的具体使用

我们知道,在Java中,获取Class对象的方式有三种方式:

1、Object的getClass()方法

2、class属性可以直接获取到

3、通过Class类的静态方法Class.forName();方法获取

我们通过代码来看看

我们先写一个bean类Student

public class Student{
	int id;
	String name;
	int age;

}
package fanshe;

 /** 
 * 获取Class对象的三种方式 
 * 1 Object ——> getClass(); 
 * 2 任何数据类型(包括基本数据类型)都有一个静态的class属性 
 * 3 通过Class类的静态方法:forName(String  className)(常用) 
 * 
 */

public class ReflectTest {
	
	public static void main(String[] args) {
		//第1种方式
		Student stu1 = new Student();
		Class stu1Class = stu1.getClass();
		System.out.println(stu1Class.getName());
		
		
		//第2种方式
		Class stu2Class = Student.class;
		//判断和第1种方式的结果是否一致
		System.out.println(stu2Class == stu1Class);
		
		
		//第3种方式
		try {
			Class stu3Class = Class.forName("fanshe.Student");
			System.out.println(stu3Class == stu1Class);
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}
}

三种方式中我们常用的还是第三种,因为第一种方式通过对象的getClass()方法,必然需要先知道对象所从属的类是什么,这种方式有点多此一举;第二种方式通过.class属性的方式获取,但是它需要我们自己导入这个类所依赖的所有包。第三种方式是常用的。

1.通过反射获取构造方法

class Student{
    String name;
    int age;
    char sex;

    public Student(){
        System.out.println("公有的无参的构造方法");
    }
    Student(String str){
        System.out.println("默认的构造方法" + str);
    }

    public Student(int age){
        System.out.println("age:"+age);
    }

    public Student(String name,int age){
        System.out.println("name:"+name+",age:"+age);
    }

    protected  Student(boolean b){
        System.out.println("受保护的构造方法");
    }

    private Student(char sex){
        System.out.println("私有的构造方法"+sex);
    }

}

我这里给出一个Student类,里面有6个构造方法,再来看看测试类

package fanshe;  
  
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("fanshe.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);  
    //  Student stu = (Student)obj;  
          
        System.out.println("******************获取私有构造方法,并调用*******************************");  
        con = clazz.getDeclaredConstructor(char.class);  
        System.out.println(con);  
        //调用构造方法  
        con.setAccessible(true);//暴力访问(忽略掉访问修饰符)  
        obj = con.newInstance('男');  
    }  
      
}

打印结果:

**********************所有公有构造方法*********************************  
public fanshe.Student(java.lang.String,int)  
public fanshe.Student(char)  
public fanshe.Student()  
************所有的构造方法(包括:私有、受保护、默认、公有)***************  
private fanshe.Student(int)  
protected fanshe.Student(boolean)  
public fanshe.Student(java.lang.String,int)  
public fanshe.Student(char)  
public fanshe.Student()  
fanshe.Student(java.lang.String)  
*****************获取公有、无参的构造方法*******************************  
con = public fanshe.Student()  
调用了公有、无参构造方法执行了。。。  
******************获取私有构造方法,并调用*******************************  
public fanshe.Student(char)  
姓名:男

方法说明:

1、获取构造方法:

1)批量的方法:public Constructor[] getConstructors():所有"公有的"构造方法

                          public Constructor[] getDeclaredConstructors():获取所有的构造方法(包括私有、受保护、默认、公有)

2).获取单个的方法,并调用:
public Constructor getConstructor(Class... parameterTypes):获取单个的"公有的"构造方法:

public Constructor getDeclaredConstructor(Class... parameterTypes):获取"某个构造方法"可以是私有的,或受保护、默认、公有;

调用构造方法:Constructor-->newInstance(Object... initargs)

newInstance是 Constructor类的方法(管理构造函数的类)

api的解释为:newInstance(Object... initargs)使用此Constructor对象表示的构造方法来创建该构造方法的声明类的新实例,并用指定的初始化参数初始化该实例。它的返回值是T类型,所以newInstance是创建了一个构造方法的声明类的新实例对象。并为之调用。

2.获取成员变量并调用

Student类

package fanshe;


public class Student {
    
    public 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 + "]";
    }
}
测试类:
package fanshe;

import java.lang.reflect.Field;

public class Fields {


    public static void main(String[] args) throws Exception{
        //1.获取Class对象
        Class clazz = Class.forName("fanshe.Student");

        //2.获取字段
        System.out.println("------获取所有公有的字段------");
        Field[] fields = clazz.getFields();
        for (Field f:fields) {
            System.out.println(f);
        }

        System.out.println("------获取所有的字段,包括公有的受保护的默认的私有的------");
        fields = clazz.getDeclaredFields();
        for (Field f:fields) {
            System.out.println(f);
        }

        System.out.println("------获取所有公有字段  并调用-----------");
        Field filed = clazz.getField("name");
        System.out.println(filed);
        //获取一个对象
        Object obj = clazz.getConstructor().newInstance();//产生Student对象  相当于 Student student = new Student();
        //为字段设值
        filed.set(obj,"小明");
        //验证设置是否正确
        Student stu = (Student)obj;
        System.out.println("验证姓名:"+stu.name);


        System.out.println("------------获取私有字段 并调用---------");
        filed = clazz.getDeclaredField("phoneNum");
        System.out.println(filed);
        filed.setAccessible(true);//暴力反射,解除phoneNum的私有限定
        filed.set(obj,"11111111111");
        System.out.println("验证电话:"+stu);


    }


}
运行结果:
------获取所有公有的字段------
public java.lang.String fanshe.Student.name
------获取所有的字段,包括公有的受保护的默认的私有的------
public java.lang.String fanshe.Student.name
protected int fanshe.Student.age
char fanshe.Student.sex
private java.lang.String fanshe.Student.phoneNum
------获取所有公有字段  并调用-----------
public java.lang.String fanshe.Student.name
验证姓名:小明
------------获取私有字段 并调用---------
private java.lang.String fanshe.Student.phoneNum
验证电话:Student [name=小明, age=0, sex= , phoneNum=11111111111]
方法说明:在调用字段需要传递两个参数,一个是你要设置的对象,另一个是你要设置该字段具体的值。所以需要如下操作

1.Object obj = clazz.getConstructor().newInstance();相当于创建了一个Student对象,与Student stu = new Student()操作一样。

2.为字段设置值,代码为field.set(obj,"xiaoming");//为第1步中创建的stu对象的name属性设置具体值。stu.name="xiaoming";


3.获取成员方法并调用

Student类

package fanshe1;


public class Student {

    public void method1(String s){
        System.out.println("调用了:公有的,String参数的show1(): s = " + s);
    }

    protected  void method2(){
        System.out.println("调用了:受保护的,无参的show2()");
    }

    void mehtod3(){
        System.out.println("调用了:默认的,无参的show3()");
    }
    private String method4(int age){
        System.out.println("调用了,私有的,并且有返回值的,int参数的show4(): age = " + age);
        return "abcd";
    }
}
测试类:
package fanshe1;


import java.lang.reflect.Method;

public class MethodTest {

    public static void main(String[] args) throws Exception{
        //1.获取Class对象
        Class clazz = Class.forName("fanshe1.Student");

        //获取成员方法操作

        System.out.println("-------获取公有的成员方法---------");
        Method[] methods = clazz.getMethods();
        for (Method m:methods) {
            System.out.println(m);
        }

        System.out.println("-------获取所有的成员方法(包含公有的、受保护的、默认的、私有的)--------");
        methods = clazz.getDeclaredMethods();
        for (Method m:methods) {
            System.out.println(m);
        }


        System.out.println("-------获取公有的并且方法名为method1的方法-------------");
        Method method = clazz.getMethod("method1",String.class);
        System.out.println(method);
        //实例化一个Student对象
        Object obj = clazz.getConstructor().newInstance();
        method.invoke(obj,"xiaoming");

        System.out.println("-------获取私有的并且方法名为method4的方法-------------");
        method = clazz.getDeclaredMethod("method4",int.class);
        System.out.println(method);
        method.setAccessible(true);//解除show4方法的私有限定
        Object obj1 =method.invoke(obj,18);//需要两个参数,一个是要调用的对象(获取有反射),一个是实参
        System.out.println("返回值:"+obj1);




    }
}
运行结果
-------获取公有的成员方法---------
public void fanshe1.Student.method1(java.lang.String)
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
-------获取所有的成员方法(包含公有的、受保护的、默认的、私有的)--------
private java.lang.String fanshe1.Student.method4(int)
public void fanshe1.Student.method1(java.lang.String)
protected void fanshe1.Student.method2()
void fanshe1.Student.mehtod3()
-------获取公有的并且方法名为method1的方法-------------
public void fanshe1.Student.method1(java.lang.String)
调用了:公有的,String参数的show1(): s = xiaoming
-------获取私有的并且方法名为method4的方法-------------
private java.lang.String fanshe1.Student.method4(int)
调用了,私有的,并且有返回值的,int参数的show4(): age = 18
返回值:abcd
方法说明:
method = stuClass.getDeclaredMethod("method4", int.class);//调用制定方法(所有包括私有的),需要传入两个参数,第一个是调用的方法名称,第二个是方法的形参类型,切记是类型。
System.out.println(m);
m.setAccessible(true);//解除私有限定
Object result = m.invoke(obj, 20);//需要两个参数,一个是要调用的对象(获取有反射),一个是实参
System.out.println("返回值:" + result);

4.反射main方法

Student类

package fanshe2;


public class Student {

    public static void main(String[] args){
        System.out.println("main方法执行");
    }
}
测试类
package fanshe2;

import java.lang.reflect.Method;

public class Main {

    public static void main(String[] args){
        try {
            //1.获取Class对象
            Class clazz = Class.forName("fanshe2.Student");
            //2.获取main方法
            Method method = clazz.getMethod("main",String[].class);//第一个参数为方法名,第二个参数为对应方法中的参数类型

            //第一个参数,对象类型,因为方法是static静态的,所以为null可以,第二个参数是String数组,这里要注意在jdk1.4时是数组,jdk1.5之后是可变参数
            //这里拆的时候将  new String[]{"a","b","c"} 拆成3个对象。。。所以需要将它强转。
            method.invoke(null,(Object) new String[]{"a","b","c"});//第一种方式
            //method.invoke(null,new Object[]{new String[]{"a","b","c"}});//第二种方式


        }catch (Exception e){
            e.printStackTrace();
        }
    }

}
运行结果:
main方法执行

5.通过反射获取配置文件内容

package fanshe3;


public class Student {


    public void show(){
        System.out.println("show方法执行...");

    }
}

pro.txt  我在自己的电脑的E盘中创建了pro.txt、

className = fanshe3.Student
methodName = show

测试类

package fanshe3;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Properties;

public class Test {

    public static void main(String[] args){
        try {
            //1.获取Class对象
            Class clazz =Class.forName(getValue("className"));
            //2.获取show方法
            Method method = clazz.getMethod(getValue("methodName"));
            method.invoke(clazz.getConstructor().newInstance());


        }catch (Exception e){
            e.printStackTrace();
        }

    }

    public static String getValue(String key) throws IOException{
        Properties pro = new Properties();//获取配置文件的对象
        File file = new File("E:\\pro.txt");
        FileReader in = new FileReader(file);//获取输入流
        pro.load(in);//将流加载到配置文件对象中
        in.close();
        return pro.getProperty(key);//返回根据key获取的value值



    }

}
输出结果
show方法执行...

6.通过反射越过泛型检查

泛型用在编译期,编译过后泛型擦除(消失掉)。所以是可以通过反射越过泛型检查的
package fanshe4;

import java.lang.reflect.Method;
import java.util.ArrayList;


public class Test {

    public static void main(String[] args) throws Exception{
        ArrayList<String> list = new ArrayList<>();
        list.add("aaa");
        list.add("bbb");

        //获取ArrayList的Class对象,反向的调用add()方法,添加数据
        Class clazz = list.getClass();//得到list对象的字节码对象
        //获取add()方法
        Method method = clazz.getMethod("add", Object.class);
        //调用add方法
        method.invoke(list,"ccc");
        
        //遍历集合,检查ccc是否add成功、
        for (Object obj:list) {
            System.out.println(obj);
        }


    }
}

输出结果
aaa
bbb
ccc







  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值