Java高级知识 — 反射


参考: Java基础篇:反射机制详解

一、概念及用途

1.1 定义

反射库(reflection library)提供一个非常丰富且精心设计的工具集,以便编写能够动
态操纵Java代码的程序,这项功能被大量地应用于JavaBeans中。
能够分析类能力的程序称为反射(reflective)。

反射是一种功能强大且复杂的机制。使用它的主要人员是工具构造者,而不是应用程序
员。

1.2 用途

反射机制的功能极其强大,在下面可以看
到,反射机制可以用来:

  • 在运行中分析类的能力。例如,实现反编译功能:.class–>.java。
  • 在运行中査看对象。例如,编写一个toString方法供所有类使用。
    反射最重要的用途就是开发各种通用框架。比如很多框架(Spring)都是配置化的(比如通过XML文件配置Bean),为了保证框架的通用性,他们可能需要根据配置文件加载不同的类或者对象,调用不同的方法,这个时候就必须使用到反射了,运行时动态加载需要的加载的对象。
  • 实现通用的数组操作代码。
  • 利用Method对象,这个对象很像C++中的函数指针。

1.3 优缺点

1、优点:使用反射,我们就可以在运行时获得类的各种内容,进行反编译,对于Java这种先编译再运行的语言,能够让我们很方便的创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象。
2、缺点:
1)反射会消耗一定的系统资源,因此,如果不需要动态地创建一个对象,那么就不需要用反射;
2)反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。

二、反射原理

下图是类的正常加载过程,反射原理与class对象:
Class对象的由来是将class文件读入内存,并为之创建一个Class对象。
在这里插入图片描述

二、反射相关类

类名用途
Class类代表类的实体,在运行的Java应用程序中表示类和接口
Field类代表类的成员变量(成员变量也称为类的属性)
Method类代表类的方法
Constructor类代表类的构造方法
Modifier类提供了 static 方法和常量,对类和成员访问修饰符进行解码

参考:
Java高级特性——反射
Java基础——Modifier类

三、反射的基本使用

3.1 获得Class的三种方法

1)Object–>getClass
2)任何数据类型(包括基本的数据类型)都有一个“静态”的class属性
3)通过class类的静态方法:forName(String className)(最常用)
 如果类名保存在字符串中,并可运行中改变,就可以使用这个方法。当然,这个方法
只有在className是类名或接口名时才能执行,否则,forName方法将抛出一个checked exception (已检査异常)。使用这个方法时都应该提供一个异常处理器(exception
 handler)。

package fanshe;
public class Fanshe {
	public static void main(String[] args) {
		//第一种方式获取Class对象  
		Student stu1 = new Student();//这一new 产生一个Student对象,一个Class对象。
		Class stuClass = stu1.getClass();//获取Class对象
		System.out.println(stuClass.getName());
		
		//第二种方式获取Class对象
		Class stuClass2 = Student.class;
		System.out.println(stuClass == stuClass2);//判断第一种方式获取的Class对象和第二种方式获取的是否是同一个
		
		//第三种方式获取Class对象
		try {
			Class stuClass3 = Class.forName("fanshe.Student");//注意此字符串必须是真实路径,就是带包名的类路径,包名.类名
			System.out.println(stuClass3 == stuClass2);//判断三种方式是否获取的是同一个Class对象
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}
}

注意,在运行期间一个类只有一个Class对象产生,所以打印结果都是true;

三种方式中,常用第三种,第一种对象都有了还要反射干什么,第二种需要导入类包,依赖太强,不导包就抛编译错误。一般都使用第三种,一个字符串可以传入也可以写在配置文件中等多种方法。

Class c1 = Double[].class;
Class c2 = int.class;
//请注意,一个Class对象实际上表示的是一个类型,而这个类型未必一定是一种类。例如,int不是类,但int.class是一个Class类型的对象。
//c2.forName()返回 int

在这里插入图片描述

3.2 判断是否为某个类的实例

一般的,我们使用instanceof 关键字来判断是否为某个类的实例。同时我们也可以借助反射中Class对象的isInstance()方法来判断时候为某个类的实例,他是一个native方法。

public native boolean isInstance(Object obj);

3.3 创建实例

通过反射来生成对象主要有两种方法:
1)使用Class对象的newInstance()方法来创建Class对象对应类的实例。

Class<?> cl= String.class;
Object obj = cl.newInstance();

2)先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建对象,这种方法可以用指定的构造器构造类的实例。

//获取String的Class对象
Class<?> cl = String.class;
//通过Class对象获取指定的Constructor构造器对象
Constructor constructor = cl.getConstructor(String.class);
//根据构造器创建实例:
Object obj = constructor.newInstance("hello reflection");

3.4 通过反射获取构造方法并使用

