实例讲解Java编程中数组反射的使用方法
什么是反射
“反射(reflection)能够让运行于jvm中的程序检测和修改运行时的行为。”这个概念常常会和内省(introspection)混淆,以下是这两个术语在wikipedia中的解释:
- 内省用于在运行时检测某个对象的类型和其包含的属性;
- 反射用于在运行时检测和修改某个对象的结构及其行为。
- 从它们的定义可以看出,内省是反射的一个子集。有些语言支持内省,但并不支持反射,如c++。
内省示例:instanceof 运算符用于检测某个对象是否属于特定的类。
if (obj instanceof dog) { dog d = (dog) obj; d.bark(); }
反射示例:class.forname()方法可以通过类或接口的名称(一个字符串或完全限定名)来获取对应的class对象。forname方法会触发类的初始化。
// 使用反射 class<?> c = class.forname("classpath.and.classname"); object dog = c.newinstance(); method m = c.getdeclaredmethod("bark", new class<?>[0]); m.invoke(dog);
在java中,反射更接近于内省,因为你无法改变一个对象的结构。虽然一些api可以用来修改方法和属性的可见性,但并不能修改结构。
数组的反射
数组的反射有什么用呢?何时需要使用数组的反射呢?先来看下下面的代码:
integer[] nums = {1, 2, 3, 4}; object[] objs = nums; //这里能自动的将integer[]转成object[] object obj = nums; //integer[]当然是一个object int[] ids = {1, 2, 3, 4}; //object[] objs2 = ids; //这里不能将int[]转换成object[] object obj2 = ids; //int[] 是一个object
上面的例子表明:基本类型的一维数组只能当做object,而不能当作object[]。
int[][] intarray = {{1, 2}, {3, 4}}; object[] oa = intarray; object obj = intarray; //integer[][] integerarray = intarray; int[][] 不是 integer[][] integer[][] integerarray2 = new integer[][]{{1, 2}, {3, 4}}; object[][] oa2 = integerarray2; object[] oa3 = integerarray2; object obj2 = integerarray2;
从上面的例子可以看出java的二位数组是数组的数组。下面来看下对数组进行反射的例子:
package cn.zq.array.reflect; import java.lang.reflect.array; import java.util.arrays; import java.util.random; public class arrayreflect { public static void main(string[] args) { random rand = new random(47); int[] is = new int[10]; for(int i = 0; i < is.length; i++) { is[i] = rand.nextint(100); } system.out.println(is); system.out.println(arrays.aslist(is)); /*以上的2个输出都是输出类似"[[i@14318bb]"的字符串, 不能显示数组内存放的内容,当然我们采用遍历的方式来输出数组内的内容*/ system.out.println("--1.通过常规方式遍历数组对数组进行打印--"); for(int i = 0; i < is.length; i++) { system.out.print(is[i] + " "); } system.out.println(); system.out.println("--2.通过数组反射的方式遍历数组对数组进行打印--"); object obj = is; //将一维的int数组向上转为object system.out.println("obj isarray:" + obj.getclass().isarray()); for(int i = 0; i < array.getlength(obj); i++) { int num = array.getint(obj, i); //也能通过这个常用的方法来获取对应索引位置的值 //object value = array.get(obj, i); //如果数组存放的是基本类型,那么返回的是基本类型对应的包装类型 system.out.print(num + " "); } } }
输出:
[i@14318bb [[i@14318bb] --1.通过常规方式遍历数组对数组进行打印-- 58 55 93 61 61 29 68 0 22 7 --2.通过数组反射的方式遍历数组对数组进行打印-- obj isarray:true 58 55 93 61 61 29 68 0 22 7
上面的例子首先创建了一个int的一维数组,然后随机的像里面填充0~100的整数,接着通过system.out.println()方法直接对数组输出或者用arrays.aslist方法(如果不是基本类型的一维数组此方法能按照期望转成list,如果是二维数组也不能按照我们期望转成list)将数组转成list再输出,通过都不是我们期望的输出结果。接下来以常规的数组的遍历方式来输出数组内的内容,然后将int[]看成是一个object,利用反射来遍历其内容。class.isarray()可以用来判断是对象是否为一个数组,假如是一个数组,那么在通过java.lang.reflect.array这个对数组反射的工具类来获取数组的相关信息,这个类通过了一些get方法,可以用来获取数组的长度,各个版本的用来获取基本类型的一维数组的对应索引的值,通用获取值的方法get(object array, int index),设置值的方法,还有2个用来创建数组实例的方法。通过数组反射工具类,可以很方便的利用数组反射写出通用的代码,而不用再去判断给定的数组到底是那种基本类型的数组。
package cn.zq.array.reflect; import java.lang.reflect.array; public class newarrayinstance { public static void main(string[] args) { object o = array.newinstance(int.class, 20); int[] is = (int[]) o; system.out.println("is.length = " + is.length); object o2 = array.newinstance(int.class, 10, 8); int[][] iss = (int[][]) o2; system.out.println("iss.length = " + iss.length + ", iss[0].lenght = " + iss[0].length); } } is.length = 20 iss.length = 10, iss[0].lenght = 8
array总共通过了2个方法来创建数组
object newinstance(class<?> componenttype, int length),根据提供的class来创建一个指定长度的数组,如果像上面那样提供int.class,长度为10,相当于new int[10];
object newinstance(class<?> componenttype, int... dimensions),根据提供的class和维度来创建数组,可变参数dimensions用来指定数组的每一维的长度,像上面的例子那样相当于创建了一个new int[10][8]的二维数组,但是不能创建每一维长度都不同的多维数组。通过第一种创建数组的方法,可以像这样创建数组object o = array.newinstance(int[].class, 20)可以用来创建二维数组,这里相当于object o = new int[20][];
当然通过上面例子那样来创建数组的用法是很少见的,其实也是多余的,为什么不直接通过new来创建数组呢?反射创建数组不仅速度没有new快,而且写的程序也不易读,还不如new来的直接。事实上通过反射创建数组确实很少见,是有何种变态的需求需要用反射来创建数组呢!
由于前面对基本类型的数组进行输出时遇到一些障碍,下面将利用数组反射来实现一个工具类来实现期望的输出:
package cn.zq.util; import java.io.bytearrayoutputstream; import java.io.printstream; import java.lang.reflect.array; public class print { public static void print(object obj) { print(obj, system.out); } public static void print(object obj, printstream out) { out.println(getprintstring(obj)); } public static void println() { print(system.out); } public static void println(printstream out) { out.println(); } public static void printnb(object obj) { printnb(obj, system.out); } public static void printnb(object obj, printstream out) { out.print(getprintstring(obj)); } public static printstream format(string format, object ... objects) { return format(system.out, format, objects); } public static printstream format(printstream out, string format, object ... objects) { object[] handleobjects = new object[objects.length]; for(int i = 0; i < objects.length; i++) { object object = objects[i]; if(object == null || isprimitivewrapper(object)) { handleobjects[i] = object; } else { bytearrayoutputstream bos = new bytearrayoutputstream(); printstream ps = new printstream(bos); printnb(object, ps); ps.close(); handleobjects[i] = new string(bos.tobytearray()); } } out.format(format, handleobjects); return out; } /** * 判断给定对象是否为基本类型的包装类。 * @param o 给定的object对象 * @return 如果是基本类型的包装类,则返回是,否则返回否。 */ private static boolean isprimitivewrapper(object o) { return o instanceof void || o instanceof boolean || o instanceof character || o instanceof byte || o instanceof short || o instanceof integer || o instanceof long || o instanceof float || o instanceof double; } public static string getprintstring(object obj) { stringbuilder result = new stringbuilder(); if(obj != null && obj.getclass().isarray()) { result.append("["); int len = array.getlength(obj); for(int i = 0; i < len; i++) { object value = array.get(obj, i); result.append(getprintstring(value)); if(i != len - 1) { result.append(", "); } } result.append("]"); } else { result.append(string.valueof(obj)); } return result.tostring(); } }
上面的print工具类提供了一些实用的进行输出的静态方法,并且提供了一些重载版本,可以根据个人的喜欢自己编写一些重载的版本,支持基本类型的一维数组的打印以及多维数组的打印,看下下面的print工具进行测试的示例:
package cn.zq.array.reflect; import static cn.zq.util.print.print; import java.io.printstream; import static cn.zq.util.print.*; public class printtest { static class person { private static int counter; private final int id = counter ++; public string tostring() { return getclass().getsimplename() + id; } } public static void main(string[] args) throws exception { print("--打印非数组--"); print(new object()); print("--打印基本类型的一维数组--"); int[] is = new int[]{1, 22, 31, 44, 21, 33, 65}; print(is); print("--打印基本类型的二维数组--"); int[][] iss = new int[][]{ {11, 12, 13, 14}, {21, 22,}, {31, 32, 33} }; print(iss); print("--打印非基本类型的一维数组--"); person[] persons = new person[10]; for(int i = 0; i < persons.length; i++) { persons[i] = new person(); } print(persons); print("--打印非基本类型的二维数组--"); person[][] persons2 = new person[][]{ {new person()}, {new person(), new person()}, {new person(), new person(), new person(),}, }; print(persons2); print("--打印empty数组--"); print(new int[]{}); print("--打印含有null值的数组--"); object[] objects = new object[]{ new person(), null, new object(), new integer(100) }; print(objects); print("--打印特殊情况的二维数组--"); object[][] objects2 = new object[3][]; objects2[0] = new object[]{}; objects2[2] = objects; print(objects2); print("--将一维数组的结果输出到文件--"); printstream out = new printstream("out.c"); try { print(iss, out); } finally { out.close(); } print("--格式化输出--"); format("%-6d%s %b %s", 10086, "is", true, iss); /** * 上面列出了一些print工具类的一些常用的方法, * 还有一些未列出的方法,请自行查看。 */ } }
输出:
--打印非数组-- java.lang.object@61de33 --打印基本类型的一维数组-- [1, 22, 31, 44, 21, 33, 65] --打印基本类型的二维数组-- [[11, 12, 13, 14], [21, 22], [31, 32, 33]] --打印非基本类型的一维数组-- [person0, person1, person2, person3, person4, person5, person6, person7, person8, person9] --打印非基本类型的二维数组-- [[person10], [person11, person12], [person13, person14, person15]] --打印empty数组-- [] --打印含有null值的数组-- [person16, null, java.lang.object@ca0b6, 100] --打印特殊情况的二维数组-- [[], null, [person16, null, java.lang.object@ca0b6, 100]] --将一维数组的结果输出到文件-- --格式化输出-- 10086 is true [[11, 12, 13, 14], [21, 22], [31, 32, 33]]
输出文件:
可见print工具类已经具备打印基本类型的一维数组以及多维数组的能力了,总体来说上面的工具类还是挺实用的,免得每次想要看数组里面的内容都有手动的去编写代码,那样是在是太麻烦了,以后直接把print工具类拿过去用就行了,多么的方便啊。
上面的工具类确实能很好的工作,但是假如有这样一个需求:给你一个数组(也有可能是其他的容器),你给我整出一个list。那么我们应该怎样做呢?事实上arrays.aslist不总是能得到我们所期望的结果,java5虽然添加了泛型,但是是有限制的,并不能像c++的模板那样通用,正是因为java中存在基本类型,即使有自动包装的机制,与泛型一起并不能使用,参数类型必须是某种类型,而不能是基本类型。下面给出一种自己的解决办法:
package cn.zq.util; import java.lang.reflect.array; import java.util.arraylist; import java.util.arrays; import java.util.enumeration; import java.util.iterator; import java.util.list; import java.util.map; public class collectionutils { public static list<?> aslist(object obj) { return converttolist( makeiterator(obj)); } public static <t>list<t> converttolist(iterator<t> iterator) { if(iterator == null) { return null; } list<t> list = new arraylist<t>(); while(iterator.hasnext()) { list.add(iterator.next()); } return list; } @suppresswarnings({ "rawtypes", "unchecked" }) public static iterator<?> makeiterator(object obj) { if(obj instanceof iterator) { return (iterator<?>) obj; } if(obj == null) { return null; } if(obj instanceof map) { obj = ((map<?, ?>)obj).entryset(); } iterator<?> iterator = null; if(obj instanceof iterable) { iterator = ((iterable<?>)obj).iterator(); } else if(obj.getclass().isarray()) { //object[] objs = (object[]) obj; //原始类型的一位数组不能这样转换 arraylist list = new arraylist(array.getlength(obj)); for(int i = 0; i < array.getlength(obj); i++) { list.add(array.get(obj, i)); } iterator = list.iterator(); } else if(obj instanceof enumeration) { iterator = new enumerationiterator((enumeration) obj); } else { iterator = arrays.aslist(obj).iterator(); } return iterator; } public static class enumerationiterator<t> implements iterator<t> { private enumeration<t> enumeration; public enumerationiterator(enumeration<t> enumeration) { this.enumeration = enumeration; } public boolean hasnext() { return enumeration.hasmoreelements(); } public t next() { return enumeration.nextelement(); } public void remove() { throw new unsupportedoperationexception(); } } }
测试代码:
package cn.zq.array.reflect; import java.util.iterator; import java.util.list; import cn.zq.array.reflect.printtest.person; import cn.zq.util.collectionutils; public class collectionutilstest { public static void main(string[] args) { system.out.println("--基本类型一维数组--"); int[] nums = {1, 3, 5, 7, 9}; list<?> list = collectionutils.aslist(nums); system.out.println(list); system.out.println("--非基本类型一维数组--"); person[] persons = new person[]{ new person(), new person(), new person(), }; list<person> personlist = (list<person>) collectionutils.aslist(persons); system.out.println(personlist); system.out.println("--iterator--"); iterator<person> iterator = personlist.iterator(); list<person> personlist2 = (list<person>) collectionutils.aslist(iterator); system.out.println(personlist2); } }
输出:
--基本类型一维数组-- [1, 3, 5, 7, 9] --非基本类型一维数组-- [person0, person1, person2] --iterator-- [person0, person1, person2]
在java的容器类库中可以分为collection,map,数组,由于iterator(以及早期的遗留接口enumeration)是所有容器的通用接口并且collection接口从iterable(该接口的iterator将返回一个iterator),所以在makeiterator方法中对这些情形进行了一一的处理,对map类型,只需要调用其entryset()方法,对于实现了iterable接口的类(collection包含在内),调用iterator()直接得到iterator对象,对于enumeration类型,利用适配器enumerationiterator进行适配,对于数组,利用数组反射遍历数组放入arraylist中,对于其他的类型调用arrays.aslist()方法创建一个list。collectionutils还提供了一些其他的方法来进行转换,可以根据需要添加自己需要的方法。
总结:数组的反射对于那些可能出现数组的设计中提供更方便、更灵活的方法,以免写那些比较麻烦的判断语句,这种灵活性付出的就是性能的代价,对于那些根本不需要数组反射的情况下用数组的反射实在是不应该。是否使用数组的反射,在实际的开发中仁者见仁智者见智,根据需要来选择是否使用数组的反射,最好的方式就是用实践来探路,先按照自己想到的方式去写,在实践中不断的完善。