【Java】反射入门

本文是面向初学者的反射入门教程。主要内容是在运行时获取类的构造方法、成员方法、成员变量。文章侧重于怎么做(代码怎么写),弱化对反射内部原理的解释说明。

目录

什么是反射

类加载器

Class类

Constructor类

Field类

Method类


什么是反射

反射的定义:允许运行中的 Java 程序获取自身的信息,并且可以操作类或对象的内部属性。通过 Class类 获取 某类的字节码信息称之为反射(Reflection)。

反射的功能:

  1. 对于任意一个,都能够知道这个类的所有属性方法
  2. 对于任意一个对象,都能够调用它的任意一个方法属性

反射的前提:

  1. 被反射的类,一定要有空参数的构造方法;
  2. 构造方法权限必须是public;

JAVA语言编译之后会生成一个.class文件,反射就是通过字节码文件找到某一个类、类中的方法以及属性等。并且调用该类的方法和属性。

反射的实现主要借助以下四个类:

  1. Class:代表任何的类
  2. Constructor:代表类的构造方法
  3. Field:代表类中的属性
  4. Method:代表类中的方法

类加载器

当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化。

类的加载过程:

  1. 加载
    1. 就是指将class文件读入内存,并为之创建一个Class对象。
    2. 任何类被使用时系统都会建立一个Class对象
  2. 连接
    1. 验证 是否有正确的内部结构,并和其他类协调一致
    2. 准备 负责为类的静态成员分配内存,并设置默认初始化值
    3. 解析 将类的二进制数据中的符号引用替换为直接引用
  3. 初始化
    1. 创建类的实例
    2. 类的静态变量,或者为静态变量赋值
    3. 类的静态方法
    4. 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
    5. 初始化某个类的子类
    6. 直接使用java.exe命令来运行某个主类

类加载器的组成

  • Bootstrap ClassLoader 根类加载器
  1. 也被称为引导类加载器,负责Java核心类的加载,最基础的文件加载
  2. 比如System,String等。在JDK中JRE/lib/rt.jar文件中
  • Extension ClassLoader 扩展类加载器
  1. 负责JRE的扩展目录中jar包的加载,也即加载JRE/lib/ext/*jar
  2. 在JDK中JRE的lib目录下ext目录
  • System ClassLoader 系统类加载器
  1. 负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包和类路径。例如,在MySQL的驱动的时候,就是在系统类加载器中实现加载的。

通过这些描述就可以知道我们常用的类,都是由谁来加载完成的。到目前为止我们已经知道把class文件加载到内存了,那么,如果我们仅仅站在这些class文件的角度,我们如何来使用这些class文件中的内容呢?这就是我们反射要研究的内容。

如何获得类加载器:

获得该类的字节码对象:Class c = 类名.class;

获得类加载器:ClassLoader cl = c.getClassLoader();

获得该类下的所有资源:getResource(“资源名”);

Class类

什么是Class类:

  1. 当JVM装载了字节码文件(即.class文件)后,会把每一个类都封装到Class类实例中,每一个类都对应着一个Class实例。
  2. Class类实例(即Class对象)中保存的是创建类的一些列数据信息,比如创建一个HelloWorld类,那么JVM会生成一个内容是HelloWorld的Class类对象。
  3. Class类的作用是运行时提供或获得某个对象的类型信息,和C++中的typeid()函数类似。这些信息也可用于反射。

特性:

  • Class 没有公共构造方法。即不能程序员创建
  • Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的。
  • 所在位置:java.lang.Class<T>

获取Class对象的三种方式:

  1. 对象获取
    类名 变量名1 = new 类名();
    Class 变量名2 = 变量名1.getClass();
  2. 类名获取
    Class 变量名 = 类名.class;
  3. Class类的静态方法获取
    Class 变量名 = Class.forName(字符串的类名,即包名和类名要一起);(注意需要抛出异常ClassNotFoundException)

注意:三种方式获取到的Class对象相等!

如何通过反射创建对象?

  1. 方案一:获取Class类对象,调用newInstance()方法,注意这种方式只能生成object类的实例
  2. 方案二:获取Class类对象,获取构造器,由构造器调用newInstance()方法

通过反射快速创建类实例

//前提:①必须又有空构造方法; ②构造方法权限必须是public
package kyle;

import entity.Person;

import java.lang.reflect.Constructor;

public class Main {
    public static void main(String[] args) throws Exception {
        Class c = Class.forName("entity.Person");
        Object obj = c.newInstance();
        System.out.println(obj.toString());

        Constructor<Person> con = c.getConstructor(String.class,Integer.class,String.class);
        Person person = con.newInstance("kyle",22,"man");
        System.out.println(person.toString());
    }
}
/*
    输出:
    Person{name='null', age=null, sex='null'}
    Person{name='kyle', age=22, sex='man'}
 */

Constructor类

java.lang.reflect.Constructor<T>

获取构造器:

public构造器

  1. Constructor getConstructor()
  2. Constructor[] getConstructors()
  3. Constructor<T> getConstructor(Class<?>... parameterTypes)  获取指定参数名的构造器。例如Constructor con = c.getConstructor(String.class,int.class);就是获取参数为String和int型的构造函数

所有(含private)构造器

  1. Constructor[] getDeclaredConstructors()  获取全部构造器,即使是private的构造器
  2. Constructor getDeclaredConstructor(Class... parameterTypes)  获取指定参数的构造器,即使是private的构造器

操作构造器:

  1. void setAccessible(true)  修改构造器访问权限,true代表暴力攻破权限,false表示保留不可访问权限(暴力反射)
  2. T newInstance(Object… initargs)  调用构造器传入初始化参数,创建对象

