Java反射机制详解

本文详细介绍了Java反射机制,包括什么是反射、反射的作用,以及如何加载类、获取构造方法、成员变量、成员方法和配置文件信息。此外,还探讨了如何利用反射越过泛型检查,展示了反射在程序灵活性和配置更新操作中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

什么是反射

一般我们获取类的信息是去new一个对象,然后查看、修改它的属性或者运行它的方法。而反射不需要创建对象,就可以单独获取类的构造方法、成员变量、成员方法、main方法等信息,这些操作是通过Class对象完成的,Class类只有一个,其中包含了所有类的信息,当一个类被加载的时候,就会自动为这个类创建一个Class对象,通过这个对象,我们可以得到类的所有信息。

用反射可以干什么

1、获取一个类的所有信息。
2、获取一个接口的常量和方法声明。
3、在程序运行中加载某些类并执行它的方法。
4、在程序运行中创建一些在程序运行期间才知道名字的类的实例。
5、在程序运行期间获取或修改一些对象的成员变量。
6、在程序运行期间检测或执行一些对象的方法。
7、跳过了对象的创建环节,增加了程序的灵活性。
8、利用反射完成一些软件的配置更新操作。

加载类的几种方法

1、代码

	public void getclass()
	{
		//第一种方式获取Class对象
		Student st =  new Student();  //同时产生st对象和class对象
		Class<?> stClass1=st.getClass();  //获取Class 对象
		System.out.println(stClass1.getName()); 			
		//第二种,常用,字符串可以传入也可以写在配置文件中
		try {
			Class<?> stClass2=Class.forName("com.myf.reflect1229.Student");
			System.out.println(stClass2==stClass1);
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}		
		//第三种
		Class<?> stClass3=Student.class;
		System.out.println(stClass3==stClass1);
	}

2、运行结果

调用了公有,无参构造方法
com.myf.reflect1229.Student
true
true

3、分析:第一种方式需要先创建类的对象,然后通过对象去获取Class类,这并没有体现出反射的优点,我们一般不采用这种方法;第二种方式是最常用的,我们通过一段关于类的位置信息的字符串来加载类,获取Class对象,这样比较直接,也很方便;第三种方式是通过类来获取,虽然少了对象的创建步骤,但还是比不上第二种方法对Class对象的直接获取来的方便。所以,我们一般通过类的位置来加载类,获取Class对象。以上三种方法获取的Class对象是同一个对象。

建一个Student类

Student类包括不同访问修饰符修饰的成员变量,成员方法,构造方法,以便之后运用反射机制来操作这个类。

public class Student {
	 public String name;
	 private char gender;
	 protected int age;
	 String number;
	 //默认构造方法
	 Student(String str)
	 {
		 System.out.println("默认构造方法:s="+str);
	 }
	 //无参构造方法
	 public Student()
	 {
		 System.out.println("调用了公有,无参构造方法");
	 }	 
	 //有一个参数的构造方法
	 public Student(char gender)
	 {
		 System.out.println("性别:"+gender);
		 this.gender=gender;
	 }	 
	 //有多个参数的构造方法
	 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="+age);
	 }	
	 public String toString()
	 {
		return "name:"+name+"gender:"+gender+"age:"+age+"number:"+number;
	 }	
	 /**
	  * 公有的方法
	  * @param s
	  */
	 public void show1(String s)
	 {
		 System.out.println("调用了公有的,String参数的方法show1(),s="+s);
	 }	 
	 /**
	  * 受保护的方法
	  */
	 protected void show2()
	 {
		 System.out.println("调用了受保护的方法show2()");
	 }	 
	 /**
	  * 私有的,int参数的,有返回值的方法
	  */
	 private String show3(int age)
	 {
		 System.out.println("调用了私有的,int参数的,有返回值的方法show3()");
		 this.age=age;
		 return "123";
	 }	 
	/**
	 * 默认无参无返回值的方法
	 */
	 void show4()
	 {
		 System.out.println("默认无参,无返回值的方法");
	 }    	 
}

利用反射获取构造方法

