在路上

 找回密码
 立即注册
在路上 站点首页 学习 查看内容

实例讲解Java编程中数组反射的使用方法

2016-7-29 15:37| 发布者: zhangjf| 查看: 587| 评论: 0

摘要: 什么是反射 “反射(Reflection)能够让运行于JVM中的程序检测和修改运行时的行为。”这个概念常常会和内省(Introspection)混淆,以下是这两个术语在Wikipedia中的解释: 内省用于在运行时检测某个对象的类型和 ...

什么是反射
“反射(Reflection)能够让运行于JVM中的程序检测和修改运行时的行为。”这个概念常常会和内省(Introspection)混淆,以下是这两个术语在Wikipedia中的解释:

  • 内省用于在运行时检测某个对象的类型和其包含的属性;
  • 反射用于在运行时检测和修改某个对象的结构及其行为。
  • 从它们的定义可以看出,内省是反射的一个子集。有些语言支持内省,但并不支持反射,如C++。

内省示例:instanceof 运算符用于检测某个对象是否属于特定的类。

  1. if (obj instanceof Dog) {
  2. Dog d = (Dog) obj;
  3. d.bark();
  4. }
复制代码

反射示例:Class.forName()方法可以通过类或接口的名称(一个字符串或完全限定名)来获取对应的Class对象。forName方法会触发类的初始化。

  1. // 使用反射
  2. Class<?> c = Class.forName("classpath.and.classname");
  3. Object dog = c.newInstance();
  4. Method m = c.getDeclaredMethod("bark", new Class<?>[0]);
  5. m.invoke(dog);
复制代码

在Java中,反射更接近于内省,因为你无法改变一个对象的结构。虽然一些API可以用来修改方法和属性的可见性,但并不能修改结构。

数组的反射
数组的反射有什么用呢?何时需要使用数组的反射呢?先来看下下面的代码:

  1. Integer[] nums = {1, 2, 3, 4};
  2. Object[] objs = nums; //这里能自动的将Integer[]转成Object[]
  3. Object obj = nums; //Integer[]当然是一个Object
  4. int[] ids = {1, 2, 3, 4};
  5. //Object[] objs2 = ids; //这里不能将int[]转换成Object[]
  6. Object obj2 = ids; //int[] 是一个Object
复制代码

上面的例子表明:基本类型的一维数组只能当做Object,而不能当作Object[]。

  1. int[][] intArray = {{1, 2}, {3, 4}};
  2. Object[] oa = intArray;
  3. Object obj = intArray;
  4. //Integer[][] integerArray = intArray; int[][] 不是 Integer[][]
  5. Integer[][] integerArray2 = new Integer[][]{{1, 2}, {3, 4}};
  6. Object[][] oa2 = integerArray2;
  7. Object[] oa3 = integerArray2;
  8. Object obj2 = integerArray2;
复制代码

从上面的例子可以看出java的二位数组是数组的数组。下面来看下对数组进行反射的例子:

  1. package cn.zq.array.reflect;
  2. import java.lang.reflect.Array;
  3. import java.util.Arrays;
  4. import java.util.Random;
  5. public class ArrayReflect {
  6. public static void main(String[] args) {
  7. Random rand = new Random(47);
  8. int[] is = new int[10];
  9. for(int i = 0; i < is.length; i++) {
  10. is[i] = rand.nextInt(100);
  11. }
  12. System.out.println(is);
  13. System.out.println(Arrays.asList(is));
  14. /*以上的2个输出都是输出类似"[[I@14318bb]"的字符串,
  15. 不能显示数组内存放的内容,当然我们采用遍历的方式来输出数组内的内容*/
  16. System.out.println("--1.通过常规方式遍历数组对数组进行打印--");
  17. for(int i = 0; i < is.length; i++) {
  18. System.out.print(is[i] + " ");
  19. }
  20. System.out.println();
  21. System.out.println("--2.通过数组反射的方式遍历数组对数组进行打印--");
  22. Object obj = is; //将一维的int数组向上转为Object
  23. System.out.println("obj isArray:" + obj.getClass().isArray());
  24. for(int i = 0; i < Array.getLength(obj); i++) {
  25. int num = Array.getInt(obj, i);
  26. //也能通过这个常用的方法来获取对应索引位置的值
  27. //Object value = Array.get(obj, i); //如果数组存放的是基本类型,那么返回的是基本类型对应的包装类型
  28. System.out.print(num + " ");
  29. }
  30. }
  31. }
