Tamping Java Foundation-Reflection

Posted by digitalgod on Sat, 11 May 2019 18:47:44 +0200

A week has passed since the nested Fragment s lazily loaded articles. To tell the truth, lately there has been a slight decrease in enthusiasm for learning, or a slight drift of myself, or a slight increase in temptation in reality, but this is a bad state and you have to adjust yourself to keep moving towards your goals.

Preface

This article will restore the reflection knowledge in Java basics. Since the author developed mobile Android, he can use fewer reflections in his daily work by counting the number of fingers.Now the last time I remember using it was when I changed the underline width of TabLayout.The main reason this recapture reflects this part of the knowledge is actually annotations and dynamic proxies.In contrast to reflection, these two are common in frameworks we use every day, such as EventBus, ButterKnife, Retrofit, and even AOP (Face Oriented Programming) in Android.

This article actually has no nutritional value. Like other related articles, this article is more about recording the reflected API s, which is more likely to be forgotten for easy reference in the future.This paper will record from the following aspects:

  1. What is reflection?What does reflection mean?
  2. Reflected Entry - Class Class
  3. Reflected member variable acquisition - Field class
  4. Reflected method acquisition - Method class
  5. Construction method acquisition of reflection - Constructor class
  6. Reflected Get Annotation Related Information

Reflection mechanism

Trying to explain why reflection is necessary is not easy. There are some jvm class loading mechanisms involved. From the previous point of view of modifying the underline width of TabLayout, first TabLayout is a class that exists in the SDK and is not defined by us, but in use we encounter the need to modify its content, which we get when the program runs through reflectionIt takes its internal private variable mTabStrip and modifies its LayoutParams.We know that we cannot access it through tablayout.mTabStrip because variables are private.

Field tabStrip = tablayout.getDeclaredField("mTabStrip");
tabStrip.setAccessible(true);
...
do reset padding LayoutParams operation
//Copy Code

Of course, this is just a usage scenario I've encountered, and friends who are JavaWeb users should know more about it.

So here's the official statement of what reflection is:

Reflection allows us to get information about members and members of each type in a program or assembly at run time.The Java reflection mechanism dynamically creates an object and invokes its properties, even if the type of the object is unknown at compile time.

Through Java's reflection mechanism we can:

  1. Determine at run time which class any object belongs to;
  2. Construct an object of any class at run time;
  3. Get the member variables and methods that any class has at run time, even if it is private;
  4. Call the method of any object at run time;

Note the preconditions and focus that we have mentioned is runtime.

Reflected Entry - Class Class

How to get Class classes

To manipulate the members or methods of a class through reflection, Java provides us with a set of API s that belong to the Class class. As the entry point for some column reflection operations, Java provides three methods or obtains a Class object of an object or class:

  • Using Object's getClass method, if we know an object of a class, we can use the superclass getClass method to get:
TabLayout tabLayout = findViewById(R.id.tabLayout);
Class<?> tabLayoutClass = tabLayout.getClass();
//Copy Code
  • We can also get a Class object of a class directly by XXX.class without creating an object of that class.
//For classes that normally refer to data types, we can call the following
Class<?> tabLayoutClass = TabLayout.class;

//For basic data types we can use XXX.TYPE to call
Class<?> classInt = Integer.TYPE;
//Copy Code
  • ** Created by passing in the fully qualified name of a class through the Class static method Class.forName(String name) method.**This method will require a ClassNotFoundException exception to be thrown or caught.
Class c3 = Class.forName("android.support.design.widget.TabLayout");
//Copy Code

Obtain member variables of the target class through reflection

Class classes can help us get member variables of a class when we only know its name, even if some member variables are private.We assume that we only know the name of a class and not its internal composition, that is, there is no API list available to us internally.The Java Class class provides four ways to get these member variables.

