[Java Technology Topic] "Breakthrough of Technical Blind Spots" guides you to break through the dynamic technical principles of Java technology blind spots that you may have (reflection technology topic)

The relevant technical guidelines of this series of technical topics mainly include the following three aspects:

Types of programming languages

Learning a new dynamically typed language can take a long time, making developers who are already familiar with Java more likely to continue using Java to solve problems. However, Java itself supports dynamism, which can play a role in some occasions that require flexibility. An example in Java is the reflection API, which can look up and call methods by method name at runtime. The Java language is also constantly updated to improve support for dynamism and flexibility.

The overall programming language is divided into three categories: static type language and dynamic type language, semi-static semi-dynamic type language.

 

statically typed language

The Java language is a statically typed programming language, that is, type checking is performed at compile time. In Java, the type of each variable needs to be explicitly specified at the time of declaration; the types of all variables, method parameters, and return values ​​must be determined before the program runs. This static type feature enables the compiler to perform extensive type checking at compile time, thereby discovering obvious type errors in the code. However, this also means that the code contains a lot of unnecessary type declarations, making the code too verbose and inflexible. Correspondingly, the type checking of dynamically typed languages ​​(such as JavaScript and Ruby, etc.) is performed at runtime. In such languages, the types of variables in source code can be determined dynamically at runtime.

dynamically typed language

Compared with statically typed languages, the type checking of dynamically typed languages ​​(such as JavaScript and Ruby, etc.) is performed at runtime. In such languages, types do not need to be explicitly declared in the source code, so code written in a dynamically typed language is more concise. The popularity of dynamically typed languages ​​in recent years also reflects the importance of dynamism in languages. Proper dynamism is very helpful for improving development efficiency because it reduces the amount of code that developers need to write.

Technology Core Direction

Although Java is a statically typed language, it also provides dynamic features that make code more flexible. These features include scripting language support API, reflection API, dynamic proxies, and dynamic language support introduced in JSR292. Developers can choose different ways to increase code flexibility. For example, you can use the scripting language support API to integrate the scripting language into Java programs, use the reflection API to dynamically call methods at runtime, use dynamic proxies to intercept interface method calls, or use method handles in JSR292 to achieve more functions. The method handle supports a variety of transformation operations and can meet the needs of different occasions.

 

reflection API

Reflection API is a dynamic support provided by the Java language, which allows programs to obtain the internal structure of Java classes, such as constructors, fields, and methods, and interact with them at runtime. The reflection API can also implement many useful functions commonly used in dynamic languages. According to the object-oriented thinking, the state of the object should be changed through the method instead of directly modifying the value of the property. The property setting and obtaining method names in Java classes usually follow the JavaBeans specification and are named after setXxx and getXxx. Therefore, you can write a tool class for setting and getting properties of any object that conforms to the JavaBeans specification.

Java's reflection API can be used to implement functions similar to those implemented in the JavaScript language, and there is not much difference in the amount of code. The implementation idea is to first find the method from the class of the object, and then call the method and pass in the parameters. This static method can be used in the program as a utility method.

public class ReflectSetter
   public static void invokeSetter(Object obj,String field,Object value) throws NoSuchMethodException,InvocationTargetException,IllegalAccessException{
     String methodName "set"+field.substring(0,1).toUppercase() + field.substring(1);
     class<?>clazz obj.getclass();
     Method method clazz.getMethod (methodName,value.getclass ())
     method.invoke (obj,value);
 }
}


It can be seen from the above examples that the reflection API can realize the flexible use of the Java language. In fact, the reflection API defines a loose contract between the provider and the user. This contract can only be established on the name and parameter type when the method is called, without first declaring the variable in the code. This method provides greater flexibility and dynamism, but it also requires the developer to ensure the legitimacy of the call. If the method call is invalid, the relevant exception will be thrown at runtime.

Reflection Case Introduction


The reflection API is often used when the method name or property name changes according to specific rules:

In the Servlet, use the reflection API to traverse all the parameters in the HTTP request, and then use the invokeSetter method to fill the property values ​​of the domain object.
In database operations, the scene of creating and populating domain objects from the query result set is also realized through the reflection API. These correspondences can be established through the reflection API.
Reflection function operation
Although the reflection API can bring flexibility to Java programs, its implementation mechanism will also bring performance costs. Invoking methods through reflection is generally an order of magnitude or two slower than writing them directly in source code. Although the performance of the reflection API has been improved with the improvement of the Java virtual machine, in some applications with high performance requirements, the reflection API needs to be used with caution.

 

