Java基础学习总结--反射

一、什么是反射?

​ JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

二、能利用反射做什么?

​ 我们知道反射机制允许程序在运行时取得任何一个已知名称的class的内部信息,包括包括其constructor(构造方法),fields(属性),methods(方法)等,并可于运行时改变fields内容或调用methods。那么我们便可以更灵活的编写代码,代码可以在运行时装配,无需在组件之间进行源代码链接,降低代码的耦合度;还有动态代理的实现等等;但是需要注意的是反射使用不当会造成很高的资源消耗!

三、前期了解知识

(一)类的加载

​ 想知道如何使用反射,就先要了解类的加载。

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

  1. 加载
    就是指将class文件读入内存,并为之创建一个Class对象。
    任何类被使用时系统都会建立一个Class对象。
  2. 连接
    验证 是否有正确的内部结构,并和其他类协调一致
    准备 负责为类的静态成员分配内存,并设置默认初始化值
    解析 将类的二进制数据中的符号引用替换为直接引用
  3. 初始化 基本的的初始化步骤

(二)类加载器

  1. 类加载器:负责将.class文件加载到内在中,并为之生成对应的Class对象。
    虽然我们不需要关心类加载机制,但是了解这个机制我们就能更好的理解程序的运行。

  2. 类加载器的组成
    Bootstrap ClassLoader 根类加载器
    Extension ClassLoader 扩展类加载器
    Sysetm ClassLoader 系统类加载器

  3. 类加载器的作用
    (1) Bootstrap ClassLoader 根类加载器,也被称为引导类加载器。负责Java核心类的加载
    比如System,String等。在JDK中JRE的lib目录下rt.jar文件中

    (2)Extension ClassLoader 扩展类加载器。负责JRE的扩展目录中jar包的加载。
    在JDK中JRE的lib目录下ext目录
    (3)Sysetm ClassLoader 系统类加载器。负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所 指定的jar包和类路径

四、如何使用反射?

现有一个Person类

public class Person {
	
	private String name;
	int age;
	public String address;

	public Person() {
	}

	private Person(String name) {
		this.name = name;
	}

	Person(String name, int age) {
		this.name = name;
		this.age = age;
	}

	public Person(String name, int age, String address) {
		this.name = name;
		this.age = age;
		this.address = address;
	}

	public void show() {
		System.out.println("show");
	}

	public void method(String s) {
		System.out.println("method " + s);
	}

	public String getString(String s, int i) {
		return s + "-->" + i;
	}

	private void function() {
		System.out.println("function");
	}

	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + ", address=" + address
				+ "]";
	}
}

那么对于这个类,如何使用反射呢?

(一)获取该类的字节码文件在内存中对应的Class对象

/**
 *有三种方式可以获得Class对象
 */
//1、通过对象调用 getClass() 方法来获取,通常应用在:比如你传过来一个 Object
//  类型的对象,而我不知道你具体是什么类,用这种方法
  Person p1 = new Person();
  Class c1 = p1.getClass();
        
//2、直接通过 类名.class 的方式得到,该方法最为安全可靠,程序性能更高
//  这说明任何一个类都有一个隐含的静态成员变量 class
  Class c2 = Person.class;
        
//3、通过 Class 对象的 forName() 静态方法来获取,用的最多,
//   但可能抛出 ClassNotFoundException 异常
  Class c3 = Class.forName("com.ys.reflex.Person");

注意:一个类在 JVM 中只会有一个 Class 实例,即我们对上面获取的 c1,c2,c3进行 == 比较,结果都是true

(二)通过Class类提供的方法获取构造方法

  1. 获取构造方法
/**
 *反射之获取无参构造方法,并使用
 */
public class TestConstructor01 {
	public static void main(String[] args) throws Exception {
		//获取Class对象
		Class c = Class.forName("study01.Person");
		
        //获取无参构造函数
		Constructor con = c.getDeclaredConstructor();	
	
		//创建实例
		Object obj = con.newInstance();
	}
}
/**
* 如果只需要利用无参构造来创建实例,那么也可以不用获取构造器,直接使用newInstance方法
*/
public class TestConstructor01 {
	public static void main(String[] args) throws Exception {
		Class c = Class.forName("com.zuobiao.testReflection.Person");
		Person p = (Person)c.newInstance();
	}
}
/**
 *反射之获取带参构造方法,并使用
 */
public class TestConstructor02 {
	public static void main(String[] args) throws Exception {
		//获取Class对象
		Class c = Class.forName("study01.Person");
		
		//public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
		//获取构造方法
		Constructor con = c.getDeclaredConstructor(String.class, int.class, String.class);
		
		//创建实例
		Object obj = con.newInstance("张三", 27, "北京");
	}
}
/**
 *反射之获取private修饰的构造方法,并使用
 */
