實(shí)例講解Java編程中數(shù)組反射的使用方法
什么是反射
“反射(Reflection)能夠讓運(yùn)行于JVM中的程序檢測(cè)和修改運(yùn)行時(shí)的行為。”這個(gè)概念常常會(huì)和內(nèi)?。↖ntrospection)混淆,以下是這兩個(gè)術(shù)語(yǔ)在Wikipedia中的解釋?zhuān)?/p>
- 內(nèi)省用于在運(yùn)行時(shí)檢測(cè)某個(gè)對(duì)象的類(lèi)型和其包含的屬性;
- 反射用于在運(yùn)行時(shí)檢測(cè)和修改某個(gè)對(duì)象的結(jié)構(gòu)及其行為。
- 從它們的定義可以看出,內(nèi)省是反射的一個(gè)子集。有些語(yǔ)言支持內(nèi)省,但并不支持反射,如C++。
內(nèi)省示例:instanceof 運(yùn)算符用于檢測(cè)某個(gè)對(duì)象是否屬于特定的類(lèi)。
if (obj instanceof Dog) {
Dog d = (Dog) obj;
d.bark();
}
反射示例:Class.forName()方法可以通過(guò)類(lèi)或接口的名稱(chēng)(一個(gè)字符串或完全限定名)來(lái)獲取對(duì)應(yīng)的Class對(duì)象。forName方法會(huì)觸發(fā)類(lèi)的初始化。
// 使用反射
Class<?> c = Class.forName("classpath.and.classname");
Object dog = c.newInstance();
Method m = c.getDeclaredMethod("bark", new Class<?>[0]);
m.invoke(dog);
在Java中,反射更接近于內(nèi)省,因?yàn)槟銦o(wú)法改變一個(gè)對(duì)象的結(jié)構(gòu)。雖然一些API可以用來(lái)修改方法和屬性的可見(jiàn)性,但并不能修改結(jié)構(gòu)。
數(shù)組的反射
數(shù)組的反射有什么用呢?何時(shí)需要使用數(shù)組的反射呢?先來(lái)看下下面的代碼:
Integer[] nums = {1, 2, 3, 4};
Object[] objs = nums; //這里能自動(dòng)的將Integer[]轉(zhuǎn)成Object[]
Object obj = nums; //Integer[]當(dāng)然是一個(gè)Object
int[] ids = {1, 2, 3, 4};
//Object[] objs2 = ids; //這里不能將int[]轉(zhuǎn)換成Object[]
Object obj2 = ids; //int[] 是一個(gè)Object
上面的例子表明:基本類(lèi)型的一維數(shù)組只能當(dāng)做Object,而不能當(dāng)作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的二位數(shù)組是數(shù)組的數(shù)組。下面來(lái)看下對(duì)數(shù)組進(jìn)行反射的例子:
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個(gè)輸出都是輸出類(lèi)似"[[I@14318bb]"的字符串,
不能顯示數(shù)組內(nèi)存放的內(nèi)容,當(dāng)然我們采用遍歷的方式來(lái)輸出數(shù)組內(nèi)的內(nèi)容*/
System.out.println("--1.通過(guò)常規(guī)方式遍歷數(shù)組對(duì)數(shù)組進(jìn)行打印--");
for(int i = 0; i < is.length; i++) {
System.out.print(is[i] + " ");
}
System.out.println();
System.out.println("--2.通過(guò)數(shù)組反射的方式遍歷數(shù)組對(duì)數(shù)組進(jìn)行打印--");
Object obj = is; //將一維的int數(shù)組向上轉(zhuǎn)為Object
System.out.println("obj isArray:" + obj.getClass().isArray());
for(int i = 0; i < Array.getLength(obj); i++) {
int num = Array.getInt(obj, i);
//也能通過(guò)這個(gè)常用的方法來(lái)獲取對(duì)應(yīng)索引位置的值
//Object value = Array.get(obj, i); //如果數(shù)組存放的是基本類(lèi)型,那么返回的是基本類(lèi)型對(duì)應(yīng)的包裝類(lèi)型
System.out.print(num + " ");
}
}
}
輸出:
[I@14318bb [[I@14318bb] --1.通過(guò)常規(guī)方式遍歷數(shù)組對(duì)數(shù)組進(jìn)行打印-- 58 55 93 61 61 29 68 0 22 7 --2.通過(guò)數(shù)組反射的方式遍歷數(shù)組對(duì)數(shù)組進(jìn)行打印-- obj isArray:true 58 55 93 61 61 29 68 0 22 7
上面的例子首先創(chuàng)建了一個(gè)int的一維數(shù)組,然后隨機(jī)的像里面填充0~100的整數(shù),接著通過(guò)System.out.println()方法直接對(duì)數(shù)組輸出或者用Arrays.asList方法(如果不是基本類(lèi)型的一維數(shù)組此方法能按照期望轉(zhuǎn)成List,如果是二維數(shù)組也不能按照我們期望轉(zhuǎn)成List)將數(shù)組轉(zhuǎn)成List再輸出,通過(guò)都不是我們期望的輸出結(jié)果。接下來(lái)以常規(guī)的數(shù)組的遍歷方式來(lái)輸出數(shù)組內(nèi)的內(nèi)容,然后將int[]看成是一個(gè)Object,利用反射來(lái)遍歷其內(nèi)容。Class.isArray()可以用來(lái)判斷是對(duì)象是否為一個(gè)數(shù)組,假如是一個(gè)數(shù)組,那么在通過(guò)java.lang.reflect.Array這個(gè)對(duì)數(shù)組反射的工具類(lèi)來(lái)獲取數(shù)組的相關(guān)信息,這個(gè)類(lèi)通過(guò)了一些get方法,可以用來(lái)獲取數(shù)組的長(zhǎng)度,各個(gè)版本的用來(lái)獲取基本類(lèi)型的一維數(shù)組的對(duì)應(yīng)索引的值,通用獲取值的方法get(Object array, int index),設(shè)置值的方法,還有2個(gè)用來(lái)創(chuàng)建數(shù)組實(shí)例的方法。通過(guò)數(shù)組反射工具類(lèi),可以很方便的利用數(shù)組反射寫(xiě)出通用的代碼,而不用再去判斷給定的數(shù)組到底是那種基本類(lèi)型的數(shù)組。
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總共通過(guò)了2個(gè)方法來(lái)創(chuàng)建數(shù)組
Object newInstance(Class<?> componentType, int length),根據(jù)提供的class來(lái)創(chuàng)建一個(gè)指定長(zhǎng)度的數(shù)組,如果像上面那樣提供int.class,長(zhǎng)度為10,相當(dāng)于new int[10];
Object newInstance(Class<?> componentType, int... dimensions),根據(jù)提供的class和維度來(lái)創(chuàng)建數(shù)組,可變參數(shù)dimensions用來(lái)指定數(shù)組的每一維的長(zhǎng)度,像上面的例子那樣相當(dāng)于創(chuàng)建了一個(gè)new int[10][8]的二維數(shù)組,但是不能創(chuàng)建每一維長(zhǎng)度都不同的多維數(shù)組。通過(guò)第一種創(chuàng)建數(shù)組的方法,可以像這樣創(chuàng)建數(shù)組Object o = Array.newInstance(int[].class, 20)可以用來(lái)創(chuàng)建二維數(shù)組,這里相當(dāng)于Object o = new int[20][];
當(dāng)然通過(guò)上面例子那樣來(lái)創(chuàng)建數(shù)組的用法是很少見(jiàn)的,其實(shí)也是多余的,為什么不直接通過(guò)new來(lái)創(chuàng)建數(shù)組呢?反射創(chuàng)建數(shù)組不僅速度沒(méi)有new快,而且寫(xiě)的程序也不易讀,還不如new來(lái)的直接。事實(shí)上通過(guò)反射創(chuàng)建數(shù)組確實(shí)很少見(jiàn),是有何種變態(tài)的需求需要用反射來(lái)創(chuàng)建數(shù)組呢!
由于前面對(duì)基本類(lèi)型的數(shù)組進(jìn)行輸出時(shí)遇到一些障礙,下面將利用數(shù)組反射來(lái)實(shí)現(xiàn)一個(gè)工具類(lèi)來(lái)實(shí)現(xiàn)期望的輸出:
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;
}
/**
* 判斷給定對(duì)象是否為基本類(lèi)型的包裝類(lèi)。
* @param o 給定的Object對(duì)象
* @return 如果是基本類(lèi)型的包裝類(lèi),則返回是,否則返回否。
*/
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工具類(lèi)提供了一些實(shí)用的進(jìn)行輸出的靜態(tài)方法,并且提供了一些重載版本,可以根據(jù)個(gè)人的喜歡自己編寫(xiě)一些重載的版本,支持基本類(lèi)型的一維數(shù)組的打印以及多維數(shù)組的打印,看下下面的Print工具進(jìn)行測(cè)試的示例:
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("--打印非數(shù)組--");
print(new Object());
print("--打印基本類(lèi)型的一維數(shù)組--");
int[] is = new int[]{1, 22, 31, 44, 21, 33, 65};
print(is);
print("--打印基本類(lèi)型的二維數(shù)組--");
int[][] iss = new int[][]{
{11, 12, 13, 14},
{21, 22,},
{31, 32, 33}
};
print(iss);
print("--打印非基本類(lèi)型的一維數(shù)組--");
Person[] persons = new Person[10];
for(int i = 0; i < persons.length; i++) {
persons[i] = new Person();
}
print(persons);
print("--打印非基本類(lèi)型的二維數(shù)組--");
Person[][] persons2 = new Person[][]{
{new Person()},
{new Person(), new Person()},
{new Person(), new Person(), new Person(),},
};
print(persons2);
print("--打印empty數(shù)組--");
print(new int[]{});
print("--打印含有null值的數(shù)組--");
Object[] objects = new Object[]{
new Person(), null, new Object(), new Integer(100)
};
print(objects);
print("--打印特殊情況的二維數(shù)組--");
Object[][] objects2 = new Object[3][];
objects2[0] = new Object[]{};
objects2[2] = objects;
print(objects2);
print("--將一維數(shù)組的結(jié)果輸出到文件--");
PrintStream out = new PrintStream("out.c");
try {
print(iss, out);
} finally {
out.close();
}
print("--格式化輸出--");
format("%-6d%s %B %s", 10086, "is", true, iss);
/**
* 上面列出了一些Print工具類(lèi)的一些常用的方法,
* 還有一些未列出的方法,請(qǐng)自行查看。
*/
}
}
輸出:
--打印非數(shù)組-- java.lang.Object@61de33 --打印基本類(lèi)型的一維數(shù)組-- [1, 22, 31, 44, 21, 33, 65] --打印基本類(lèi)型的二維數(shù)組-- [[11, 12, 13, 14], [21, 22], [31, 32, 33]] --打印非基本類(lèi)型的一維數(shù)組-- [Person0, Person1, Person2, Person3, Person4, Person5, Person6, Person7, Person8, Person9] --打印非基本類(lèi)型的二維數(shù)組-- [[Person10], [Person11, Person12], [Person13, Person14, Person15]] --打印empty數(shù)組-- [] --打印含有null值的數(shù)組-- [Person16, null, java.lang.Object@ca0b6, 100] --打印特殊情況的二維數(shù)組-- [[], null, [Person16, null, java.lang.Object@ca0b6, 100]] --將一維數(shù)組的結(jié)果輸出到文件-- --格式化輸出-- 10086 is TRUE [[11, 12, 13, 14], [21, 22], [31, 32, 33]]
輸出文件:

可見(jiàn)Print工具類(lèi)已經(jīng)具備打印基本類(lèi)型的一維數(shù)組以及多維數(shù)組的能力了,總體來(lái)說(shuō)上面的工具類(lèi)還是挺實(shí)用的,免得每次想要看數(shù)組里面的內(nèi)容都有手動(dòng)的去編寫(xiě)代碼,那樣是在是太麻煩了,以后直接把Print工具類(lèi)拿過(guò)去用就行了,多么的方便啊。
上面的工具類(lèi)確實(shí)能很好的工作,但是假如有這樣一個(gè)需求:給你一個(gè)數(shù)組(也有可能是其他的容器),你給我整出一個(gè)List。那么我們應(yīng)該怎樣做呢?事實(shí)上Arrays.asList不總是能得到我們所期望的結(jié)果,java5雖然添加了泛型,但是是有限制的,并不能像c++的模板那樣通用,正是因?yàn)閖ava中存在基本類(lèi)型,即使有自動(dòng)包裝的機(jī)制,與泛型一起并不能使用,參數(shù)類(lèi)型必須是某種類(lèi)型,而不能是基本類(lèi)型。下面給出一種自己的解決辦法:
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; //原始類(lèi)型的一位數(shù)組不能這樣轉(zhuǎn)換
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();
}
}
}
測(cè)試代碼:
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("--基本類(lèi)型一維數(shù)組--");
int[] nums = {1, 3, 5, 7, 9};
List<?> list = CollectionUtils.asList(nums);
System.out.println(list);
System.out.println("--非基本類(lèi)型一維數(shù)組--");
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);
}
}
輸出:
--基本類(lèi)型一維數(shù)組-- [1, 3, 5, 7, 9] --非基本類(lèi)型一維數(shù)組-- [Person0, Person1, Person2] --Iterator-- [Person0, Person1, Person2]
在java的容器類(lèi)庫(kù)中可以分為Collection,Map,數(shù)組,由于Iterator(以及早期的遺留接口Enumeration)是所有容器的通用接口并且Collection接口從Iterable(該接口的iterator將返回一個(gè)Iterator),所以在makeIterator方法中對(duì)這些情形進(jìn)行了一一的處理,對(duì)Map類(lèi)型,只需要調(diào)用其entrySet()方法,對(duì)于實(shí)現(xiàn)了Iterable接口的類(lèi)(Collection包含在內(nèi)),調(diào)用iterator()直接得到Iterator對(duì)象,對(duì)于Enumeration類(lèi)型,利用適配器EnumerationIterator進(jìn)行適配,對(duì)于數(shù)組,利用數(shù)組反射遍歷數(shù)組放入ArrayList中,對(duì)于其他的類(lèi)型調(diào)用Arrays.asList()方法創(chuàng)建一個(gè)List。CollectionUtils還提供了一些其他的方法來(lái)進(jìn)行轉(zhuǎn)換,可以根據(jù)需要添加自己需要的方法。
總結(jié):數(shù)組的反射對(duì)于那些可能出現(xiàn)數(shù)組的設(shè)計(jì)中提供更方便、更靈活的方法,以免寫(xiě)那些比較麻煩的判斷語(yǔ)句,這種靈活性付出的就是性能的代價(jià),對(duì)于那些根本不需要數(shù)組反射的情況下用數(shù)組的反射實(shí)在是不應(yīng)該。是否使用數(shù)組的反射,在實(shí)際的開(kāi)發(fā)中仁者見(jiàn)仁智者見(jiàn)智,根據(jù)需要來(lái)選擇是否使用數(shù)組的反射,最好的方式就是用實(shí)踐來(lái)探路,先按照自己想到的方式去寫(xiě),在實(shí)踐中不斷的完善。
- java中數(shù)組的定義及使用方法(推薦)
- Java中遍歷數(shù)組使用foreach循環(huán)還是for循環(huán)?
- 關(guān)于JAVA 數(shù)組的使用介紹
- Java中對(duì)象數(shù)組的使用方法詳解
- Java使用選擇排序法對(duì)數(shù)組排序?qū)崿F(xiàn)代碼
- java使用數(shù)組和鏈表實(shí)現(xiàn)隊(duì)列示例
- Java中使用數(shù)組實(shí)現(xiàn)棧數(shù)據(jù)結(jié)構(gòu)實(shí)例
- Java二維數(shù)組簡(jiǎn)單定義與使用方法示例
- Java中數(shù)組的使用與注意事項(xiàng)詳解(推薦)
相關(guān)文章
String StringBuilder StringBuffer區(qū)別以及源碼分析
string是C++標(biāo)準(zhǔn)庫(kù)的一個(gè)重要的部分,主要用于字符串處理??梢允褂幂斎胼敵隽鞣绞街苯舆M(jìn)行string操作,同時(shí),C++的算法庫(kù)對(duì)string類(lèi)也有著很好的支持,并且string類(lèi)還和c語(yǔ)言的字符串之間有著良好的接口2021-06-06
Java?8函數(shù)式接口之Consumer用法示例詳解
這篇文章主要為大家介紹了Java?8函數(shù)式接口之Consumer用法示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07
Java注解處理器學(xué)習(xí)之編譯時(shí)處理的注解詳析
編譯時(shí)注解相信對(duì)每一個(gè)java開(kāi)發(fā)者來(lái)說(shuō)都不陌生,下面這篇文章主要給大家介紹了關(guān)于Java注解處理器學(xué)習(xí)之編譯時(shí)處理的注解的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來(lái)一起看看吧2018-05-05
SpringBoot基于HttpMessageConverter實(shí)現(xiàn)全局日期格式化
這篇文章主要介紹了SpringBoot基于HttpMessageConverter實(shí)現(xiàn)全局日期格式化,使用Jackson消息轉(zhuǎn)換器,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2018-12-12
熟練掌握J(rèn)ava8新特性之Stream API的全面應(yīng)用
Stream是Java8的一大亮點(diǎn),是對(duì)容器對(duì)象功能的增強(qiáng),它專(zhuān)注于對(duì)容器對(duì)象進(jìn)行各種非常便利、高效的 聚合操作(aggregate operation)或者大批量數(shù)據(jù)操作。Stream API借助于同樣新出現(xiàn)的Lambda表達(dá)式,極大的提高編程效率和程序可讀性,感興趣的朋友快來(lái)看看吧2021-11-11