Method Name return type Whether to include inherited attributes Is Private Property Available
getField() Field YES NO
getDeclaredField() Field NO YES
getFields() Field[] YES NO
getDeclaredFields() Field[] NO YES
  • Test getField() / getDeclaredField()
  Class<TopStudent> topStudentClass = TopStudent.class;
  
  //id public property defined in parent class Student
  Field id = topStudentClass.getField("id");
  //The grade public attribute is defined in TopStudent
  Field grade = topStudentClass.getField("grade");
  //isReal private property definition cannot be obtained in TopStudent Private property will throw NoSuchFieldException: isReal
  Field isReal = topStudentClass.getField("isReal");

  //id public property definition cannot get public parent property java.lang.NoSuchFieldException in parent Student: ID
  Field  declaredId = topStudentClass.getDeclaredField("id");
  //The grade public attribute is defined in TopStudent
  Field declaredGrade = topStudentClass.getDeclaredField("grade");
  //The isReal private attribute is defined in TopStudent
  Field declaredIsReal = topStudentClass.getDeclaredField("isReal");
//Copy Code
  • Test getFields() / getDeclaredFields()
  Field[] fields = topStudentClass.getFields();
  Field[] declaredFields = topStudentClass.getDeclaredFields();

  //fields = [public int Reflection.TopStudent.grade, public int Reflection.Student.id]
  System.out.println("fields = " + Arrays.toString(fields));

  // grade  id
  for (Field field : fields) {
      System.out.println("" + field.getName());
  }

  //declaredFields = [private boolean Reflection.TopStudent.isReal, public int Reflection.TopStudent.grade]
  System.out.println("declaredFields = " + Arrays.toString(declaredFields));
  //isReal grade
  for (Field field : declaredFields) {
      System.out.println("" + field.getName());
  }
//Copy Code

In fact, after we get the property through reflection, the next step may be to get or modify the value of the property, and the Field class also has set and get methods for us.IllegalAccessException exceptions are thrown when the set method acts on private properties at the same time.At this point we need to pass.

At the same time, if we don't know the type of the attribute beforehand, we can also get the type of the attribute by getType/getGenericType, which gets the generic common symbol when the attribute is a generic representation. If not, the return value is the same as the content of the getType.

  TopStudent topStudent = topStudentClass.newInstance();

  Field grade = topStudentClass.getDeclaredField("grade");
  
  grade.set(topStudent, 4);
  //Can not set int field Reflection.TopStudent.grade to java.lang.Float
 // grade.set(topStudent,4.0f);
  System.out.println("grade = " + grade.get(topStudent));

  Class<?> type = grade.getType();
  Type genericType = grade.getGenericType();
  System.out.println("type = " + type);
  System.out.println("genericType = " + genericType);
  //If we know that the corresponding variable is a basic type of variable, we can use the setXXX equivalent method
   grade.setInt(topStudent,4);
  //Can not set int field Reflection.TopStudent.grade to (float)4.0
  //grade.setFloat(topStudent,4);


 //Remember to set isAccessible to true when setting values for private properties again
  Field isReal = topStudentClass.getDeclaredField("isReal");
  isReal.setAccessible(true);
  // If isReal.setAccessible(true) is not set;
  // can not access a member of class Reflection.TopStudent with modifiers "private" exception will be thrown
  isReal.set(topStudent, true);
  boolean isRealValue = (boolean) isReal.get(topStudent);
  System.out.println("isRealValue = " + isRealValue);

  int gradeValue = grade.getInt(topStudent);
  System.out.println("gradeValue  " + gradeValue);
//Copy Code

IllegalArgumentException exception caused by auto-packing

It's worth noting that when I reflect a class with a wrapper class whose property is the basic data type, we can't set the value directly using setXXX, throw out java.lang.IllegalArgumentException, and use set(Object obj,Object val) to run directly because setInt and other methods can't do automatic boxing for us, while the latter can:

 // Test Automatic Unpacking
  Field code = topStudentClass.getField("code");
  //Successful packing
  code.set(topStudent,100);
  //Can not automatically box Can not set java.lang.Integer field Reflection.Student.code to (int)200
  code.setInt(topStudent,200);
  int codeVal = (int) code.get(topStudent);
  System.out.println("codeVal = " + codeVal);