1、代码

	/**
	 * 获取构造方法并使用
	 * @throws Exception
	 */
	private void getMethod() throws Exception
	{	
			Class<?> stClass= Class.forName("com.myf.reflect1229.Student");  //加载类
			Constructor<?>[] con = stClass.getConstructors();  //获取所有公共构造方法
			System.out.println("获取公有构造方法。。。。。。。");
			for(Constructor<?> c : con)
			{
				System.out.println(c);
			}			
			Constructor<?>[] con1 = stClass.getDeclaredConstructors();  //获取所有构造方法
			System.out.println("获取类申明的所有构造方法。。。。。。。");
			for(Constructor<?> c : con1)
			{
				System.out.println(c);
			}		
		    Constructor<?> con2=stClass.getConstructor(); //获取指定公共构造方法,获取无参构造方法
		    System.out.println("获取公有,无参构造方法。。。。。。");
		    System.out.println(con2);		     		    
		    System.out.println("获取指定构造方法。。。。。");
		    Constructor<?> con3 = stClass.getDeclaredConstructor(char.class);//以入口参数类型为筛选
		    System.out.println(con3);		    
//		    con3.setAccessible(true);  //暴力访问,忽略访问修饰符,获取访问权限
		    Object obj=con3.newInstance('男');  //使用该构造方法创建类的对象实例,此时方法表中只有obj的方法
		    Student st=(Student)obj; //创建时向上转型得到obj,再向下转型得到st
		    System.out.println("性别:"+st);//验证
	}

2、运行结果

获取公有构造方法。。。。。。。
public com.myf.reflect1229.Student(java.lang.String,int)
public com.myf.reflect1229.Student()
public com.myf.reflect1229.Student(char)
获取类申明的所有构造方法。。。。。。。
private com.myf.reflect1229.Student(int)
protected com.myf.reflect1229.Student(boolean)
public com.myf.reflect1229.Student(java.lang.String,int)
com.myf.reflect1229.Student(java.lang.String)
public com.myf.reflect1229.Student()
public com.myf.reflect1229.Student(char)
获取公有,无参构造方法。。。。。。
public com.myf.reflect1229.Student()
获取指定构造方法。。。。。
public com.myf.reflect1229.Student(char)
性别:男
性别:name:nullgender:男age:0number:null

3、分析
代码中主要涉及的内容就是对Class类的一些方法的应用。这里用到了四个方法:
①Constructor<?>[] con = stClass.getConstructors();是获取公共构造方法
②Constructor<?>[] con1 = stClass.getDeclaredConstructors();是获取所有构造方法
③Constructor<?> con2=stClass.getConstructor();是获取指定的公共构造方法
④Constructor<?> con3 = stClass.getDeclaredConstructor(char.class);是获取指定的构造方法
末尾带“s”的方法一般返回的都是一个构造方法的数组;不带“s”的一般是返回某个指定的构造方法;公有构造方法和所有构造方法的区分就是看有没有“Declared”了。返回构造方法后我们就可以用构造方法来获取对象了,我们把获取的构造方法看做是Constructor类的对象,调用它的newInstance()方法可以得到当前类的对象。需要注意的是返回的对象是Object类的对象(Object类是所有类的超类),这里是自动地向上转型了,我们此时不能直接使用这个对象来操作类的方法和属性,还需要向下强制转型之后才可以,不然我们得到的方法表只会是Object类的方法表。暴力访问的方法此处还没有用到,我们在下面的程序中介绍。

利用反射获取成员变量

原理和获取构造方法类似,我们直接看代码:
1、代码

	/**
	 * 获取成员变量并使用
	 */
	private void getValue() throws Exception
	{
		Class<?> stClass=Class.forName("com.myf.reflect1229.Student");  //加载类
		System.out.println("获取所有可访问的成员字段。。。。。");
		Field[] fields=stClass.getFields();  
		for(Field f : fields)
		{
			System.out.println(f);
		}
		System.out.println("获取所有的字段。。。。。");
		Field[] fields1=stClass.getDeclaredFields(); 
		for(Field f : fields1)
		{
			System.out.println(f);
		}
		System.out.println("获取特定公有字段并调用。。。。。");
		Field f = stClass.getField("name");  //以成员变量属性名来筛选
		System.out.println(f);
		Object obj = stClass.getConstructor().newInstance();
		f.set(obj, "李小龙");
		//验证属性
		Student st = (Student)obj; //向下转型
		System.out.println("名字是:"+st.name);		
		System.out.println("获取私有(指定)字段并调用。。。。");
		Field f1 = stClass.getDeclaredField("gender");
		System.out.println(f1);		
		f1.setAccessible(true);  //暴力反射,解除私有限定
		f1.set(st, '男');//解除限定后可以设置
		System.out.println("验证:"+st);
	}

