Java基础加强第二讲 反射(中)——反射API类:Constructor、Field以及Method类

反射

反射的概述

一个类有多个组成部分,例如:成员变量,方法,构造方法等。反射就是加载类,并解剖出类的各个组成部分。也就是说,反射是把Java类中的各种成分映射成相应的Java类。例如,一个Java类用一个Class类的对象来表示,一个类中的组成部分:成员变量,方法,构造方法,包等等信息也用一个个的Java类来表示,就像汽车是一个类,汽车中的发动机,变速箱等等也是一个个的类。表示Java类的Class类显然要提供一系列的方法,来获得其中的变量,方法,构造方法,修饰符,包等信息,这些信息就是用相应类的实例对象来表示,它们是Field、Method、Contructor、Package等等。
一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象,得到这些实例对象后有什么用呢?怎么用呢?这正是学习和应用反射的要点。

Constructor类

Constructor类代表某个类中的一个构造方法。Constructor对象代表一个构造方法,大家觉得Constructor对象上会有什么方法呢?

通过反射获取构造方法

package cn.liayun.reflect;

import java.lang.reflect.Constructor;

public class ReflectDemo {

	public static void main(String[] args) throws Exception {
		//得到某个类所有的构造方法
		Constructor[] constructors = Class.forName("java.lang.String").getConstructors();
		
		//得到某一个构造方法
		Constructor constructor1 = String.class.getConstructor(StringBuffer.class);
	}

}

一个类会有多个构造方法,那么用什么方式可以区分清楚想得到其中的哪个方法呢?根据参数的个数和类型,例如Class.getMethod(name, Class... args)中的args参数就代表所要获取的那个方法的各个参数的类型的列表,参数类型用Class实例对象的方式来表示。

获取指定参数的构造方法并创建对象

通常我们通过指定参数的构造方法创建对象是这样做的,
在这里插入图片描述
而用反射是这样做的:
在这里插入图片描述

获取无参的构造方法并创建对象

最便捷的方式,就是通过Class实例对象的newInstance()方法。该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象,所以利用此种方式创建类对象时,类必须有一个无参的构造函数。该方法内部的具体代码是怎样写的呢?用到了缓存机制来保存默认构造方法的实例对象。
在这里插入图片描述

Field类

Field类代表某个类中的一个成员变量。

通过反射方式,获取指定public修饰的成员变量

下面我会定义一个ReflectPoint类来进行演示。

package cn.liayun.domain;

public class ReflectPoint {
	private int x;
    public int y;
    
    public ReflectPoint(int x, int y) {
        super();
        this.x = x;
        this.y = y;
    }
}

试着运行如下测试程序代码,那你会得到5。
在这里插入图片描述
通过以上程序代码,相信我们能够回答这样一个问题。得到的Field对象是对应到类上面的成员变量呢,还是对应到对象上的成员变量呢?类只有一个,而该类的实例对象有多个,如果是与对象关联,哪关联的是哪个对象呢?所以字段fieldY代表的是y的定义,而不是具体的y变量。

通过反射方式,获取指定的任意成员变量(包含私有)

试着运行如下测试程序代码,那你将发现会报异常——java.lang.NoSuchFieldException: x,究其原因是该字段是用private修饰的,那要获取该成员变量又该如何呢?
在这里插入图片描述
Class实例对象的getField方法获取的是指定public修饰的成员变量,而getDeclaredField方法可以获取任意的成员变量,所以我们可以使用该方法。
在这里插入图片描述
发现还是报错,如果非要获取,怎么办,可用暴力反射。
在这里插入图片描述
那么问题来了,我把自己的变量定义成private,就是不想让人家访问,可是,现在人家用暴力反射还是能够访问我,这说不通啊,能不能让人家用暴力反射也访问不了我。首先,private主要是给javac编译器看的,希望在写程序的时候,在源代码中不要访问我,是帮助程序员实现高内聚、低耦合的一种策略。你这个程序员不领情,非要去访问,那我拦不住你,由你去吧。同样的道理,泛型集合在编译时可以帮助我们限定元素的内容,这是人家提供的好处,而你非不想要这个好处,怎么办?绕过编译器,就可以往集合中存入另外类型了。

练习

将任意一个对象中的所有String类型的成员变量所对应的字符串内容中的"b"改成"a"。例如,有ReflectPoint类定义如下:

package cn.liayun.domain;

public class ReflectPoint {
	private int x;
    public int y;
    public String str1 = "ball";
    public String str2 = "basketball";
    public String str3 = "itcast";
    
    public ReflectPoint(int x, int y) {
        super();
        this.x = x;
        this.y = y;
    }
    
    @Override
    public String toString() {
        return str1 + ":" + str2 + ":" + str3;
    }
}

将一个ReflectPoint类的实例对象中所有String类型的成员变量所对应的字符串内容中的"b"改成"a"。

