Java中关于反射的基础详解。

本文是根据自己最近学习反射所做的总结,因为本身学的不深入,所以就做一些基础的总结,尽量详细。

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

简单的来说,反射机制指的是程序在运行时能够获取自身信息。在JAVA中,只要给定类的名字,那么就可以通过反射机制来获得类的所有信息。包括访问修饰符、父类、实现的接口、属性和方法的所有信息,并可在运行时创建对象、修改属性(包括私有的)、调用方法(包括私有的)。

那么为什么要用反射机制?直接创建对象不就可以了吗?这就涉及到了动态与静态的概念。

在这里假设一下场景,等一下再说动态静态的概念。

比如,你和同学正在开发一个项目,你们做不同的部分,但是你要用到的学生类里面的方法,是你同学负责的部分,你同学还没把这部分代码写好,这就耽误了你的正常使用,但是你又不能停止工作,这个时候你就可以用反射来获取学生类的一切信息。利用反射创建学生对象,调用学生类里面的方法等等。 (这里是我自己假想的,不是很确定正确与否,如果有人看到,这里有问题麻烦在下面留言告知。谢谢。)

继续说动态与静态。

静态编译:在编译的时候确定类型,绑定对象,即通过。就是如果是static修饰的东西,在编译的时候就分配了内存。

动态编译:运行时候确定类型,绑定对象。动态编译最大限度发挥了Java的灵活性,体现了多态的应用,用以降低类之间的耦合性。

如static int a=10;在编译的时候,就为a这个变量分配了内存。

Student stu=new Student(“zhangsan0”,30);这句在运行的时候才会new出来stu这个实例,才会分配内存。

一句话,反射机制的优点就是可以实现动态创建对象和编译,体现出很大的灵活性,特别是在J2EE的开发当中。

它的缺点是对性能有影响,使用反射基本上是一种解释操作,这类操作总是慢于直接执行的相同操作。反射功能强大,但是耗时,多用来做工具和框架。

下面开始结合代码来说解释。

1 利用反射机制获取一个类的Class对象的方法  有三种  如下

// ---获取Employee这个类所对应的Class对象
//第一种方法通过  对象名点getClass()
Employee emp=new Employee("zhangsna", 18);
Class<T> classtype=emp.getClass();
System.out.println(classtype.getName());
//第二种方法  类名点class
Class<T> classtype=Employee.class;
System.out.println(classtype.getName());
//第三种方法
//使用Class.Forname() 来获取一个类所对应的Class对象
Class<T> classtype=Class.forName("com.classtype.Employee");//这里括号里面填的是 报名点类名  (也叫全限定名)
System.out.println(classtype.getName());

这里我们可以看出来,第一种方法,是new 出来emp对象了,既然能new出来,就可以直接用对象访问类中的任何方法了。第一种方法也没有什么必要了。

我们都知道,要创建对象,就要有构造方法,下面我们利用反射机制获取一个类的构造方法

获取构造方法有四种手段,调用Class类中的下面四个方法:

getConstructor()//可以传参数,返回指定public修饰的构造方法

getConstructors()//返回一个列表,  列表里面是所有的public修饰的构造方法

getDeclaredConstructor()//可以传参数,返回指定构造方法,与权限无关

getDeclaredConstructors()//返回一个列表,列表里面是所有的构造方法,与权限无关

package com.constructor;

import java.lang.reflect.Constructor;

//使用反射获取Person111中所有的构造器
public class ConstructorDemo {

	public static void main(String[] args) throws NoSuchMethodException, SecurityException, ClassNotFoundException {
		getAllConstructor();
		getOneConstructor();
	}
	//获取指定的一个构造方法
	private static void getOneConstructor() throws NoSuchMethodException, SecurityException{
		//第一步先获取一个类的字节码文件
		Class clz=Person111.class;
		//调取指定构造器的方法即可
		//如:想获取public Person111(String name){};
		//应该告诉程序,我想获取的是带有一个String类型的参数的构造器
		Constructor cs = clz.getConstructor(String.class);
		System.out.println(cs);
		//如果获取private Person111(String name,int age){}
		//并不是Constructor cs = clz.getConstructor(String.class,int.class);
		//方法不带Declared永远只能获取公共的构造器
		Constructor cs1 = clz.getDeclaredConstructor(String.class,int.class);
		System.out.println(cs1);
	}
	//获取所有构造方法
	private static void getAllConstructor(){
		//找到Person111这个类的这份字节码
		Class<Person111> clz = Person111.class;
		//获取构造器
		//Class中public Constructor<?>[] getConstrutors()  
		Constructor[] cs=clz.getConstructors();//这只能获取public修饰的构造器
		for (Constructor c : cs) { 
			System.out.println(c+"我是public修饰的构造方法");//输出两个结果。就是两个public修饰的
		}
		
		System.out.println("=========================");
		cs=clz.getDeclaredConstructors();//这个方法可以获取所有构造器  与权限无关
		for (Constructor c : cs) { 
			System.out.println(c+"我是构造方法,与权限无关");
		}
	}
}
class Person111{
	public Person111(){};
	public Person111(String name){};
	private Person111(String name,int age){};
}
有了构造方法,我们就可以创建实例了。我们都知道,new一个实例的时候,会调用构造方法。