复制代码

输出:

  1. [I@14318bb
  2. [[I@14318bb]
  3. --1.通过常规方式遍历数组对数组进行打印--
  4. 58 55 93 61 61 29 68 0 22 7
  5. --2.通过数组反射的方式遍历数组对数组进行打印--
  6. obj isArray:true
  7. 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个用来创建数组实例的方法。通过数组反射工具类,可以很方便的利用数组反射写出通用的代码,而不用再去判断给定的数组到底是那种基本类型的数组。

  1. package cn.zq.array.reflect;
  2. import java.lang.reflect.Array;
  3. public class NewArrayInstance {
  4. public static void main(String[] args) {
  5. Object o = Array.newInstance(int.class, 20);
  6. int[] is = (int[]) o;
  7. System.out.println("is.length = " + is.length);
  8. Object o2 = Array.newInstance(int.class, 10, 8);
  9. int[][] iss = (int[][]) o2;
  10. System.out.println("iss.length = " + iss.length
  11. + ", iss[0].lenght = " + iss[0].length);
  12. }
  13. }
  14. is.length = 20
  15. 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来的直接。事实上通过反射创建数组确实很少见,是有何种变态的需求需要用反射来创建数组呢!
由于前面对基本类型的数组进行输出时遇到一些障碍,下面将利用数组反射来实现一个工具类来实现期望的输出:

  1. package cn.zq.util;
  2. import java.io.ByteArrayOutputStream;
  3. import java.io.PrintStream;
  4. import java.lang.reflect.Array;
  5. public class Print {
  6. public static void print(Object obj) {
  7. print(obj, System.out);
  8. }
  9. public static void print(Object obj, PrintStream out) {
  10. out.println(getPrintString(obj));
  11. }
  12. public static void println() {
  13. print(System.out);
  14. }
  15. public static void println(PrintStream out) {
  16. out.println();
  17. }
  18. public static void printnb(Object obj) {
  19. printnb(obj, System.out);
  20. }
  21. public static void printnb(Object obj, PrintStream out) {
  22. out.print(getPrintString(obj));
  23. }
  24. public static PrintStream format(String format, Object ... objects) {
  25. return format(System.out, format, objects);
  26. }
  27. public static PrintStream format(PrintStream out, String format, Object ... objects) {
  28. Object[] handleObjects = new Object[objects.length];
  29. for(int i = 0; i < objects.length; i++) {
  30. Object object = objects[i];
  31. if(object == null || isPrimitiveWrapper(object)) {
  32. handleObjects[i] = object;
  33. } else {
  34. ByteArrayOutputStream bos = new ByteArrayOutputStream();
  35. PrintStream ps = new PrintStream(bos);
  36. printnb(object, ps);
  37. ps.close();
  38. handleObjects[i] = new String(bos.toByteArray());
  39. }
  40. }
  41. out.format(format, handleObjects);
  42. return out;
  43. }
  44. /**
  45. * 判断给定对象是否为基本类型的包装类。
  46. * @param o 给定的Object对象
  47. * @return 如果是基本类型的包装类,则返回是,否则返回否。
  48. */
  49. private static boolean isPrimitiveWrapper(Object o) {
  50. return o instanceof Void || o instanceof Boolean
  51. || o instanceof Character || o instanceof Byte
  52. || o instanceof Short || o instanceof Integer
  53. || o instanceof Long || o instanceof Float
  54. || o instanceof Double;
  55. }
  56. public static String getPrintString(Object obj) {
  57. StringBuilder result = new StringBuilder();
  58. if(obj != null && obj.getClass().isArray()) {
  59. result.append("[");
  60. int len = Array.getLength(obj);
  61. for(int i = 0; i < len; i++) {
  62. Object value = Array.get(obj, i);
  63. result.append(getPrintString(value));
  64. if(i != len - 1) {
  65. result.append(", ");
  66. }
  67. }
  68. result.append("]");
  69. } else {
  70. result.append(String.valueOf(obj));
  71. }
  72. return result.toString();
  73. }
  74. }
复制代码

上面的Print工具类提供了一些实用的进行输出的静态方法,并且提供了一些重载版本,可以根据个人的喜欢自己编写一些重载的版本,支持基本类型的一维数组的打印以及多维数组的打印,看下下面的Print工具进行测试的示例:

  1. package cn.zq.array.reflect;
  2. import static cn.zq.util.Print.print;
  3. import java.io.PrintStream;
  4. import static cn.zq.util.Print.*;
  5. public class PrintTest {
  6. static class Person {
  7. private static int counter;
  8. private final int id = counter ++;
  9. public String toString() {
  10. return getClass().getSimpleName() + id;
  11. }
  12. }
  13. public static void main(String[] args) throws Exception {
  14. print("--打印非数组--");
  15. print(new Object());
  16. print("--打印基本类型的一维数组--");
  17. int[] is = new int[]{1, 22, 31, 44, 21, 33, 65};
  18. print(is);
  19. print("--打印基本类型的二维数组--");
  20. int[][] iss = new int[][]{
  21. {11, 12, 13, 14},
  22. {21, 22,},
  23. {31, 32, 33}
  24. };
  25. print(iss);
  26. print("--打印非基本类型的一维数组--");
  27. Person[] persons = new Person[10];
  28. for(int i = 0; i < persons.length; i++) {
  29. persons[i] = new Person();
  30. }
  31. print(persons);
  32. print("--打印非基本类型的二维数组--");
  33. Person[][] persons2 = new Person[][]{
  34. {new Person()},
  35. {new Person(), new Person()},
  36. {new Person(), new Person(), new Person(),},
  37. };
  38. print(persons2);
  39. print("--打印empty数组--");
  40. print(new int[]{});
  41. print("--打印含有null值的数组--");
  42. Object[] objects = new Object[]{
  43. new Person(), null, new Object(), new Integer(100)
  44. };
  45. print(objects);
  46. print("--打印特殊情况的二维数组--");
  47. Object[][] objects2 = new Object[3][];
  48. objects2[0] = new Object[]{};
  49. objects2[2] = objects;
  50. print(objects2);
  51. print("--将一维数组的结果输出到文件--");
  52. PrintStream out = new PrintStream("out.c");
  53. try {
  54. print(iss, out);
  55. } finally {
  56. out.close();
  57. }
  58. print("--格式化输出--");
  59. format("%-6d%s %B %s", 10086, "is", true, iss);
  60. /**
  61. * 上面列出了一些Print工具类的一些常用的方法,
  62. * 还有一些未列出的方法,请自行查看。
  63. */
  64. }
  65. }
复制代码

输出:

  1. --打印非数组--
  2. java.lang.Object@61de33
  3. --打印基本类型的一维数组--
  4. [1, 22, 31, 44, 21, 33, 65]
  5. --打印基本类型的二维数组--
  6. [[11, 12, 13, 14], [21, 22], [31, 32, 33]]
  7. --打印非基本类型的一维数组--
  8. [Person0, Person1, Person2, Person3, Person4, Person5, Person6, Person7, Person8, Person9]
  9. --打印非基本类型的二维数组--
  10. [[Person10], [Person11, Person12], [Person13, Person14, Person15]]
  11. --打印empty数组--
  12. []
  13. --打印含有null值的数组--
  14. [Person16, null, java.lang.Object@ca0b6, 100]
  15. --打印特殊情况的二维数组--
  16. [[], null, [Person16, null, java.lang.Object@ca0b6, 100]]
  17. --将一维数组的结果输出到文件--
  18. --格式化输出--
  19. 10086 is TRUE [[11, 12, 13, 14], [21, 22], [31, 32, 33]]
复制代码

输出文件:

2016425142151040.png (1111×352)

可见Print工具类已经具备打印基本类型的一维数组以及多维数组的能力了,总体来说上面的工具类还是挺实用的,免得每次想要看数组里面的内容都有手动的去编写代码,那样是在是太麻烦了,以后直接把Print工具类拿过去用就行了,多么的方便啊。
上面的工具类确实能很好的工作,但是假如有这样一个需求:给你一个数组(也有可能是其他的容器),你给我整出一个List。那么我们应该怎样做呢?事实上Arrays.asList不总是能得到我们所期望的结果,java5虽然添加了泛型,但是是有限制的,并不能像c++的模板那样通用,正是因为java中存在基本类型,即使有自动包装的机制,与泛型一起并不能使用,参数类型必须是某种类型,而不能是基本类型。下面给出一种自己的解决办法:

  1. package cn.zq.util;
  2. import java.lang.reflect.Array;
  3. import java.util.ArrayList;
  4. import java.util.Arrays;
  5. import java.util.Enumeration;
  6. import java.util.Iterator;
  7. import java.util.List;
  8. import java.util.Map;
  9. public class CollectionUtils {
  10. public static List<?> asList(Object obj) {
  11. return convertToList(
  12. makeIterator(obj));
  13. }
  14. public static <T>List<T> convertToList(Iterator<T> iterator) {
  15. if(iterator == null) {
  16. return null;
  17. }
  18. List<T> list = new ArrayList<T>();
  19. while(iterator.hasNext()) {
  20. list.add(iterator.next());
  21. }
  22. return list;
  23. }
  24. @SuppressWarnings({ "rawtypes", "unchecked" })
  25. public static Iterator<?> makeIterator(Object obj) {
  26. if(obj instanceof Iterator) {
  27. return (Iterator<?>) obj;
  28. }
  29. if(obj == null) {
  30. return null;
  31. }
  32. if(obj instanceof Map) {
  33. obj = ((Map<?, ?>)obj).entrySet();
  34. }
  35. Iterator<?> iterator = null;
  36. if(obj instanceof Iterable) {
  37. iterator = ((Iterable<?>)obj).iterator();
  38. } else if(obj.getClass().isArray()) {
  39. //Object[] objs = (Object[]) obj; //原始类型的一位数组不能这样转换
  40. ArrayList list = new ArrayList(Array.getLength(obj));
  41. for(int i = 0; i < Array.getLength(obj); i++) {
  42. list.add(Array.get(obj, i));
  43. }
  44. iterator = list.iterator();
  45. } else if(obj instanceof Enumeration) {
  46. iterator = new EnumerationIterator((Enumeration) obj);
  47. } else {
  48. iterator = Arrays.asList(obj).iterator();
  49. }
  50. return iterator;
  51. }
  52. public static class EnumerationIterator<T> implements Iterator<T> {
  53. private Enumeration<T> enumeration;
  54. public EnumerationIterator(Enumeration<T> enumeration) {
  55. this.enumeration = enumeration;
  56. }
  57. public boolean hasNext() {
  58. return enumeration.hasMoreElements();
  59. }
  60. public T next() {
  61. return enumeration.nextElement();
  62. }
  63. public void remove() {
  64. throw new UnsupportedOperationException();
  65. }
  66. }
  67. }
复制代码

测试代码:

  1. package cn.zq.array.reflect;
  2. import java.util.Iterator;
  3. import java.util.List;
  4. import cn.zq.array.reflect.PrintTest.Person;
  5. import cn.zq.util.CollectionUtils;
  6. public class CollectionUtilsTest {
  7. public static void main(String[] args) {
  8. System.out.println("--基本类型一维数组--");
  9. int[] nums = {1, 3, 5, 7, 9};
  10. List<?> list = CollectionUtils.asList(nums);
  11. System.out.println(list);
  12. System.out.println("--非基本类型一维数组--");
  13. Person[] persons = new Person[]{
  14. new Person(),
  15. new Person(),
  16. new Person(),
  17. };
  18. List<Person> personList = (List<Person>) CollectionUtils.asList(persons);
  19. System.out.println(personList);
  20. System.out.println("--Iterator--");
  21. Iterator<Person> iterator = personList.iterator();
  22. List<Person> personList2 = (List<Person>) CollectionUtils.asList(iterator);
  23. System.out.println(personList2);
  24. }
  25. }
复制代码

输出:

  1. --基本类型一维数组--
  2. [1, 3, 5, 7, 9]
  3. --非基本类型一维数组--
  4. [Person0, Person1, Person2]
  5. --Iterator--
  6. [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还提供了一些其他的方法来进行转换,可以根据需要添加自己需要的方法。

总结:数组的反射对于那些可能出现数组的设计中提供更方便、更灵活的方法,以免写那些比较麻烦的判断语句,这种灵活性付出的就是性能的代价,对于那些根本不需要数组反射的情况下用数组的反射实在是不应该。是否使用数组的反射,在实际的开发中仁者见仁智者见智,根据需要来选择是否使用数组的反射,最好的方式就是用实践来探路,先按照自己想到的方式去写,在实践中不断的完善。

最新评论

小黑屋|在路上 ( 蜀ICP备15035742号-1 

;

GMT+8, 2025-5-6 09:37

Copyright 2015-2025 djqfx

返回顶部