2、运行结果

获取所有可访问的成员字段。。。。。
public java.lang.String com.myf.reflect1229.Student.name
获取所有的字段。。。。。
public java.lang.String com.myf.reflect1229.Student.name
private char com.myf.reflect1229.Student.gender
protected int com.myf.reflect1229.Student.age
java.lang.String com.myf.reflect1229.Student.number
获取特定公有字段并调用。。。。。
public java.lang.String com.myf.reflect1229.Student.name
调用了公有,无参构造方法
名字是:李小龙
获取私有(指定)字段并调用。。。。
private char com.myf.reflect1229.Student.gender
验证:name:李小龙gender:男age:0number:null

3、分析
获取成员变量也是分为获取公有成员变量和所有成员变量两种方式,获取指定字段的思路也和上述一样,成员变量的单词用Filed表示。在选择特定成员变量时,是用成员变量名来筛选,而选择特定构造方法时,是用参数类型.class来筛选,这一点要注意区分。程序中我们需要访问的是private修饰的变量,我们知道,private修饰的变量是不可以被外部访问的,所以我们需要解除对这个成员变量的访问限制,然后就可以对其进行修改操作,设置参数时使用set方法,需要输入对象(obj)和设置内容,最后输出验证。

利用反射获取成员方法

1、代码

	/*
	 * 获取成员方法并调用
	 */
	private void getMemberMethods()throws Exception
	{
		Class<?> stClass = Class.forName("com.myf.reflect1229.Student");
		System.out.println("获取公有的方法。。。。。。");
		Method[] methods= stClass.getMethods(); //获取所有公有方法,包括继承来的,因此输出有object的方法
		for(Method m : methods)
		{
			System.out.println(m);
		}
		System.out.println("获取所有方法。。。。。。。");
		Method[] methods1 = stClass.getDeclaredMethods(); //获取所有方法,不包括继承来的
		for(Method m : methods1)
		{
			System.out.println(m);
		}
		System.out.println("获取指定公有方法。。。。。。。");
		Method methods2 = stClass.getMethod("show1", String.class);//获取指定名称和参数的公有方法 
		System.out.println(methods2);
		System.out.println("获取指定私有方法并调用。。。。。");		
		Method methods3 = stClass.getDeclaredMethod("show3", int.class); //获取私有方法
		System.out.println(methods3);
		methods3.setAccessible(true);   //暴力访问
		Object obj = stClass.getConstructor().newInstance(); //获取对象
		Student st = (Student)obj; //向下转型
		Object obj1 = methods3.invoke(st, 18); //调用方法
		System.out.println("返回值:"+obj1);						
	}

2、运行结果