那么我们通过调用构造方法就可以创建实例。

package com.constructor;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/*通过调用构造方法创建实例
 * 
 * */
public class ConstructorInvokeDemo {

	public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, ClassNotFoundException {
		//传统方法
		//new Person111();
		//使用反射来调用
		//疑惑:  Person111.class和Class.forName();??
		//这里Person111.class其实就是Class.forName("全限定名");
//		System.out.println(clz);
//		System.out.println(Class.forName("com.constructor.Person111"));
		
		
		//1.先找到被调用的构造方法所在类的字节码
		Class<Person111> clz = Person111.class;
		//2.找到被调用的指定的构造器
		Constructor<Person111> cs = clz.getConstructor();
		//3.执行该构造器
		cs.newInstance();//无参的
		ClassLoader c = clz.getClassLoader();
	
		//如果一个类中的构造器可以直接访问,同时没有参数,可以直接调用Class中的newInstance方法
		//不用第二步了
		clz.newInstance();
		
		//调用带参数的构造器
		Constructor<Person111> cs1 = clz.getConstructor(String.class);
		
		//调用私有的构造器
		 cs1 = clz.getDeclaredConstructor(String.class,int.class);
		 System.out.println(cs1);
		 //cs1.newInstance("lisi",78);//这里会报错,因为这个构造方法是私有的
		 //此时就要告诉程序在运行的时候忽略安全检查。或者设置该私有的构造器可访问
		 cs1.setAccessible(true);//设置可访问  OK了
		 cs1.newInstance("lisi",45);
	}
}
class Person111{
	public Person111(){
		System.out.println("无参");
	}
	public User(String name){
		System.out.println("1个参");
	}
	private User(String name,int age){
		System.out.println("2个参");
	}
}
我们也可以获取其他的方法(非构造方法)

也是有四种手段,调用Class类中的下面四个方法:

getMethod()//可以传参数,获取指定的方法

getMethods()//获取所有public修饰的方法  包括自身的和继承过来的   返回的是一个列表

getDeclaredMethod()//可以传参数,获取指定的方法,与权限无关

getDeclaredMethods()//获取所有的方法,不包括继承过来的方法,与权限无关。  返回的是一个列表

package com.method;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Scanner;

/*获取Person111类中的方法
 * 
 * */
public class MethodDemo {

	public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, ClassNotFoundException {
		//传统方法
		//Person111 Person111=new Person111();
		//Person111.Person111name("zhangsan");
		
		//使用反射来获取
		//1.先找到被调用的构造器所在类的字节码
		Class<Person111> clz = Person111.class;
		//2.找到被调用的方法
		Method[] cs = clz.getMethods();
		for (Method method : cs) {
			System.out.println(method);//获取所有公共方法   
			//包括自身和继承过来的所有的public方法。
		}
		//只获取本类的方法  要用getDeclaredMethods()  和访问权限无关
		//不包括继承来的方法
		cs=clz.getDeclaredMethods();
		for (Method method : cs) {
			System.out.println(method);//获取本类所有方法,与访问权限无关		
		}
	}
	//获取指定的一个方法
	private static void getOneMethod() throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{
		//第一步  获取字节码文件
		Class<Person111> clz = Person111.class;
		//第二步  找到指定的方法
		//只有通过方法签名才能找到唯一的方法
		//方法签名=法名+参数列表(参数类型,参数个数,参数顺序)
		Method cs = clz.getMethod("Person111name", String.class);
		System.out.println(cs);//第一个参数就是方法的名字,后面是参数的类型
		//这里说一下,获取方法与获取构造方法不同的是,获取构造器不用传方法名字,因为构造方法名字与类名字一样。
		//没有必要传入构造方法。
		
		//获取私有方法
		cs=clz.getDeclaredMethod("Person111nameage", String.class,int.class);
		System.out.println(cs);		
	}
}
class Person111{
	public void Person111(){
		System.out.println("无参");
	}
	public void Person111name(String name){
		System.out.println("1个参");
	}
	private void Person111nameage(String name,int age){
		System.out.println("2个参");
	}
}

