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

 更新时间:2016年04月25日 14:22:53   作者:RiccioZhang  
这篇文章主要介绍了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]] 

输出文件:

2016425142151040.png (1111×352)

可见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还提供了一些其他的方法来进行转换,可以根据需要添加自己需要的方法。

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

相关文章

  • SpringBoot实现yml配置文件为变量赋值

    SpringBoot实现yml配置文件为变量赋值

    这篇文章主要介绍了SpringBoot实现yml配置文件为变量赋值,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-02-02
  • spring 中事务注解@Transactional与trycatch的使用

    spring 中事务注解@Transactional与trycatch的使用

    这篇文章主要介绍了spring 中事务注解@Transactional与trycatch的使用,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-06-06
  • 关于Unsupported Media Type的解决方案

    关于Unsupported Media Type的解决方案

    在Web开发中,415错误表示服务器无法处理请求附带的媒体格式,本文介绍了导致HTTP 415错误的原因以及解决该问题的两种方法,首先,415错误通常是由于客户端请求的内容类型与服务器期望的不匹配引起的,例如,服务器可能期望JSON格式的数据
    2024-10-10
  • JAVA实现微信APPV3支付保姆级教程

    JAVA实现微信APPV3支付保姆级教程

    微信实现支付功能与支付宝实现支付功能是相似的,这篇文章主要介绍了JAVA实现微信APPV3支付的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2024-01-01
  • Java synchronized关键字使用方式及特性解析

    Java synchronized关键字使用方式及特性解析

    这篇文章主要介绍了Java synchronized关键字使用方式及特性解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-12-12
  • 分析mybatis运行原理

    分析mybatis运行原理

    Mybatis是一个优秀的持久层框架,它对JDBC操作数据库的过程进行封装,使开发者只需要关注sql本身。我们原来使用JDBC操作数据库,需要手动的写代码去注册驱动、获取connection、获取statement等等,现在Mybaits帮助我们把这些事情做了,我们只需要关注我们的业务sql即可
    2021-06-06
  • Java字典生成算法讲解

    Java字典生成算法讲解

    今天小编就为大家分享一篇关于Java字典生成算法讲解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-02-02
  • 详解Java中native方法的使用

    详解Java中native方法的使用

    native是与C++联合开发的时候用的!使用native关键字说明这个方法是原生函数,也就是这个方法是用C/C++语言实现的,并且被编译成了DLL,由java去调用。本文给大家介绍java 中native方法使用,感兴趣的朋友一起看看吧
    2020-09-09
  • 图解Java经典算法折半查找的原理与实现

    图解Java经典算法折半查找的原理与实现

    折半查找法也叫做⼆分查找,顾名思义就是把数据分成两半,再判断所查找的key在哪⼀半中,再重复上述步骤知道找到⽬标key,下面这篇文章主要介绍了图解Java经典算法折半查找的原理与实现
    2022-09-09
  • springmvc不进入Controller导致404的问题

    springmvc不进入Controller导致404的问题

    这篇文章主要介绍了springmvc不进入Controller导致404的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-02-02

最新评论