/*
 * 通过Class对象可以获取某个类中的:构造方法、成员变量、成员方法;并访问成员;
 * 
 * 1.获取构造方法:
 * 		1).批量的方法:
 * 			public Constructor[] getConstructors():所有"公有的"构造方法
            public Constructor[] getDeclaredConstructors():获取所有的构造方法(包括私有、受保护、默认、公有)
 * 		2).获取单个的方法,并调用:
 * 			public Constructor getConstructor(Class... parameterTypes):获取单个的"公有的"构造方法:
 * 			public Constructor getDeclaredConstructor(Class... parameterTypes):获取"某个构造方法"可以是私有的,或受保护、默认、公有; 		
 * 		3).调用构造方法:
 * 			Constructor-->newInstance(Object... initargs)
 */
public static void main(String[] args) throws Exception {
	//1.加载Class对象
	Class clazz = Class.forName("fanshe.Student");
	
	//2.获取所有公有构造方法
	/**********************所有公有构造方法*********************************/
	Constructor[] conArray = clazz.getConstructors();
	
	/************所有的构造方法(包括:私有、受保护、默认、公有)***************/
	conArray = clazz.getDeclaredConstructors();
	
	/*****************获取公有、无参的构造方法*******************************/
	Constructor con = clazz.getConstructor(null);
	//1> 因为是无参的构造方法所以类型是一个null,不写也可以:这里需要的是一个参数的类型,切记是类型
	//2> 返回的是描述这个无参构造函数的类对象。
	//调用构造方法
	Object obj = con.newInstance();

	/******************获取私有构造方法,并调用*******************************/
	con = clazz.getDeclaredConstructor(char.class);
	//调用构造方法
	con.setAccessible(true);//暴力访问(忽略掉访问修饰符)
	obj = con.newInstance('男');
}

3.5 获取成员变量并调用

import java.lang.reflect.Field;
/*
 * 获取成员变量并调用:
 * 
 * 1.批量的
 * 		1).Field[] getFields():获取所有的"公有字段"
 * 		2).Field[] getDeclaredFields():获取所有字段,包括:私有、受保护、默认、公有;
 * 2.获取单个的:
 * 		1).public Field getField(String fieldName):获取某个"公有的"字段;
 * 		2).public Field getDeclaredField(String fieldName):获取某个字段(可以是私有的)
 * 
 * 	 设置字段的值:
 * 		Field --> public void set(Object obj,Object value):
 * 					参数说明:
 * 					1.obj:要设置的字段所在的对象;
 * 					2.value:要为字段设置的值;
 */
public static void main(String[] args) throws Exception {
	//1.获取Class对象
	Class stuClass = Class.forName("fanshe.field.Student");
	//2.获取字段
	System.out.println("************获取所有公有的字段********************");
	Field[] fieldArray = stuClass.getFields();
	for(Field f : fieldArray){
		System.out.println(f);
	}
	System.out.println("************获取所有的字段(包括私有、受保护、默认的)********************");
	fieldArray = stuClass.getDeclaredFields();
	for(Field f : fieldArray){
		System.out.println(f);
	}
	System.out.println("*************获取公有字段**并调用***********************************");
	Field f = stuClass.getField("name");
	System.out.println(f);
	//获取一个对象
	Object obj = stuClass.getConstructor().newInstance();//产生Student对象--》Student stu = new Student();
	//为字段设置值
	f.set(obj, "刘德华");//为Student对象中的name属性赋值--》stu.name = "刘德华"
	//验证
	Student stu = (Student)obj;
	System.out.println("验证姓名:" + stu.name);
	
	System.out.println("**************获取私有字段****并调用********************************");
	f = stuClass.getDeclaredField("phoneNum");
	System.out.println(f);
	f.setAccessible(true);//暴力反射,解除私有限定
	f.set(obj, "18888889999");
	System.out.println("验证电话:" + stu);
}

3.6 获取成员方法并调用

import java.lang.reflect.Method;
 
/*
 * 获取成员方法并调用:
 * 
 * 1.批量的:
 * 		public Method[] getMethods():获取所有"公有方法";(包含了父类的方法也包含Object类)
 * 		public Method[] getDeclaredMethods():获取所有的成员方法,包括私有的(不包括继承的)
 * 2.获取单个的:
 * 		public Method getMethod(String name,Class<?>... parameterTypes):
 * 					参数:
 * 						name : 方法名;
 * 						Class ... : 形参的Class类型对象
 * 		public Method getDeclaredMethod(String name,Class<?>... parameterTypes)
 * 
 * 	 调用方法:
 * 		Method --> public Object invoke(Object obj,Object... args):
 * 					参数说明:
 * 					obj : 要调用方法的对象;
 * 					args:调用方式时所传递的实参;
):
 */