package cn.liayun.reflect;

import java.lang.reflect.Field;

import cn.liayun.domain.ReflectPoint;

public class ReflectDemo {

	public static void main(String[] args) throws Exception {
		ReflectPoint pt1 = new ReflectPoint(3, 5);
		
		/*
		 * 作业:将任意一个对象中的所有String类型的成员变量所对应的字符串内容中的"b"改成"a"。
		 */
		changeStringValue(pt1);
		System.out.println(pt1);
	}

	private static void changeStringValue(Object obj) throws Exception {
		Field[] fields = obj.getClass().getFields();
		for (Field field : fields) {
//			field.getType();//得到字段的类型
//			if (field.getType().equals(String.class)) {
			if (field.getType() == String.class) {//字节码只有同一份,对于字节码的比较,应该用==比较
				String oldValue = (String) field.get(obj);
				String newValue = oldValue.replace('b', 'a');
				field.set(obj, newValue);
			}
		}
	}
}

Method类

Method类代表某个类中的一个成员方法。

通过反射方式得到类中的某一个方法,并调用该方法

通过反射方式得到类中的某一个方法,比如String类中的charAt方法。
在这里插入图片描述
大家应通过思考和推理的方式来学习反射中的API。例如,Class.getMethod方法用于得到一个方法,该方法要接受什么参数呢?显然要一个方法名,而一个同名的方法有多个重载形式,用什么方式可以区分清楚想得到重载方法系列中的哪个方法呢?根据参数的个数和类型,例如,Class.getMethod(name, Class… args)中的args参数就代表所要获取的那个方法的各个参数的类型的列表。再强调一遍参数类型用Class实例对象来表示!
通常我们调用String类中的charAt方法,是这样做的,
在这里插入图片描述
而用反射是这样做的,
在这里插入图片描述
如果传递给Method对象的invoke()方法的第一个参数为null,这有着什么样的意义呢?说明该Method对象对应的是一个静态方法!静态方法调用不需要对象,所以传递null。

JDK1.4和JDK1.5中invoke方法的区别

  • JDK1.5中invoke方法的定义如下:

    public Object invoke(Object obj, Object... args)
    
  • JDK1.4中invoke方法的定义如下:

    public Object invoke(Object obj, Object[] args)
    

    即按JDK1.4的语法,需要将一个数组作为参数传递给invoke方法,数组中的每个元素分别对应被调用方法中的一个参数。所以,调用charAt方法的代码也可以用JDK1.4改写为:
    在这里插入图片描述

练习:用反射方式执行某个类中的main方法

写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法。首先用普通方式调用,如下:

package cn.liayun.reflect;

public class ReflectMethodDemo {

	public static void main(String[] args) throws Exception {
		/*
		 * 写一个程序,这个程序能够根据用户提供的类名,去执行该类中的main方法。
		 */
		TestArguments.main(new String[] {"111","222","333"});
	}

}

class TestArguments {
	public static void main(String[] args) {
		for (String arg : args) {
			System.out.println(arg);
		}
	}
}

然后再使用反射的方式来调用,
在这里插入图片描述
发现报异常——java.lang.IllegalArgumentException: wrong number of arguments。究其原因就是此时实际调用的是main(String a1, String a2, String a3) 方法,而TestArguments类中没有接收3个String类型参数的main方法,TestArguments类中的main方法只接收一个参数,结果你调用的时候传了3个参数,所以就会报异常——错误的参数个数。
那么问题来了,启动Java程序的main方法的参数是一个字符串数组,即
在这里插入图片描述
通过反射方式来调用这个main方法时,如何为invoke方法传递参数呢?按JDK1.5的语法,整个数组是一个参数,而按JDK1.4的语法,数组中的每个元素对应一个参数,当把一个字符串数组作为参数传递给invoke方法时,javac会到底按照哪种语法进行处理呢?JDK1.5肯定要兼容JDK1.4的语法,会按JDK1.4的语法进行处理,即把数组打散成为若干个单独的参数。所以,在给main方法传递参数时,不能使用如下代码
在这里插入图片描述
javac只把它当作JDK1.4的语法进行理解,而不把它当作JDK1.5的语法解释,因此会出现参数类型不对的问题。解决办法有两种:

  1. 将String数组再包装进一个Object数组,这样将Object数组拆分后得到的就是String数组。说白了,就是我给你的数组,你不会当作参数,而是把其中的内容当作参数;
  2. 将String数组转成Object类型的对象,编译器会作特殊处理,编译时不把参数当作数组看待,也就不会将数组打散成若干个参数了。

在这里插入图片描述
注意,Object和String具有父子关系,但Objec[]和String[]没有父子关系,所以以下代码是错误的。
在这里插入图片描述
更进一步来说,用反射方式执行某个类中的main方法,就不能写以下代码。
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

李阿昀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值