获取完方法,下面我们利用反射机制来调用类中的方法。

package com.method;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/*
 * 利用反射动态调用方法
 * */
public class MethodInvoke {
	public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException 
	{
		//调用Personfangfa0()方法
		
		//第一步  获取方法所在类的字节码文件
		Class<Person> clz = Person.class;
		//第二步获取要调用的方法
		Method cs = clz.getMethod("Personfangfa1",String.class);
		//在method类中有方法
		//invoke(obj, args)表示调用当前method所表示的方法)
		/*obj:表示被调用方法底层所属对象                重点
		 *args:表示调用方法时传递的实际参数
		 *返回 底层方法的返回结果
		 * */
		//第三步  利用反射创建对象实例
		Object obj = clz.newInstance();
		 Object s = cs.invoke(obj,"zhagsan");//s为接受该方法的返回值
		 //这里的参数  上面已经说明了。
		 System.out.println(s);
		 
		 
		 
		 //调用私有方法
		 System.out.println("***************************");
		 cs=clz.getDeclaredMethod("Personfangfa2", String.class,int.class);
		 
		 //调用之前设置可访问权限 
		 cs.setAccessible(true);
		 cs.invoke(clz.newInstance(), "lisi",12);
	}
}
class Person{
	public  void Personfangfa0(){
		System.out.println("无参");
	}
	public String Personfangfa1(String name){
		System.out.println("1个参");
		return name;
	}
	private void Personfangfa2(String name,int age){
		System.out.println("2个参");
	}
}
利用反射调用类中的静态方法与上面方法不一样。

package com.method;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/*使用反射调用静态方法
 * */
public class StaticMethodInvokeDemo {

	public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException {
		//第一步  获取静态方法所属类的字节码文件
		Class clz=StaticMethodInvokeDemo.class;
		//第二步  获取要调用的静态方法
		Method cs = clz.getMethod("statimethod");//没有参数,不用写
		//cs.invoke(clz.newInstance());//通用方法调用,同样,无参数。
		//静态方法不属于某个对象
		//如果底层方法是静态的,可以忽略obj参数,设置为null.
		cs.invoke(null);//无参数。不写
	}
	public static void statimethod(){
		System.out.println("adaads");
	}

}
利用反射调用可变参数的方法。这里有一点要注意的是传参数的时候,其底层会自动解包,所以如果我们传参数的时候,直接传入的是正确的参数,代码到了底层经过解包,就变成不正确的了。所以,我们传参数的时候,把正确的包装一下,最好用Object包装,因为一切类都是它的子类,所以用它包装不会出错。代码到了底层,通过自动解包,就是被包装的正确的参数。这样就不会出错。具体实现看代码。

package com.method;
/*利用反射调用可变参数的方法
 * 
 * */
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;

public class VarArgsMethodInvolkeDemo {

	public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		//使用反射调用sum
		
		//第一步  获取方法所属类的字节码文件
		Class<VarArgsMethodInvolkeDemo> clz = VarArgsMethodInvolkeDemo.class;
		//第二步  获取要调用发方法
		Method cs = clz.getMethod("sum", int[].class);//可变参数底层其实就是数组
		//cs.invoke(null, 1,2,3,4,5);这中写法是错误的
		//调用
		cs.invoke(null, new int[]{1,2,3,4,5});
		
		System.out.println("*******************");
		//第二步  获取要调用的方法
		cs=clz.getMethod("show2", String[].class);
		//cs.invoke(null,new String[]{"A","B","C"});//这样写错误
		
		/*对于数组类型的引用类型的参数,底层会自动解包,为了解决
		该问题,我们使用Object的一维数组把实际参数包装起来。
		查看源码发现,invoke的参数本来就是一个object类型。所以传入
		object包装的类型之后,解包刚好是正确的类型。*/
		cs.invoke(null,new Object[]{new String[]{"A","B","C"}});
		
		/*以后使用反射调用invoke方法,传递实际参数的时候,无论是基本数据类型还是引用数据类型
		也无论是可变参数还是不可变参数。反正就是 一切实际参数都包装在Object[]{}中,就没有问题*/
		//只有一个参数也没问题,装进Object数组
	}
	//基本类型
	public static void show1(int ...args) {
		System.out.println(Arrays.toString(args));
	}
	//引用类型
	public static void show2(String ...args) {
		System.out.println(Arrays.toString(args));
	}

}