//Copy Code

How to modify a final modifier variable

From a coding point of view, if we define a member variable as final, we don't want anyone to modify its value, but who can take into account the actual requirements?Fortunately, we can also modify a final member variable by reflection.Of course, there are many things to note.

For Java basic data types and member variables assigned with String str = "111", they are optimized inline by the JVM at compile time, which is simply understood as being written to the.class file after compilation, and we cannot modify the member variables of a successful final.

Successful modifications can be made in cases other than those described above

as

//Test set final

public class Student {
    public final int id  = 30;
    public final Integer cod  = 90;
    public static final int ID = 1000;
    ...
}

Field id = topStudentClass.getField("id");
// If setAccessible(true) is not set, IllegalAccessException will be thrown
// Setting settAccessible will bypass checks
id.setAccessible(true);
id.set(topStudent,100);
int idVal = (int) id.get(topStudent);
System.out.println("idVal = " + idVal);//100
System.out.println("idVal = " + topStudent.id);//30 Modification failed

Field code = topStudentClass.getField("code");
code.setAccessible(true);
code.set(topStudent,100);
int codeVal = (int) code.get(topStudent);
System.out.println("codeVal = " + codeVal);//100
System.out.println("codeVal = " + topStudent.code);//100 modified successfully


Field ID = topStudentClass.getField("ID");

//IllegalAccessException is thrown even if setAccessible(true) is set
//ID.setAccessible(true);

//Remove final modifications of corresponding member variables by reflection
Field modifiersField = Field.class.getDeclaredField("modifiers"); 
modifiersField.setAccessible(true);
modifiersField.setInt(ID, ID.getModifiers() & ~Modifier.FINAL); 

ID.set(topStudent,100);
int IDVal = (int) id.get(topStudent);
System.out.println("IDVal = " + IDVal);//100
System.out.println("IDVal = " + TopStudent.ID);//1000 Modification Failed
//Copy Code

In conclusion, there is nothing wrong with final in case someone digs a big pit for himself and buries it.

Membership methods for target classes by reflection

Get member method of target class

In addition to getting member variables through Class, reflection can also get all member methods of a class.As with back-member variables, there are four ways to get members:

Method Name return type Whether to include the get parent method Is Private Method Available
getMethod() Method YES NO
getDeclaredMethod() Method NO YES
getMethods() Method[] YES NO
getDeclaredMethods() Method[] NO YES

Suppose we set up two classes, which are as follows:

public class Student {
    .....
    private String name;
    .....
    
    public String getName() {
        System.out.println("I am Student Of  public Method");
        return name;
    }

    private void testPrivate(){
        System.out.println("I am Student Of private Method");
    }
}

public class TopStudent extends Student {
    private boolean isReal;
    public int grade;

    public boolean isReal() {
        System.out.println("I am TopStudent Of public Method");
        return isReal;
    }

    private void testSelfPrivate(){
        System.out.println("I am TopStudent Of private Method");
    }
}

//Copy Code

We try to get the methods contained in the TopStudent class with getMethods()/getDeclaredMethods():

private void testGetMethod() {
   Class<TopStudent> topStudentClass = TopStudent.class;
   Method[] methods = topStudentClass.getMethods();
   Method[] declaredMethods = topStudentClass.getDeclaredMethods();

   for (Method method: methods) {
       System.out.println(method.getName());
   }

   System.out.println("---------");

   for (Method method: declaredMethods) {
       System.out.println(method.getName());
   }
}
//Copy Code

From the printed results, you can see that the getMethods() method contains all Public methods for all parent classes and the current class, while the getDeclaredMethods() method contains only all methods for the current class.It seems that there is no way to get the parent's private method, what?Subclasses simply cannot inherit the parent's private methods.