get constructor


The construction method in the Java class can be obtained through the reflection API, so that the Java object can be dynamically created at runtime. Specific steps are as follows:

To get the object of the Class class, you can use the Class.forName method or the .class attribute of the class.
Obtain a list of all public constructors through the getConstructors method of the Class class, or use the getConstructor method to obtain public constructors based on parameter types. If you need to obtain the constructors actually declared in the class, you can use the getDeclaredConstructors and getDeclaredConstructor methods.
After obtaining the java.lang.reflect.Constructor object representing the construction method, you can obtain the name of the construction method through its getName method, the getParameterTypes method to obtain the parameter type of the construction method, and the getModifiers method to obtain information such as the modifier of the construction method.
Finally, new objects can be created using the newInstance method, which accepts a variable parameter list to pass the parameter values ​​​​of the constructor. If the constructor has no parameters, the newInstance method can be called directly.
It should be noted that using the reflection API to create objects is inefficient and should be avoided in scenarios with high performance requirements.

There is nothing special about the acquisition and use of general construction methods. What needs special explanation is the use of construction methods with variable parameter lengths and nested class construction methods.

Variable-Length Arguments - Constructor

If a construction method declares a variable-length parameter, you need to use the corresponding array type Class object to obtain the construction method, because the variable-length parameter is actually implemented through an array.

Use the reflection API to obtain variable-length constructors.
For example, if the constructor of a class VarargsConstructor contains variable-length parameters of String type, you need to use String[].class when calling the getDeclaredConstructor method, otherwise the constructor will not be found. When calling the newInstance method, the string array used as the actual parameter needs to be converted to the Object type first to avoid ambiguity when calling the method, so that the compiler knows to pass the string array as a variable-length parameter.

public class VarargsConstructor {
	public VarargsConstructor(String... names) {}
}

public void useVarargsConstructor() throws Exception { 
	Constructor<VarargsConstructor> constructor = VarargsConstructor.class.
		getDeclaredConstructor(String[].class);
	constructor.newInstance((Object) new String[]{"A", "B", "C"});
}


When obtaining the constructor of a nested class, it is necessary to distinguish between static and non-static cases.

 

Static nested classes can be used in the usual way.

A non-static nested class is special in that its object instance has an implicit object reference pointing to the outer class object that contains it. The existence of this implicit object reference allows the code in the non-static nested class to directly refer to the private fields and methods contained in the outer class. Therefore, when obtaining the constructor of a non-static nested class, the first value of the type parameter list must be the Class object of the outer class.

For example, for the non-static nested class NestedClass, when obtaining its constructor, the Class object of the outer class needs to be passed in as the first parameter, so as to pass the reference of the outer object when creating a new object.

static class StaticNestedClass {
	public StaticNestedClass(String name) {}
}
class NestedClass {
	public NestedClass(int count) {}
}
public void useNestedClassConstructor() throws Exception {
	Constructor< StaticNestedClass> sncc = StaticNestedClass.class. getDeclaredConstructor(String.class);
	sncc.newInstance("Alex");
	Constructor<NestedClass> ncc = NestedClass.class.getDeclaredConstructor(ConstructorUsage.class, int.class);
	NestedClass ic = ncc.newInstance(this, 3);
}


Get the Field field
Through the reflection API, you can get the field (field) in the class, including the public static field and the instance field in the object. Once you have obtained an object of the java.lang.reflect.Field class that represents a field, you can get and set the value of the field. Similar to the method of obtaining the construction method, there are 4 methods in the Class class to obtain fields, namely getFields, getField, getDeclaredFields and getDeclaredField.

The getFields method returns the public static fields and instance fields in the object; the
getField method returns the public static fields of the specified name or the instance fields in the object; the
getDeclaredFields method returns all fields in the class, including private static fields and instances in the object Field;
the getDeclaredField method returns the field of the specified name, including private static fields and instance fields in objects.
Use reflection API to get and use static fields and instance fields
Get and use examples of static fields and instance fields. The difference between the two is that you don't need to provide a specific object instance when using static fields, just use null.

