Java反射机制(一)—— 使用反射

在这里插入图片描述

1.什么是反射

我们知道一个对象的类型在编译时就确定了下来,并编译成字节码文件供JVM使用。如果我们需要在程序运行过程中动态创建一个类型的对象,常规编码就无法实现了。这就需要使用到Java的反射机制。在Java类的加载和初始化阶段会将该类的构造器、方法、属性分别以软引用对象(SoftReference)的Constructor、Method、Field对象缓存的形式保存下来,Java反射机制就是利用这些对象创建实例、调用方法和属性。

2.使用反射

2.1利用反射创建一个对象

要创建一个对象,首先要明确的就是对象的类型。Java反射机制会利用一个类型的Class对象动态的创建一个对象。获取Class对象的方式有三种:

  • 类.class属性 任何类型都有这个属性,可以直接获取该类型的Class对象。
  • 对象.getClass()方法 任何类型的对象都有这个方法,因为这是Object类的成员,这种方法可利用一个已存在的对象创建出一个新的对象。
  • Class.forName(“包.类”) 利用Class类的forName方法也可以获取类型的Class对象,不过需要传入类型的完整路径(包括包名和类名),并且如果该类未加载过,会执行类加载。(推荐使用该方法,因为该方法仅需一个字符串即可创建Class对象,更为灵活)

获得了类型的Class对象之后,就可以创建对应的对象了。创建一个类的对象,除了确定类型就是调用构造函数。Class对象有两种方法可以创建实例。

  • 直接使用Class对象的newInstance()方法。该方法可以直接调用目标类的公共无参构造函数创建对象。这种方法创建对象非常便捷,但局限性较大,当无法直接调用目标类的无参构造函数时,将无法创建对象,并且会抛出异常。

  • 通过目标类的构造函数对象Constructor创建实例。可以调用Class对象的getDeclaredConstructors()方法得到目标类所有的构造函数组成的数组(包括私有的构造函数),再利用Constructor对象的newInstance()方法创建目标类对象。需要注意的是,当调用的构造函数带有参数时,需要传入对应的参数,可用getParameterTypes方法获取构造函数所有参数类型数组。使用这种方式不受构造函数的访问级别及参数个数的限制。通过这种方式甚至可以给单例类创建一个新的对象,需谨慎使用。

public class TestReflect {
	public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException {
        Class<Test1> cla = Test1.class;//获取Class对象
        Constructor<?>[] cons=cla.getDeclaredConstructors();//获取该类所有的构造函数对象
		
        Test1[] tests = new Test1[cons.length];
		
        for (int i = 0; i < cons.length; i++) {
			
            if(!cons[i].isAccessible()){
                cons[i].setAccessible(true);//将无权限访问的构造函数设置为可用
            }
			
            Class<?>[] ps = cons[i].getParameterTypes(); //获取构造函数参数类型数组
			
            Object[] pars = new Object[ps.length];//构造函数的参数数组
			
            for (int j = 0; j < ps.length; j++) {//为构造函数的参数数组赋值
				
                if(ps[j].isAssignableFrom(String.class)){//参数是否为String类型或其子类
	                pars[j] = "字符串";
                }else if(ps[j].isAssignableFrom(int.class)){//参数是否为int类型或其子类
                    pars[j] = 1;
               }
            }
			
            tests[i] = (Test1) cons[i].newInstance(pars);//创建对象
			
        }
		
        System.out.println(tests[0]+" | "+tests[1]+" | "+tests[2]);
	}
}

class Test1{
    
	private Test1(){
        System.out.println("调用了无参构造函数");
	}
	public Test1(String s){
        System.out.println("调用了(String s)构造函数");
	}
	public Test1(String s,int a){
        System.out.println("调用了(String s, int a)构造函数");
	}
}

运行结果
在这里插入图片描述
值得注意的是部分构造函数访问级别低(如私有构造函数),直接使用Constructor对象的newInstance方法创建对象将触发异常。这种情况下如果一定要使用该构造函数,必须使用setAccessible(true)将该方法设置为可用。

除了使用getDeclaredConstructors()获取所有的构造函数,更多时候我们会使用getDeclaredConstructor(Class<?>… parameterTypes) 直接获取某个特定的构造方法用于创建对象。parameterTypes参数为目标构造函数的参数类型数组。

public class TestReflect {
	public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException {
        Class<Test1> cla = Test1.class;//获取Class对象
        
        Class<?>[] parameterTypes = {String.class , int.class};//参数类型数组
        
        Constructor<Test1> con = cla.getDeclaredConstructor(parameterTypes);//获取构造函数对象
		
        if(!con.isAccessible()){
            con.setAccessible(true);
        }
        Test1 t = con.newInstance("字符串",1);
	}
}

class Test1{
    