获取公有的方法。。。。。。
public java.lang.String com.myf.reflect1229.Student.toString()
public void com.myf.reflect1229.Student.show1(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 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()
获取所有方法。。。。。。。
public java.lang.String com.myf.reflect1229.Student.toString()
private java.lang.String com.myf.reflect1229.Student.show3(int)
public void com.myf.reflect1229.Student.show1(java.lang.String)
void com.myf.reflect1229.Student.show4()
protected void com.myf.reflect1229.Student.show2()
获取指定公有方法。。。。。。。
public void com.myf.reflect1229.Student.show1(java.lang.String)
获取指定私有方法并调用。。。。。
private java.lang.String com.myf.reflect1229.Student.show3(int)
调用了公有,无参构造方法
调用了私有的,int参数的,有返回值的方法show3()
返回值:123

3、分析
在筛选方法的时候我们用到的是方法名(字符串表示)和参数类型.class。stClass.getMethods()方法得到的公共构造方法还包括从Object类中继承来的方法。通过构造方法得到对象后使用method对象的invoke方法执行相应方法。对私有方法的访问依然需要解除访问限制。

利用反射获取main方法

我们再次新建一个Test2的类,写一个简单的mian方法。如下:

public class Test2
 {
	public static void main2(String[] args)
	{
		System.out.println("Test2的main方法被执行了");
	}			
}

然后在Test类中加载Test2类,执行Test2类的main方法:

	 /**
	  * 获取Test2的main方法
	  */
	  private void getMain() throws Exception
	  {			
		System.out.println("获取Test2的main方法。。。。。。");
		Class<?> classMain=Class.forName("com.myf.reflect1229.Test2");
		Method method = classMain.getMethod("main2", String[].class);
		System.out.println(method);
	 	method.invoke(null, (Object)new String[]{"a","b","c"});
	  }

运行结果

获取Test2的main方法。。。。。。
public static void com.myf.reflect1229.Test2.main2(java.lang.String[])
Test2的main方法被执行了

分析:和之前介绍的获取成员方法类似,相应参数正确选择即可。

利用反射获取配置文件信息

1、代码

	  /**
	   * 获取配置文件信息
	   */
	  private void getMessage()
	  {
		  try
		  {
			 System.out.println("执行配置文件指定的方法");
		     Class<?> stClass=Class.forName(KeyToValue("className"));
		     Method method = stClass.getMethod(KeyToValue("method"), String.class);
		     System.out.println(method);
		     Object obj = stClass.getConstructor().newInstance(); //创建对象
		     method.invoke(obj, "asdf");
		  }
		  catch(Exception e)
		  {
			  e.printStackTrace();
		  }
	  }	 
	  /**
	   * 根据键值对获取配置文件中的数据
	   * @param key
	   */
	  private String KeyToValue(String key)
	  {
		  String value="";
		  try
		  {
			  Properties pro = new Properties();//获取配置文件对象
			  InputStream in = new FileInputStream(new File("E:\\workspace\\mayifan\\src\\com\\myf\\reflect1229\\data.txt"));//从文件获取输入流		  
			  pro.load(in);   //配置文件加载输入流,获取其中的键值对
			  value=pro.getProperty(key);
		  }
		  catch(IOException e)
		  {
			  e.printStackTrace();
		  }
		  return value;
	  }

2、运行结果

执行配置文件指定的方法
public void com.myf.reflect1229.Student.show1(java.lang.String)
调用了公有,无参构造方法
调用了公有的,String参数的方法show1(),s=asdf

3、分析
在java中,利用反射来实现软件的配置更新非常重要,比如在一次更新时,服务器只要把改动后的类发给客户端,然后更换配置文件,在执行java程序的时候会自动去获取配置文件中的更新信息,从而实现对程序的改动,这对于程序的维护是十分方便的。那么如何去获取配置文件的信息呢?这里引入一个Properties类,这个类表示一个属性集,我们用它来加载和操作配置文件中的属性信息。我们一般采用txt文档作为配置文件,里面的信息采用键值对的方式来存储:a=b。读取文件需要用到IO流的知识,我采用的思路是通过文件输入流的构造方法创建一个字节输入流的对象(这里用到了自动转型),然后用Propertise的对象来加载输入流,获取信息,之后对这些信息做get或者set操作即可。我们这里读取了类的地址,和希望调用的方法名,把这些信息送到程序中执行。

利用反射越过泛型检查

1、代码

	  /**
	   * 反射越过泛型检查
	   */
	  private void crossCheck() throws Exception
	  {
		  System.out.println("反射越过泛型检查。。。。。");
		  ArrayList<String> array = new ArrayList<>();
		  array.add("ss");
		  array.add("e");
		  array.add("C");
		  Class<?> arrClass = array.getClass();
		  Method method = arrClass.getMethod("add", Object.class);
		  System.out.println(method);
		  method.invoke(array, 50);
		  method.invoke(array, 'a');
		  for(Object obj1 : array)
		  {
			  System.out.println(obj1);
		  }
	  }

2、运行结果

反射越过泛型检查。。。。。
public boolean java.util.ArrayList.add(java.lang.Object)
ss
e
C
50
a

3、分析
在java中对泛型的检查是在程序的编译期执行的,即在编译期通过对程序的编译就确定了参数的类型,如果没有错误,就不会报错。而类的加载机制是在程序运行期执行的,可以越过泛型检查,拿常用的ArrayList为例,我们通过反射可以为其输入不同类型的参数。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值