/*topStudentClass.getMethods Gets a method that contains all Public methods for all parent and current classes*/

public boolean Reflection.TopStudent.isReal()
public java.lang.String Reflection.Student.getName()
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()

/*topStudentClass.getDeclaredMethods The method obtained only contains all methods of the current class*/

boolean Reflection.TopStudent.isReal()
private void Reflection.TopStudent.testSelfPrivate()Method*/

//Copy Code

Similarly, we can get a single method of a class that is provided to us by Class with two parameters, getMethod and getDeclaredMethod, the first parameter being the method name "name" and the second parameter being the Class object of the parameter that the corresponding method requires, namely "Class<?>... parameterTypes".When we try to get a method that does not exist, we throw a NoSuchMethodException exception.

We add two methods for TopStudent to test

public void testParams(String p1,int p2){}

public void testParams(double p){}
//Copy Code
   try {
       // getMethod can normally acquire its own and parent's public methods
       Method isRealMethod = topStudentClass.getMethod("isReal");
       Method getNameMethod = topStudentClass.getMethod("getName");

       // Attempting to get a private method throws a java.lang.NoSuchMethodException exception
       Method testSelfPrivateMethod = topStudentClass.getMethod("testSelfPrivate");
       Method testPrivateMethod = topStudentClass.getMethod("testPrivate");


       //The method trying to get the parent class throws a NoSuchMethodException exception
       Method getNameDeclareMethod = topStudentClass.getDeclaredMethod("getName");

       // getDeclaredMethod can get private methods thrown and public methods
       Method isRealDeclareMethod = topStudentClass.getDeclaredMethod("isReal");
       Method testSelfPrivateDeclareMethod = topStudentClass.getDeclaredMethod("testSelfPrivate");
        
        //Test of overloaded methods
        Method testParams1 = topStudentClass.getMethod("testParams", double.class);
       Method testParams2 = topStudentClass.getMethod("testParams", String.class, int.class);
       //Getting an overloaded method that does not exist throws java.lang.NoSuchMethodException
       Method testParams3 = topStudentClass.getMethod("testParams");

   } catch (NoSuchMethodException e) {
       e.printStackTrace();
   }
//Copy Code

Call member method of target class

Because we mentioned the difference between getMethod and getDeclaredMethod above, we need to use the corresponding method in order to get the corresponding method out of use normally.

Once we get the member Method that specifies the Class, we can use the

Object invoke(Object obj, Object... args)

Method calls the method of the object of the specified class.The first parameter is an object of the class, the second variable parameter is a parameter of the method, and the return value, which is the return value of the invoked method, usually requires us to force the conversion to the specified parameter type.And we can also get the return value type through Method's getReturnType method.

It is also important to note that the private member method is as accessible as the private variable, but when we need to access the modifications, we have to bypass the permission check, which is set: method.setAccessible(true)

Let's take an example:


//Add testParams overload method for TopStudent
public String testParams(int p) {
   System.out.println("I am TopStudent Of testParams(int p) Method ," + " p = " + p);
   return String.valueOf(p * 100);
}