	private Test1(){
        System.out.println("调用了无参构造函数");
	}
	public Test1(String s){
        System.out.println("调用了(String s)构造函数");
	}
	public Test1(String s,int a){
        System.out.println("调用了(String s, int a)构造函数");
	}
}

运行结果
在这里插入图片描述

2.2利用反射调用方法

前面我们介绍了如何利用反射获取对象,获取到了对象之后我们可以直接调用权限内的所有方法。但在某些极端特殊情况下,我们需要调用一个类的私有方法时就无法通过对象直接调用了。这种情况下,我们就可以使用反射来获取方法的对象,然后调用方法。

通常情况下我们使用Class对象的getDeclaredMethod(String name, Class<?>… parameterTypes) 方法来获取目标方法对象。name参数就是目标方法的方法名,parameterTypes参数就是目标方法的参数类型数组。
在获取了方法对象之后需要使用setAccessible(true)将该对象设置为可用。
然后就可以调用Method对象的invoke(Object obj, Object… args)调用目标方法,obj参数是调用目标方法的主体,普通方法就传入类的实例,static方法就是传入类的class对象。args参数就是调用目标参数需要传入的所有参数组成的数组。

public class TestReflect {
	public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException {
		
        Test1 te1 = new Test1();
		
        Class<Test1> cla = Test1.class;//获取Class对象
		
        Method meth1 = cla.getDeclaredMethod("meth1", String.class);//获取meth1方法的对象
        Method meth2 = cla.getDeclaredMethod("meth2", new Class<?>[]{String.class , int.class});//获取meth2方法的对象
		
        meth1.setAccessible(true);
        meth2.setAccessible(true);
		
        meth1.invoke(te1, "字符串");//使用Test1的实例调用meth1方法
        meth2.invoke(Test1.class, new Object[]{"字符串",1});//使用Test1的Class对象来调用meth2方法
	}
}

class Test1{
	
	@SuppressWarnings("unused")
	private void meth1(String s){
        System.out.println("调用了meth1方法");
	}
	
	@SuppressWarnings("unused")
	private static void meth2(String s,int a){
        System.out.println("调用了meth2方法");
	}
}

运行结果
在这里插入图片描述
需要注意的是,我们使用invoke方法时需要传入调用方法的主体对象,基于面向对象的编程思维,建议调用实例方法就传入类的实例,调用static方法就传入类的Class对象

2.3利用反射调用访问Field

与利用反射调用方法类似,利用反射属性也是通过调用类Class对象的getDeclaredField(String name)方法直接获取目标Field对象,name参数就是对应的Field名称。同样,不可缺少的就是将Field对象用setAccessible(true)设置为可用。然后就可以访问Field了。

在我们使用Field的时候,通常操作包括获取值更改值

使用Field的get(Object obj)方法可以获取目标Field的值。obj为访问Field的主体,通常来说,如果访问的是单个实例的Field就传入对应实例,如果访问的是static修饰的Field就传入对应类的Class对象。

使用set(Object obj, Object value)方法可以修改Field的值。obj为访问Field的主体,value为更改的值。

public class TestReflect {
	public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException, NoSuchFieldException {
		
        Test1 te1 = new Test1();
		
        Class<Test1> cla = Test1.class;//获取Class对象
		
        Field f1 = cla.getDeclaredField("a");
        Field f2 = cla.getDeclaredField("s");
        Field f3 = cla.getDeclaredField("C");
		
        f1.setAccessible(true);
        f2.setAccessible(true);
        f3.setAccessible(true);
		
        System.out.println("a = "+f1.get(te1));
        System.out.println("s = "+f2.get(Test1.class));
        System.out.println("C = "+f3.get(te1));
		
        System.out.println("========================");
		
        f1.set(te1, 2);
        f2.set(Test1.class, "sss");
        f3.set(te1, 2);
		
        System.out.println("a = "+f1.get(te1));
        System.out.println("s = "+f2.get(Test1.class));
        System.out.println("C = "+f3.get(te1));
		
        System.out.println("========================");
		
        System.out.println(te1.getA());
        System.out.println(te1.getS());
        System.out.println(te1.getC());
		
	}
}

class Test1{
	
	private int a = 1;
	private static String s = "s";
	private final int C = 5;
	
	public int getC(){
        return C;
	}
	public String getS(){
        return s;
	}
	public int getA(){
        return a;
	}
}

运行结果
在这里插入图片描述
从运行结果可以看出对于普通的实例Field和类Field可以运用反射修改值,但是对于final修饰的常量进行修改之后Field对象的值好像是确实被修改了,但是当我们直接使用getC方法获取值的时候,常量的值并未改变。

欢迎继续阅读Java反射机制(二)—— 深入JDK了解反射
~~~欢迎留言交流~~~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值