In addition to the get and set methods for operating Object, the Field class also has corresponding methods for operating basic types, including getBoolean / setBoolean, getByte / setByte, getChar / setChar, getDouble / setDouble, getFloat / setFloat, getInt / setInt and getLong / setLong etc.

public void useField() throws Exception {
	Field fieldCount = FieldContainer.class.getDeclaredField("count");
	fieldCount.set(null, 3);
	Field fieldName = FieldContainer.class.getDeclaredField("name"); 
	FieldContainer fieldContainer = new FieldContainer(); 
	fieldName.set(fieldContainer, "Bob");
}


In general, it is relatively simple to get and set public fields in a class, but it is not possible to get or manipulate private fields through the reflection API.

Get the Method method

The most common use case for the reflection API is to get a method on an object and invoke that method at runtime. There are 4 methods in the Class class to obtain methods, namely getMethods, getMethod, getDeclaredMethods and getDeclaredMethod. These methods act like the corresponding methods for getting constructors and fields. By obtaining the object of the java.lang.reflect.Method class that represents the method, you can query the detailed information of the method, such as the parameters of the method and the type of the return value. Use the invoke method to pass in the actual parameters and call the method.

Example of getting and calling public and private methods in an object

public void useMethod() throws Exception { 		
	MethodContainer mc = new MethodContainer();
	Method publicMethod = MethodContainer.class.getDeclaredMethod("publicMethod");
	publicMethod.invoke(mc);
	Method privateMethod = MethodContainer.class.getDeclaredMethod("privateMethod");
	privateMethod.setAccessible(true);
	privateMethod.invoke(mc);
}


It should be noted that before calling a private method, you need to call the setAccessible method of the Method class to set the accessible permissions. Unlike constructors and fields, private methods in a class can be obtained through the reflection API.

Operate array

The method of using the reflection API to operate on arrays is different from that of ordinary Java objects. Need to use the utility class java.lang.reflect.Array to achieve. This class provides methods for creating arrays and manipulating array elements. The newInstance method is used to create a new array. The first parameter is the type of elements in the array, and the following parameters are the dimension information of the array.

String[] names = ( Array.newInstance(int.class, 3, 3, 3);
double[][][] arrays= (double[][][]) Array.newInstance(double[][].class , 2, 2);
1.
2.
Use the reflection API to manipulate arrays
For example, you can use the following sample code to create a one-dimensional String array of length 10 and a three-dimensional array of 3x3x3:

public void useArray() {
	String[] names = (String[]) Array.newInstance(String.class, 10);
	names[0] = "Hello"; 
	Array.set(names, 1, "World");
	String str = (String) Array.get(names, 0);
	int[][][] matrix1 = (int[][][]) Array.newInstance(int.class, 3, 3, 3);
	matrix1[0][0][0] = 1;
	int[][][] matrix2 = (int[][][]) Array.newInstance(int[].class, 3, 4);
	matrix2[0][0] = new int[10]; 
	matrix2[0][1] = new int[3]; 
	matrix2[0][0][1] = 1;
}


It should be noted that although only two dimensions are declared at the time of creation, matrix2 is actually a three-dimensional array because its element type is double.

Access rights and exception handling

Use the reflection API to bypass the default access control permissions in the Java language, such as accessing private methods declared in another class. This is achieved by calling the setAccessible method inherited from java.lang.reflect.AccessibleObject. When using the invoke method to call a method, if the method itself throws an exception, the invoke method will throw an InvocationTargetException exception to indicate this situation. You can use the getCause method of InvocationTargetException to get the real exception information for debugging.

In Java 7, all exception classes related to reflective operations have added a new parent class java.lang.ReflectiveOperationException, which can directly capture this new exception.

content summary

Java reflection technology allows the program to dynamically obtain class information, call class methods, access class attributes, etc. at runtime, thereby improving the flexibility and scalability of the program. It can obtain information such as class name, package name, parent class, interface, construction method, method, attribute, etc., create objects, call methods, access attributes, and implement dynamic proxy functions. Java reflection technology has important applications in framework development, ORM framework, dynamic proxy, unit testing and so on. However, since the use of reflection technology requires additional overhead, it should be avoided as much as possible in scenarios with high performance requirements.

Guess you like

Origin blog.csdn.net/Jernnifer_mao/article/details/131441630