try {
       Class<TopStudent> topStudentClass = TopStudent.class;
       TopStudent topStudent = topStudentClass.newInstance();
       
       //Call the public method
       Method isRealDeclareMethod = topStudentClass.getDeclaredMethod("isReal");
       isRealDeclareMethod.invoke(topStudent);

       //Calling a private method must bypass permission checks, that is, you need to set the setAccessible property of the corresponding Method object to true
       Method testSelfPrivateDeclareMethod = topStudentClass.getDeclaredMethod("testSelfPrivate");
       testSelfPrivateDeclareMethod.setAccessible(true);
       testSelfPrivateDeclareMethod.invoke(topStudent);
       
       Method testParams1 = topStudentClass.getMethod("testParams", double.class);

       //Incoming wrong parameter type will throw IllegalArgumentException exception
       //testParams1.invoke(topStudent,"200");
       testParams1.invoke(topStudent, 200);

       Method testParams2 = topStudentClass.getMethod("testParams", String.class, int.class);
       testParams2.invoke(topStudent, "test", 200);

       Method testParams3 = topStudentClass.getMethod("testParams", int.class);
       Class<?> returnType = testParams3.getReturnType();
        //returnType = class java.lang.String
       System.out.println("returnType = " + returnType);
       String result = (String) testParams3.invoke(topStudent, 200);//result = 20000

       System.out.println("result = " + result);
       
  } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
       e.printStackTrace();
   }
//Copy Code

Obtain the constructor of the target class by reflection

There are also four ways to get a constructor from reflection, which are

Method Name return type Is Private Method Available
getConstructor() Constructor<?> NO
getDeclaredConstructor() Constructor<?> YES
getConstructors() Constructor<?>[] NO
getDeclaredConstructors() Constructor<?>[] YES

For the acquisition of construction methods, it is not indicated whether the parent's construction method can be obtained, because java specifies that subclasses cannot inherit the parent's construction method.For access modifier restrictions, there is no difference from the normal member function above.

For example:

   Class<TopStudent> topStudentClass = TopStudent.class;

   Constructor<?>[] constructors = topStudentClass.getConstructors();
   for (Constructor constructor: constructors) {
       System.out.println("constructor = " + constructor);
   }

   Constructor<?>[] declaredConstructors = topStudentClass.getDeclaredConstructors();
   for (Constructor constructor: declaredConstructors) {
       System.out.println("constructor = " + constructor);
   }
   
   
   try {
       Constructor<TopStudent> isRealConstructor = topStudentClass.getConstructor(boolean.class);
       System.out.println("isRealConstructor = " + isRealConstructor);

       Constructor<TopStudent> gradeConstructor = topStudentClass.getDeclaredConstructor(int.class);
       System.out.println("gradeConstructor = " + gradeConstructor);

       TopStudent topStudent = isRealConstructor.newInstance(false);
       System.out.println("topStudent.isReal = " + topStudent.isReal()); 
    }catch (NoSuchMethodException) {
        e.printStackTrace();
    }
       
//Copy Code

Run Results

constructor = public Reflection.TopStudent(boolean,int)
constructor = public Reflection.TopStudent(boolean)

constructor = public Reflection.TopStudent(boolean,int)
constructor = private Reflection.TopStudent(int)
constructor = public Reflection.TopStudent(boolean)

isRealConstructor = public Reflection.TopStudent(boolean)
gradeConstructor = private Reflection.TopStudent(int)

//Copy Code

We've said before that you can create an object of a class with Class.newInstance(), but if a class does not provide a constructor for null parameters, it will throw an InstantiationException exception.At this point, we can call Constructor.newInstance(Object... obj) by getting the corresponding Constructor object by getting other parameter constructors.

This method accepts an object of the parameter type of the corresponding constructor and throws IllegalArgumentException if the number of parameters passed and the type error is not correct, similar to the exception thrown by the invoke method.

try {

        // If there is no empty constructor, an InstantiationException exception will be thrown
        //  TopStudent topStudent = topStudentClass.newInstance();
       TopStudent topStudent = isRealConstructor.newInstance(false);
       System.out.println("topStudent.isReal = " + topStudent.isReal());

       //The corresponding Constructor must be set to setAccessible(true) when calling a private constructor
       gradeConstructor.setAccessible(true);
       TopStudent topStudent1 = gradeConstructor.newInstance(1000);
       System.out.println("topStudent.grade = " + topStudent1.grade);
       //A java.lang.IllegalArgumentException is thrown when the number of arguments passed in is incorrect
       TopStudent errorInstance = isRealConstructor.newInstance(false, 100);


   } catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) {
       e.printStackTrace();
   }