public static void main(String[] args) throws Exception {
	//1.获取Class对象
	Class stuClass = Class.forName("fanshe.method.Student");
	//2.获取所有公有方法
	System.out.println("***************获取所有的”公有“方法*******************");
	stuClass.getMethods();
	Method[] methodArray = stuClass.getMethods();
	for(Method m : methodArray){
		System.out.println(m);
	}
	System.out.println("***************获取所有的方法,包括私有的*******************");
	methodArray = stuClass.getDeclaredMethods();
	for(Method m : methodArray){
		System.out.println(m);
	}
	System.out.println("***************获取公有的show1()方法*******************");
	Method m = stuClass.getMethod("show1", String.class);
	System.out.println(m);
	//实例化一个Student对象
	Object obj = stuClass.getConstructor().newInstance();
	m.invoke(obj, "刘德华");
	
	System.out.println("***************获取私有的show4()方法******************");
	m = stuClass.getDeclaredMethod("show4", int.class);
	System.out.println(m);
	m.setAccessible(true);//解除私有限定
	Object result = m.invoke(obj, 20);//需要两个参数,一个是要调用的对象(获取有反射),一个是实参
	System.out.println("返回值:" + result);	
}

3.7 反射main方法

import java.lang.reflect.Method;
 
/**
 * 获取Student类的main方法、不要与当前的main方法搞混了
 */
public class Main {
	public static void main(String[] args) {
		try {
			//1、获取Student对象的字节码
			Class clazz = Class.forName("fanshe.main.Student");
			
			//2、获取main方法
			Method methodMain = clazz.getMethod("main", String[].class);//第一个参数:方法名称,第二个参数:方法形参的类型,
			//3、调用main方法
			// methodMain.invoke(null, new String[]{"a","b","c"});
			//第一个参数,对象类型,因为方法是static静态的,所以为null可以,第二个参数是String数组,这里要注意在jdk1.4时是数组,jdk1.5之后是可变参数
			//这里拆的时候将new String[]{"a","b","c"} 拆成3个对象。。。所以需要将它强转。
			methodMain.invoke(null, (Object)new String[]{"a","b","c"});//方式一
			// methodMain.invoke(null, new Object[]{new String[]{"a","b","c"}});//方式二			
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

3.8 利用反射创建数组

数组在Java里是比较特殊的一种类型,它可以赋值给一个Object Reference。

public static void initialArray() throws ClassNotFoundException {
   Class<?> clazz = Class.forName("java.lang.String");
    Object array = Array.newInstance(clazz,25);
    //往数组里添加内容
    Array.set(array,0,"hello");
    Array.set(array,1,"Java");
    //获取某一项的内容
    System.out.println(Array.get(array,1);
}

//使用反射编写泛型数组代码
public static Object goodCopyOf(Object a, int newLength) {
    Class cl = a.getClass();
    if (!cl.isArray()) {
        return null;
    }
    Class componentType = cl.getComponentType();
    int length = Array.getLength(a);
    Object newArray = Array.newInstance(componentType, newLength);
    System.arraycopy(a, 0, newArray, 0, Math.min(length, newLength));
    return newArray;
}






//请注意,这个CopyOf方法可以用来扩展任意类型的数组,而不仅是对象数组。

3.9 其他用法–通过反射运行配置文件内容

//Student类
public class Student {
	public void show(){
		System.out.println("is show()");
	}
}

配置文件以txt文件为例子:

className = cn.fanshe.Student
methodName = show

测试类:

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Properties;
 
/*
 * 我们利用反射和配置文件,可以使:应用程序更新时,对源码无需进行任何修改
 * 我们只需要将新类发送给客户端,并修改配置文件即可
 */
public class Demo {
	public static void main(String[] args) throws Exception {
		//通过反射获取Class对象
		Class stuClass = Class.forName(getValue("className"));//"cn.fanshe.Student"
		//2获取show()方法
		Method m = stuClass.getMethod(getValue("methodName"));//show
		//3.调用show()方法
		m.invoke(stuClass.getConstructor().newInstance());
		
	}
	
	//此方法接收一个key,在配置文件中获取相应的value
	public static String getValue(String key) throws IOException{
		Properties pro = new Properties();//获取配置文件的对象
		FileReader in = new FileReader("pro.txt");//获取输入流
		pro.load(in);//将流加载到配置文件对象中
		in.close();
		return pro.getProperty(key);//返回根据key获取的value值
	}
}

当我们升级上面这个系统时,不再需要Student类,而需要新写一个Student2的类时,这时只需要更改pro.txt的文件内容就可以了,代码就一点不用改动。

//Student2类
public class Student2 {
	public void show2(){
		System.out.println("is show2()");
	}
}

配置文件修改为:

className = cn.fanshe.Student2
methodName = show2

3.10 其他用法–通过反射越过泛型检查

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

import java.lang.reflect.Method;
import java.util.ArrayList;
/*
 * 通过反射越过泛型检查
 * 例如:有一个String泛型的集合,怎样能向这个集合中添加一个Integer类型的值?
 */
public class Demo {
	public static void main(String[] args) throws Exception{
		ArrayList<String> strList = new ArrayList<>();
		strList.add("aaa");
		strList.add("bbb");
		
		//	strList.add(100);
		//获取ArrayList的Class对象,反向的调用add()方法,添加数据
		Class listClass = strList.getClass(); //得到 strList 对象的字节码 对象
		//获取add()方法
		Method m = listClass.getMethod("add", Object.class);
		//调用add()方法
		m.invoke(strList, 100);
		
		//遍历集合
		for(Object obj : strList){
			System.out.println(obj);
		}
	}
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值