public class TestConstructor03 {
	public static void main(String[] args) throws Exception{
		//获取Class对象
		Class c = Class.forName("study01.Person");
		
		//获取构造方法
		Constructor con = c.getDeclaredConstructor(String.class);
		
		//设置访问权限
		con.setAccessible(true);	//值为true则指示反射的对象在使用时应该取消Java语言访问检查。
		
		//创建实例
		Object obj = con.newInstance("林青霞");
	}
}

(三)通过Class类提供的方法操作成员变量

  1. 给成员变量赋值
public class TestField01 {
	public static void main(String[] args) throws Exception{
		test();
	}

	public static void test() throws Exception {
		//获取Class对象
		Class c = Class.forName("study01.Person");
		
		//获取Constructor
		Constructor con = c.getDeclaredConstructor();
		
		//创建实例
		Object obj = con.newInstance();
		System.out.println(obj);
		
		//获取Field
		Field addressField = c.getDeclaredField("address");
		//赋值Field
		addressField.setAccessible(true);
		addressField.set(obj, "北京");
		System.out.println(obj);
		
		Field nameField = c.getDeclaredField("name");
		nameField.setAccessible(true);  	
		nameField.set(obj, "张三");
		System.out.println(obj);
		
		Field ageField = c.getDeclaredField("age");
		ageField.setAccessible(true);
		ageField.set(obj, 27);
		System.out.println(obj);
	}
}

测试结果如下:

  1. 获取成员变量
public class TestField02 {
	public static void main(String[] args) throws Exception{
		test();
	}

	public static void test() throws Exception {
		//获取Class对象
		Class c = Class.forName("study01.Person");
		
		//获取Constructor
		Constructor con = c.getDeclaredConstructor();
		
		//创建实例
		Object obj = con.newInstance();
		System.out.println(obj);
        
		//获取Field
		Field addressField = c.getDeclaredField("address");
		//赋值Field
		addressField.setAccessible(true);
		addressField.set(obj, "北京");
		System.out.println(obj);
		System.out.println(addressField.get(obj));
	}
}	

测试结果如下:

(四) 通过Class类提供的方法调用成员方法

/**
 * 反射之获取无参无返回值成员方法,并调用
 */
public class TestMethod01 {
	public static void main(String[] args) throws Exception {
		test0();
	}
	
	//获取单个方法对象
    /**
    * 1、public 方法 getMethod(String name, Class<?>... parameterTypes):获取所有方法,包括父类的
    * 参数 :	name:方法的名称  parameterTypes:该方法的参数列表 
    * 2、public 方法 getDeclaredMethod(String name, Class<?>... parameterTypes):获取所有子类定义的方法
    * 参数 :	name:方法的名称  parameterTypes:该方法的参数列表 
    */	
	public static void test() throws Exception {
		//获取Class对象
		Class c = Class.forName("study01.Person");
		
		//获取构造方法
		Constructor con = c.getDeclaredConstructor();
		
		//创建实例
		Object obj = con.newInstance();
		
		//获取需要的方法对象
	
		Method m = c.getDeclaredMethod("show");
		
		//调用方法
		/**
		 * public Object invoke(Object obj, Object... args)
		 * obj - 被调用的对象  args - 用于方法调用的参数
		 */
		m.invoke(obj);
	}
}

测试结果如下:

/**
 * 反射之获取有参有返回值成员方法,并调用
 */
public class TestMethod02 {
	public static void main(String[] args) throws Exception {
		//获取Class对象
		Class c = Class.forName("study01.Person");
		
		//获取构造方法
		Constructor con = c.getDeclaredConstructor();
		
		//创建实例
		Object obj = con.newInstance();
		
		//获取需要的方法对象
		//public void method(String s)
		Method m = c.getDeclaredMethod("method", String.class);
		m.invoke(obj, "java");
		
		//public String getString(String s, int i) 
		Method m1 = c.getDeclaredMethod("getString", String.class, int.class);
		Object rst = m1.invoke(obj, "hello", 200);
		System.out.println(rst);
	}
}

测试结果如下:

/**
 * 反射之获取privste成员方法,并调用
 */
public class TestMethod02 {
	public static void main(String[] args) throws Exception {
		//获取Class对象
		Class c = Class.forName("study01.Person");
		
		//获取构造方法
		Constructor con = c.getDeclaredConstructor();
		
		//创建实例
		Object obj = con.newInstance();
		
		//获取需要的方法对象
		//private void function()
		Method m2 = c.getDeclaredMethod("function");
		m2.setAccessible(true);
		m2.invoke(obj);
	}
}

测试结果如下:

五、补充

当使用反射调用静态变量静态方法时,传递的对象出只需要传递null即可。

六、总结

灵活使用反射能让我们代码更加灵活,但是凡事都有两面性,反射也会消耗系统的性能,增加复杂性等,合理使用才是真!

Java新手,若有错误,欢迎指正!

posted @ 2020-03-28 21:45  跑调大叔!  阅读(378)  评论(0编辑  收藏  举报