获取public的构造器

import java.lang.reflect.Constructor;

public class Main {
	public static void main(String[] args) throws Exception {
		Class c = Class.forName("kyle.Person"); //获取kyle包下的Person类
		
		// Constructor[] getConstructors() 获取class文件对象中的所有公共的构造方法
		Constructor[] cons = c.getConstructors();
		for (Constructor con : cons) {
			System.out.println(con);
		}
		/*输出
			public kyle.Person(java.lang.String,int)
			public kyle.Person()
		*/
		
		//获取空参数的构造方法
		Constructor con =  c.getConstructor();
		System.out.println(con); //输出:public kyle.Person()
		
		//使用空参构造器创建类实例
		Object obj = con.newInstance();
		System.out.println(obj.toString()); //输出:Person [name=null, age=0]
	}
}

获取public有参构造器

import java.lang.reflect.Constructor;

public class Main {
	public static void main(String[] args) throws Exception {
		Class c = Class.forName("kyle.Person");
		//获取带有指定参数的构造方法
		//Constructor<T> getConstructor(Class<?>... parameterTypes)  
		//Class<?>... parameterTypes 传递要获取的构造方法的参数列表
		Constructor con = c.getConstructor(String.class,int.class);

		//使用该构造器创建实例
		// T newInstance(Object... initargs)  
		//Object... initargs 运行构造方法后,传递的实际参数
		Object obj = con.newInstance("张三",20);
		System.out.println(obj);
	}
}
//输出;Person [name=张三, age=20]

获取私有的构造方法

import java.lang.reflect.Constructor;

public class Main {
	public static void main(String[] args) throws Exception {
		Class c = Class.forName("kyle.Person");
		//Constructor[] getDeclaredConstructors()获取所有的构造方法,包括私有的
		Constructor[] cons = c.getDeclaredConstructors();
		for(Constructor con : cons){
			System.out.println(con);
		}
		/*输出:
		private kyle.Person(int,java.lang.String)
		public kyle.Person(java.lang.String,int)
		public kyle.Person()
		*/
		
		//Constructor getDeclaredConstructor(Class...c)获取到指定参数列表的构造方法
		Constructor con = c.getDeclaredConstructor(int.class, String.class);

		// Constructor类的父类中有一个父类AccessibleObject
		// 该类有个方法setAccessible(boolean b),可以实现私有的变量和方法的获取
		con.setAccessible(true);

		Object obj = con.newInstance(18, "lisi");
		System.out.println(obj);
	}
}

Field类

java.lang.reflect.Field

 

public成员变量:

  1. Field getField(String name);
  2. Field [] getFields();

全部(含private、常量、静态)成员变量:

  1. Field getDeclaredField(String name);
  2. Field[] getDeclaredFields();

操作成员变量:

  1. void set(Object obj, Object value):给对象注入某个成员变量数据
  2. Object get(Object obj):获取对象的成员变量的值。
  3. void setAccessible(true);暴力反射,设置为可以直接访问私有类型的属性。
  4. Class getType(); 获取属性的类型,返回Class对象。
  5. String getName(); 获取属性的名称。

获取public成员变量

import java.lang.reflect.Field;

public class Main {
	public static void main(String[] args) throws Exception {
		Class c = Class.forName("kyle.Person");
		
		// Class类的方法:getFields()可以获取被反射类中的所有public成员变量
		// 返回值是Field[],其中Field类描述成员变量对象的类
		Field[] fields = c.getFields();
		for(Field f : fields){
			System.out.println(f);
		} //输出:public java.lang.String kyle.Person.name
		
		//获取指定的成员变量: Field getField(传递字符串类型的变量名)
		Field field = c.getField("name");
	   
		//修改成员变量的值:void set(Object obj, Object value)
		//Object obj 必须有对象的支持,  Object value 修改后的值
		Object obj = c.newInstance();
		field.set(obj,"王五");
		System.out.println(obj); //输出:Person [name=王五, age=0]
	}
}

Method类

java.lang.reflect.Method

 

获取public方法:

  1. public Method[] getMethods() throws SecurityException; 返回某个类的所有公用(public)方法,包括其继承类的公用方法。
  2. public Method getMethod(String name, Class<?>... parameterTypes);

获取全部(含private)方法:

  1. public Method[] getDeclaredMethods() throws SecurityException; 返回类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。
  2. Method getDeclaredMethod(String name, Class<?>... parameterTypes);

操作方法:

Object invoke(Object obj, Object... args)  参数一:触发的是哪个对象的方法执行。参数二: args:调用方法时传递的实际参数

获取并执行成员方法

import java.lang.reflect.Method;

public class Main {
	public static void main(String[] args) throws Exception {
		Class c = Class.forName("kyle.Person");
		//获取被反射类中的成员方法:Method[] getMethods()获取的是class文件中的所有公共成员方法,包括继承的
		Method[] methods = c.getMethods();
		for(Method m : methods){
			System.out.println(m);
		}
		
		//获取指定的方法:Method getMethod(String methodName,Class...c)
		Method method = c.getMethod("eat"); //获取“eat”方法
		
		//运行获取到的方法
		Object obj = c.newInstance();
		//Object invoke(Object obj, Object...o)
		method.invoke(obj);
	}
}

 

总结:

  • 获取Class:
  1. Class c = new 类名().getClass();
  2. Class c = 类.class;
  3. Class c = Class.forName("类全名");
  • 获取构造方法Constructor:getConstructor、getConstructors、getDeclaredConstructor
  • 获取成员变量 Field:getField、getDeclaredField
  • 获取成员方法Method:getMethod、getDeclaredMethod

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值