下面我们看利用反射来获取类中的属性。

也是有四种手段,调用Class类中的下面四个方法:

getField()//可以传参数,获取指定属性

getFields()//获取所有public修饰的属性,包括继承过来的,返回一个列表

getDeclaredField()//可以传参数,获取指定属性,无访问权限无关

getDeclaredFields()/获取所有属性,不包括继承过来的,返回一个列表

package com.field;

import java.lang.reflect.Field;

/*利用反射获取字段
 * */
public class FieldDemo {

	public static void main(String[] args) throws NoSuchFieldException, SecurityException {
		
		//第一步   获取字节码文件
		Class<Student> clz = Student.class;
		//第二步  获取的包括继承的字段 公共的  所有的
		Field[] cs = clz.getFields();
		for (Field field : cs) {
			System.out.println(field);
		}
		cs=clz.getDeclaredFields();//不包括继承的  和访问权限无关  本类所有的
		for (Field field : cs) {
			System.out.println(field);
		}	
		System.out.println("****************");
		getOneField();
	}

	public static void getOneField() throws NoSuchFieldException, SecurityException{
		Class<Student> clz = Student.class;
		Field cs = clz.getField("score");//获取一个指定的,只能是公共的
		System.out.println(cs);
		cs=clz.getDeclaredField("money");//获取一个指定字段  可以是私有,与权限无关
		System.out.println(cs);
	}
}
class Student extends User{
	public int score;
	private int money;
}
class User{
	public String study;
	
}
下面利用反射来对类中的属性进行操作。

package com.field;

import java.lang.reflect.Field;

/*利用反射操作字段
 * 
 * */
public class FieldInvoke {

	public static void main(String[] args) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException, InstantiationException {
		setValue();
		getValue();
		
	}
	//获取值
	private static void getValue() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException, InstantiationException{
		//第一步   获取字节所在类的字节码文件
		Class<User1> clz = User1.class;
		//第二步  获取字段
		Field cs = clz.getDeclaredField("schoolname");
		/*正常是
		 * User user=new User;
		 * user.getSchoolName(18);
		 * */
		//反射
		//先创建对象实例
		Object obj = clz.newInstance();
		cs.setAccessible(true);//调用之前设置访问权限  以为上面获取的是私有的
		Object returnvalue = cs.get(obj);//这里的参数是指  属性底层所属对象
		//利用returnvalue来接收cs.get(obj)返回的obj对象的schoolname的值
		System.out.println(returnvalue);
	}
	
	//设置值
	private static void setValue() throws NoSuchFieldException, SecurityException, InstantiationException, IllegalAccessException{
		//1 获取字节码文件
		Class<User1> clz = User1.class;
		//2 获取字段对象  获取age字段
		Field cs = clz.getDeclaredField("age");
		//3 设置值 
		cs.setAccessible(true);//设置值之前设置可访问的权限
		/*正常设置是
		 * User user=new User;
		 * user.setAge(18);
		 * */
		//cs.set(obj, value);  obj:字段底层是所属对象,所字段是static的,该值设置为null
		//这里要说一下,如果字段是静态的,那么第一个参数要设置为null,因为正常访问静态属性的时候,直接类名点属性名。
		//所以这里也没有必要把类自己的名字再传进去
		//                    value:为要设置的值
		Object obj = clz.newInstance();
		cs.set(obj, 18);

		//获取name字段
		cs=clz.getDeclaredField("name");
		cs.setAccessible(true);//设置可访问权限
		cs.set(obj, "zhansan");//给name字段设置值
		System.out.println(obj);
	}
}
class User1{
	private String name;
	private int age;
	private String schoolname="清华大学";
	@Override
	public String toString() {
		return "User [name=" + name + ", age=" + age + "]";
	}
	
}

那么通过以上总结。利用反射获取构造方法,调用构造方法;

获取方法,调用方法;获取静态方法,调用静态方法;获取属性,操作属性。

我们可以发现,其实就是那几步。

第一步  获取字节码文件

第二步  获取要获取的方法(构造方法  属性)

第三步 调用

中间要注意的就是  带不带Declare。  带Declare就是与权限无关的。  不带就是只能获取public修饰的,包括继承过来的。

调用Declare获取的方法 (属性)的时候  要设置一下访问权限。setAccessible(true)。





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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值