What is reflection
"Reflection allows programs running in the JVM to detect and modify the behavior of the runtime." This concept is often confused with introspection. Here are the explanations of these two terms in Wikipedia:
Introspection example: The instanceof operator is used to detect whether an object belongs to a specific class.
if (obj instanceof Dog) { Dog d = (Dog) obj; d.bark();}Reflection example: The Class.forName() method can obtain the corresponding Class object through the name of the class or interface (a string or fully qualified name). The forName method triggers the initialization of the class.
// Use reflection Class<?> c = Class.forName("classpath.and.classname");Object dog = c.newInstance();Method m = c.getDeclaredMethod("bark", new Class<?>[0]);m.invoke(dog);In Java, reflection is closer to introspection because you can't change the structure of an object. Although some APIs can be used to modify the visibility of methods and properties, they cannot modify structures.
Reflection of arrays
What is the use of reflection of an array? When do you need to use reflection of arrays? Let’s take a look at the following code:
Integer[] nums = {1, 2, 3, 4}; Object[] objs = nums; //There can be automatically converted into Integer[] to Object[] Object obj = nums; //Integer[] is of course an Object int[] ids = {1, 2, 3, 4}; //Object[] objs2 = ids; //The int[] cannot be converted to Object[] Object obj2 = ids; //int[] is an ObjectThe above example shows that a basic type of one-dimensional array can only be regarded as an Object, not as an Object[].
int[][] intArray = {{1, 2}, {3, 4}}; Object[] oa = intArray; Object obj = intArray; //Integer[][] integerArray = intArray; int[][] not Integer[][] Integer[][] integerArray2 = new Integer[][]{{1, 2}, {3, 4}}; Object[][] oa2 = integerArray2; Object[] oa3 = integerArray2; Object obj2 = integerArray2;From the above example, we can see that the two-digit array of java is an array of arrays. Let’s take a look at the example of reflecting an array:
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)); /*The above two outputs are strings similar to "[[I@14318bb]", which cannot display the content stored in the array. Of course, we use traversal to output the contents in the array*/ System.out.println("--1. traversal to the array by printing --"); for(int i = 0; i < is.length; i++) { System.out.print(is[i] + " "); } System.out.println(); System.out.println("--2. traversal to the array by printing --"); Object obj = is; //Convert the one-dimensional int array to Object System.out.println("obj isArray:" + obj.getClass().isArray()); for(int i = 0; i < Array.getLength(obj); i++) { int num = Array.getInt(obj, i); //You can also use this commonly used method to get the value of the corresponding index position//Object value = Array.get(obj, i); //If the array stores the basic type, then the wrapper type corresponding to the basic type System.out.print(num + " "); } } }Output:
[I@14318bb [[I@14318bb] --1. Print the array by traversing the array by conventional means- 58 55 93 61 61 29 68 0 22 7 --2. Print the array by traversing the array by traversing the array by traversing the array- obj isArray:true 58 55 93 61 61 29 68 0 22 7
The above example first creates a one-dimensional array of int, and then randomly fills an integer of 0~100. Then, directly output the array through the System.out.println() method or use the Arrays.asList method (if it is not a one-dimensional array of basic types, this method can be converted to List as expected, and if it is a two-dimensional array, it cannot be converted to List as we expect). The array is converted to List and then output, and the passing is not the output result we expect. Next, the contents in the array are output in a regular array traversal method, and then treat int[] as an Object, and use reflection to traverse its content. Class.isArray() can be used to determine whether an object is an array. If it is an array, then the relevant information of the array is obtained through java.lang.reflect.Array, a tool class that reflects the array. This class uses some get methods to obtain the length of the array, each version of which is used to obtain the corresponding index of the one-dimensional array of basic types, a general method to obtain the value (Object array, int index), a method to set the value, and two methods to create array instances. Through the array reflection tool class, it is easy to use array reflection to write general code without having to judge which basic type of array the given array is.
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 has passed 2 methods to create an array
Object newInstance(Class<?> componentType, int length), create an array of specified length based on the provided class. If int.class is provided as above, the length is 10, which is equivalent to new int[10];
Object newInstance(Class<?> componentType, int... dimensions), creates an array based on the provided class and dimensions. The variable parameter dimensions are used to specify the length of each dimension of the array. As in the example above, it is equivalent to creating a two-dimensional array of new int[10][8], but it cannot create a multi-dimensional array with different lengths of each dimension. Through the first method of creating an array, you can create an array like this. Object o = Array.newInstance(int[].class, 20) can be used to create a two-dimensional array, which is equivalent to Object o = new int[20][];
Of course, it is rare to use the above example to create an array, but it is actually redundant. Why not create an array directly through new? Reflection creates an array not only faster than new, but also the program written is not easy to read, so it is not as direct as new. In fact, it is really rare to create arrays through reflection. What kind of abnormal needs are there to use reflection to create arrays!
Since some obstacles were encountered when outputting an array of basic types, the following will use array reflection to implement a tool class to achieve the desired output:
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; } /** * Determine whether a given object is a wrapper class of the basic type. * @param o Given Object object* @return If it is a wrapper class of primitive types, return yes, otherwise return No. */ 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(); } }The Print tool class above provides some practical static methods for output, and provides some overloaded versions. You can write some overloaded versions based on your personal preference, supporting the printing of basic types of one-dimensional arrays and multi-dimensional arrays. See the following examples of the Print tool testing:
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 non-array--"); print(new Object()); print("--print one-dimensional array of basic types--"); int[] is = new int[]{1, 22, 31, 44, 21, 33, 65}; print(is); print("--print two-dimensional array of basic types--"); int[][] iss = new int[][]{ {11, 12, 13, 14}, {21, 22,}, {31, 32, 33} }; print(iss); print("-print one-dimensional array of non-base types--"); Person[] persons = new Person[10]; for(int i = 0; i < persons.length; i++) { persons[i] = new Person(); } print(persons); print(persons); print("--print a two-dimensional array of non-primitive types--"); Person[][] persons2 = new Person[][]{ {new Person()}, {new Person(), new Person()}, {new Person(), new Person(), new Person(), new Person(),}, }; print(persons2); print("--print an empty array--"); print(new int[]{}); print("--print an array with null values--"); Object[] objects = new Object[]{ new Person(), null, new Object(), new Integer(100) }; print(objects); print("--Print two-dimensional array for special cases--"); Object[][] objects2 = new Object[3][]; objects2[0] = new Object[]{}; objects2[2] = objects; print(objects2); print("--Output the result of the one-dimensional array to the file--"); PrintStream out = new PrintStream("out.c"); try { print(iss, out); } finally { out.close(); } print("--format output--"); format("%-6d%s %B %s", 10086, "is", true, iss); /** * Some commonly used methods of Print tool classes are listed above, * There are also some unlisted methods, please check it yourself. */ } } Output:
--Print non-array -- java.lang.Object@61de33 --Print one-dimensional array of basic types -- [1, 22, 31, 44, 21, 33, 65] --Print two-dimensional array of basic types -- [[11, 12, 13, 14], [21, 22], [31, 32, 33]] --Print one-dimensional array of non-base type -- [Person0, Person1, Person2, Person3, Person4, Person5, Person6, Person7, Person8, Person9] --Print two-dimensional array of non-base type -- [[Person10], [Person11, Person12], [Person13, Person14, Person15]] --Print empty array -- [] --Print array with null values -- [Person16, null, java.lang.Object@ca0b6, 100] --Print two-dimensional array in special cases -- [[], null, [Person16, null, java.lang.Object@ca0b6, 100]] --Print the result of the one-dimensional array to a file -- - Format output -- 10086 is TRUE [[11, 12, 13, 14], [21, 22], [31, 32, 33]]
Output file:
It can be seen that the Print tool class already has the ability to print basic types of one-dimensional arrays and multi-dimensional arrays. Generally speaking, the above tool class is quite practical, so as not to manually write code every time you want to see the contents in the array. That would be too troublesome. Just use the Print tool class in the future. How convenient.
The tool class above does work very well, but if there is a requirement: give you an array (and maybe other containers), you can create a List for me. So what should we do? In fact, Arrays.asList does not always get the results we expect. Although java5 adds generics, it has limitations and cannot be as general as C++ templates. It is precisely because there are basic types in Java. Even if there is an automatic wrapping mechanism, it cannot be used with generics. The parameter type must be of a certain type, not a basic type. Here is a solution for your own:
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; //Or arrays of primitive types cannot be converted like this 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(); } } }Test code:
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("---basic type one-dimensional array--"); int[] nums = {1, 3, 5, 7, 9}; List<?> list = CollectionUtils.asList(nums); System.out.println(list); System.out.println("--Not basic type one-dimensional array--"); 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(personList); System.out.println("--Iterator--"); Iterator<Person> iterator = personList.iterator(); List<Person> personList2 = (List<Person>) CollectionUtils.asList(iterator); System.out.println(personList2); } }Output:
--Basic type one-dimensional array-- [1, 3, 5, 7, 9] --Non-basic type one-dimensional array-- [Person0, Person1, Person2] --Iterator-- [Person0, Person1, Person2]
In the Java container class library, it can be divided into Collection, Map, and array. Since Iterator (and the early legacy interface Enumeration) is the general interface of all containers and the Collection interface is from Iterable (the iterator of this interface will return an Iterator), these situations are processed one by one in the makeIterator method. For Map types, only the entrySet() method needs to be called. For classes that implement the Iterable interface (including Collection), call iterator() to directly obtain the Iterator object. For Enumeration types, use the adapter EnumerationIterator for adaptation. For arrays, use the array reflection to traverse the array into ArrayList, and call the Arrays.asList() method to create a List for other types. CollectionUtils also provides some other methods to convert, and you can add the methods you need as needed.
Summary: Array reflection provides more convenient and flexible methods for designs where arrays may appear, so as not to write more troublesome judgment statements. This flexibility pays the price of performance, and it is really not necessary to use array reflection when array reflection is not needed at all. Whether to use array reflections is different in actual development. Choose whether to use array reflections according to needs. The best way is to explore the way through practice, write in the way you think of, and constantly improve in practice.