//Copy Code

Type Judgment by Class

When reflection is not used, we use the instanceof keyword to determine whether it is an instance of a class.When we get a Class object of an object from the above method, we can also use the isInstance() method of the Class object to determine whether it is an instance of a class. It is a Native method, which is used as follows:

try {
       Class<Student> studentClass = Student.class;
       //Full path naming of internal classes in java is OuterClassName$InnerClassName
       Class<?> tearcherClass = Class.forName("ReflectionTest$Teacher");

       Teacher teacher = new Teacher();
       //tearcherClass.isInstance(teacher) true 
       System.out.println("tearcherClass.isInstance(teacher)" + tearcherClass.isInstance(teacher));
   } catch (ClassNotFoundException e) {
       e.printStackTrace();
   }
//Copy Code

Obtaining annotation-related information through reflection

At the beginning, it is mentioned that learning reflex may be more intended not for everyday development, but for learning the source code of some tripartite libraries where reflection is often accompanied by annotations. In this article, we will not start with annotations, but simply with the Annotation-related reflection API:

First, there is an interface under the java.lang.reflect package that is very relevant to extracting annotations. It is the AnnotatedElement interface. What objects implement the interface?In fact, it contains the classes we call Class, Constructor, Field, Method.In fact, it's not difficult to understand which members a comment can modify. A comment can modify a class, a method, or a member, so when we need to customize a comment, it's important to get the corresponding member or class's comment.

The AnnotatedElement interface defines several methods:

Method Name parameter Return value Effect
isAnnotationPresent Annotation Modified Element Class boolean Detects whether the element is modified by the corresponding comment for the parameter
getAnnotation Annotation Modified Element Class Annotation Return Annotation Object
getAnnotations nothing Annotation[] Returns all comments that exist on the program element (if no comments exist on this element, an array of zero length is returned.)
       
getDeclaredAnnotations nothing Annotation[] Returns all comments that exist directly on this element.Unlike other methods in this interface, this method ignores the inherited annotations.(If no comment exists directly on this element, an array of zero length is returned.)

Here's a simple example of how to get annotations through reflection:

Suppose we have a custom comment like this:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
protected @interface FruitName {
   String value();
}
//Copy Code

And we've defined an Apple:

public class Apple {
   @FruitName(value = "Apple")
   public String name;
}
//Copy Code

There are also the following ways to view annotation information:

public static void getFruitInfo(Class<?> clazz) {
   try {
       String fruitName = "Fruit name:";

       Field field = clazz.getDeclaredField("name");
       java.lang.annotation.Annotation[] annotations = field.getAnnotations();
       System.out.println("annotations = " + Arrays.toString(annotations));
       if (field.isAnnotationPresent(FruitName.class)) {
           FruitName fruitNameAnno = field.getAnnotation(FruitName.class);
           fruitName = fruitName + fruitNameAnno.value();
           System.out.println(fruitName);
       }
   } catch (NoSuchFieldException e) {
       e.printStackTrace();
   }
}
//Copy Code

The printout is

annotations = [@Annotation$FruitName(value=Apple)]
Fruit name: Apple
Copy Code

summary

This article analyzes the use of some common reflection APIs.These are not all APIs.There are many other reflective explanations on the web that are also good.The starting point of this article is to make a difference, but it is written that "blend with the dirt".While there is a strong connection between these two points of knowledge and reflection, both can be written independently.So only if this is a learning record, it is easy to read later.Finally, release some good reflection articles I've seen.

Some good reflection introductory articles:

Deep understanding of Java reflection (1) - Basics Java Reflection From Shallow to Deep|Advanced Requirements JAVA Reflection and Annotation Application of Reflection Technology in android Reflection in detail, the barriers Java and Android developers must cross Thinking in JavaJava Core Technology Volume I

Topics: Programming Java Attribute Android jvm