反射----7
含有数组参数的成员方法的反射 数组的反射
----------- android培训、java培训、java学习型技术博客、期待与您交流! ------------
1. 含有数组参数的成员方法的反射
(1). 使用反射调用一个以数组为形参出现的问题
[1]. 被调用方法是TestArguments类的main方法。main方法的参数是String[]数组
【示例代码】TestArguments.java
public class TestArguments {
public static void main(String[] args){
System.out.println(args.length);
for(String arg: args){
System.out.println(arg);
}
}
}
【传统方式调用TestArguments类main】
TestArguments.main(new String[]{"AAA", "BBB", "CCC"});
传统调用的打印结果:
[2]. 目标:通过反射的方式调用TestArguments这个类的main方法
【示例代码】
//以反射的形式调用TestArguments类的main方法
Class clazz =Class.forName("TestArguments");
//第一次关联:关联的形参是String[].class 也就是String数组的字节码
Method method =clazz.getMethod("main", String[].class);
//第二次关联:关联的实参是new String[]{"AAA","BBB", "CCC"}
method.invoke(null, new String[]{"AAA", "BBB", "CCC"});
运行抛出如下异常
[3]. 分析:
{1}. 经观察,异常抛出的位置是method.invoke(null, new String[]{"AAA", "BBB", "CCC"}); 异常的含义就是参数的个数传入错误。
【***wrong number of arguments异常***】这个异常的原因之一就是:实参和形参个数不匹配!!!
{2}. 但是传统的直接调用方法TestArguments.main(new String[]{"AAA", "BBB", "CCC"});可以编译通过并运行正常,说明参数传入和调用形参是一致的
{3}. 编译时正常但是运行时抛出异常,说明编译出了问题。
(3). 分析出现异常的原因
[1]. 出现异常的原因就一定出在反射的两次关联的编译上!!!
{1}. 第一次关联:Method method =clazz.getMethod("main", String[].class);
关联的形参的实参是String[].class,位于getMethod原型的可变参数数组的位置。由“可变参数数组编译原则”知道,String[].class走JDK5编译的道路,被打包成一个Class[]数组,传入getMethod方法的参数个数是1个。
{2}. 第二次关联:method.invoke(null, new String[]{"AAA", "BBB", "CCC"});
关联的形参的实参是new String[]{"AAA", "BBB", "CCC"},位于invoke原型的可变参数数组的位置。由“可变参数数组编译原则”知道, new String[]{"AAA", "BBB", "CCC"}走JDK4编译的道路,被拆分成Object[]的样子,传入invoke( )中的元素个数为3个。
{3}. 注意到关联形参,知道形参是1个,但是实参却给出了三个参数,运行时就引发异常的抛出。
[2]. 为什么抛出参数不一致的异常没有在编译的时候发现呢?
{1}. 因为javac进行语法检查的做法就是按照行来进行。
行与行之间编译结果信息不会相互联系起来。
所以虽然反射使用了先关联形参再关联实参的策略以达到传统方式的直接向形参传入实参的步骤,但是,反射对传统代码通过增加了其他的方法进行了格式化。原本传统的一行参数传递代码变成了反射操作的多行代码。因此,编译没有出现错误。但是运行必然出现问题。
{2}. 如果传统方法传值,参数个数传递不一致,javac直接编译报错。这是因为这个动作仅仅在一条语句中完成的,javac编译的有效范围就是一行,因此可以检查出来。
(4). 解决反射调用数组作为形参的方法的办法
关联形参的第一步是没有办法改变的,所以这就限制了第二步传入实参的个数必须是实实在在传入一个。
[1]. 不让javac拆分new String[]{"AAA", "BBB", "CCC"}, 保证是一个整体。
【参数被拆分的原因】String[]正好是Object[]的子类,走的是JDK4的编译。Javac将String拆分。如果把String[]包装成一个非Object[]的子类,这样javac就走JDK5的编译,这样就会被包装成一个总体。
【解决办法】----应用到String的父类是Object本身
强制转换成String[ ]的父类。String[ ]的父类可以是Object,所以强转成Object
method.invoke(null, (Object)new String[]{"AAA", "BBB", "CCC"});
[2]. 将计就计。知道走JDK4会进行拆分。那我就提前打包,让你根据Object[]进行一次拆分之后,正好是new String[]{"AAA", "BBB", "CCC"}。
【解决办法】----应用到String的父类还可以是Object[ ]
method.invoke(null, new Object[]{new String[]{"AAA", "BBB", "CCC"}});
按照JDK4编译之后,拆分结果就是就只有一个元素new String[]{"AAA", "BBB", "CCC"}。因此,运行正常。
运行结果:
3
AAA
BBB
CCC
【总结】分析编译的过程和解决方法反复用到的知识点就是数组的父类是什么的问题。
2. 数组的反射
1). 数组反射的应用背景和特点
(1). 数组的反射的应用背景
[1]. 在Java中有三种引用数据类型:数组类型,类类型和接口类型。
[2]. Java采用统一的Class类的特定对象来代表各种数据类型的字节码文件
[3]. Java对类类型 (接口类型) 进行反射
{1}. 由于Java的内之类和用户自定义类/接口千变万化,导致类的结构是千变万化的。这样Java为了细化对这些不同类的操作,引入了Method、Constructor和Field等几个类来配合Class对一个特定的类进行反射解剖。
{2}. Method、Constructor和Field这些类的实例对象全部要从Class对象的基础上进行获取,因此这些类相对与Class类而言,是配合,协助的作用。
[4]. Java对数组类型进行反射
{1}. 用反射来操作类的好处就是格式化特有的代码为通用的代码。因此需求就是十分想使用反射的想法来格式化数组的特有代码。
(2). 数组的反射的特点
{1}. 数组的结构远不如类的结构那样复杂,使用起来和基本数据类型数据十分相似。就是维度和元素数据类型会不同。
{2}. 由于数组的结构和类比起来相对固定并且Class的主要精力集中到对类类型进行解剖。所以,Class仅仅提供isArray()来判定这个字节码是不是数组。
{3}.如果是数组类型,Class就全全委托给java.lang.reflect.Array这个类对数组进行反射操作。
2). java.lang.reflect.Array类
(1). java.lang.reflect.Array是一个工具类
[1]. 这个类的构造函数是私有的 + 方法全部是静态的
[2]. 验证
public final
class Array {private Array() {} … }
Array类不能被继承,不能被创建实例。
(2). java.lang.reflect.Array是静态类的原因
由于数组的结构和对数组的操作是相对固定的,所以不同类型的数组具体操作也都差不多。因此对应一致的操作就抽象出来,放置到工具类中以共享。
3). 数组的反射的常用方法
(1). java.lang.reflect.Array源码
【前提】去掉了方法抛出的异常
package java.lang.reflect;
public final class Array {
//私有化构造方法
private Array() {}
/*
* 1. 实例化操作 ----调用的全部是私有本地化操作
* */
//依据元素类型和指定的长度创建一维数组
public static Object newInstance(Class<?> componentType, int length)
throws NegativeArraySizeException {
return newArray(componentType,length);
}
//依据元素类型和指定维度创建任意维数组
public static Object newInstance(Class<?> componentType, int... dimensions)
throws IllegalArgumentException,NegativeArraySizeException {
return multiNewArray(componentType,dimensions);
}
/*
* 2. 获取操作 ----全部本地化方法
* */
//获取数组的长度
public static native int getLength(Object array);
//以Object身份获取指定位置的数组元素
public static native Object get(Object array, int index);
/*
* 明确数组元素类型为具体类型之后,以基本数据类型的方式获取指定位置的数组元素
* */
public static native boolean getBoolean(Object array, int index);
public static native byte getByte(Object array, int index);
public static native char getChar(Object array, int index);
public static native short getShort(Object array, int index);
public static native int getInt(Object array, int index);
public static native long getLong(Object array, int index)
public static native float getFloat(Object array, int index);
public static native double getDouble(Object array, int index);
/*
* 3. 设置操作 ----全部本地化方法
* */
//以Object身份设置指定位置的数组元素的值
public static native void set(Object array, int index, Object value);
//以基本数据类型方式设置指定位置数组的元素的值
public static native void setBoolean(Object array, int index, boolean z);
public static native void setByte(Object array, int index, byte b);
public static native void setChar(Object array, int index, char c);
public static native void setShort(Object array, int index, short s);
public static native void setInt(Object array, int index, int i);
public static native void setLong(Object array, int index, long l);
public static native void setFloat(Object array, int index, float f);
public static native void setDouble(Object array, int index, double d);
/*
* Private ---- 全部本地化方法
*/
private static native Object newArray(Class componentType, int length);
private static native Object multiNewArray(Class componentType, int[] dimensions);
}
(2). java.lang.reflect.Array方法总结归纳 (全部是反射的方式操作数组)
[1]. 实例化操作
{1}. 根据指定的元素数据类型和数组长度来创建一维数组
public static Object newInstance(Class<?> componentType, int length);
{1}1. 输入参数:数组的元素类型是任意类型的,所以给出的是Class<?>的Class类型
{1}2. 返回值类型:由于所有数组的直接父类就是java.lang.Object,所以返回的类型是Object
{2}. 根据指定的元素数据类型和数组维度来创建任意维数组
public static Object newInstance(Class<?>componentType, int... dimensions)
【注意】输入的第二个参数是int... dimensions。表示高维数组每一维度的长度是多少。
[3]. 获取元素操作
{1}. 获取数组的长度
public static native int getLength(Object array);
【注意】输入参数:所有的数组全部是Object的直接子类,所以,多态性写成Object
{2}. 获取指定位置的数组的元素
public static native Object get(Object array, int index);
{3}. 如果已知一维数组的元素数据类型是基本数据类型,那就采用更为简洁的方法来获取元素,省去了强制类型转换的麻烦。
public static native xxx getXxx(Object array, int index);
{3}1. xxx是基本数据类型
{3}2. Xxx是基本数据类型的首字母大写的单词。
[3]. 设置元素操作
{1}. 设置指定位置的数组的元素
public static native void set(Object array, int index, Object value);
{3}. 如果已知一维数组的元素数据类型是基本数据类型,那就采用更为简洁的方法来设置元素,省去了强制类型转换的麻烦。
public static native void setXxx (Object array, int index, xxx value);
【xxx和XXxx含义同获取操作对应的部分】
(3). java.lang.reflect.Array和java.util.Arrays的关系
[1]. 相同点
两者全部都是工具类
[2]. 不同点
{1}. 包名不同 + 类名不同
类名:反射子包的是Array,工具包的是Arrays
{2}. 作用不同
{2}1. java.lang.reflect.Array提供的是对数组进行反射的操作
{2}2. java.util.Arrays提供的以传统的操作来对数组操作的基本数据结构算法
4). 应用举例
需求:打印一个对象
(1). 通常做法以及出现的问题
[1]. 常规的示例代码
public static void printObject(Object obj){
System.out.println(obj);
}
[2]. 调用代码
List<String> strList =new ArrayList<String>();
strList.add("AAA");
strList.add("BBB");
strList.add("CCC");
printObject(strList);
打印结果:
[AAA, BBB, CCC]
[3]. 出现的问题
调用代码修改如下:
String[] intArr =new String[]{"123", "456", "789"};
printObject(intArr);
打印结果:[Ljava.lang.String;@19836ed
【结果分析】由于数组没有显示的类,仅仅是使用操作符[]来定义。所以,数组本身没有办法像其他类类型一样重写toString()方法来修改默认的打印值“数据类型@哈希值”。
【解决思路】应该在方法内部判断传来的数据是否是数组类型,如果是,就要按照数组的方式来逐个打印数组的元素。否则按照以前的办法直接打印对象实例。
(2). 解决办法
[1]. instanceof操作符不可行
想到使用instanceof来判定数据类型。但是数组类型是千变万化的(不同维度+ 不同数据类型的组合很多),没有办法统一写成一个固定的表达形式。
但是instanceof的适用范围就是当要判断的类类型数目很少的时候,可以使用这个操作符。但是现在看来,数组的类型是不可数的离散无穷个,没有办法使用instanceof逐一列举数组的类型。
因此,instanceof操作符不能胜任这个工作。
[2]. 使用Class对象的isArray()方法
{1}. 示例代码
public static void printObject(Object obj){
Classclazz =obj.getClass();
if(clazz.isArray()){
printArr(obj);
return;
}
System.out.println(obj);
}
//将反射进行到底
private static void printArr(Object arr) {
//使用反射获取数组的长度
int arrLength =Array.getLength(arr);
for(int i=0; i<arrLength; i++){
System.out.println(Array.get(arr,i));
}
}
{2}. 调用代码
String[] intArr =new String[]{"123", "456", "789"};
printObject(intArr);
{3}. 打印结果
123
456
789
----------- android培训、java培训、java学习型技术博客、期待与您交流! ------------