JavaRush /Java Blog /Random EN /reflection API. Reflection. The Dark Side of Java
Oleksandr Klymenko
Level 13
Харків

reflection API. Reflection. The Dark Side of Java

Published in the Random EN group
Greetings, young Padawan. In this article I will tell you about the Force, the power of which java programmers use only in a seemingly hopeless situation. So, the dark side of Java is -Reflection API
Reflection API.  Reflection.  The Dark Side of Java - 1
Reflection in Java is done using the Java Reflection API. What is this reflection? There is a short and precise definition that is also popular on the Internet. Reflection (from Late Latin reflexio - going back) is a mechanism for studying data about a program during its execution. Reflection allows you to examine information about fields, methods, and class constructors. The reflection mechanism itself allows you to process types that are missing during compilation, but appear during program execution. Reflection and the presence of a logically coherent model for reporting errors makes it possible to create correct dynamic code. In other words, understanding how reflection works in java opens up a number of amazing opportunities for you. You can literally juggle classes and their components.
Reflection API.  Reflection.  The Dark Side of Java - 2
Here is a basic list of what reflection allows:
  • Find out/determine the class of an object;
  • Get information about class modifiers, fields, methods, constants, constructors and superclasses;
  • Find out which methods belong to the implemented interface/interfaces;
  • Create an instance of a class, and the name of the class is unknown until the program is executed;
  • Get and set the value of an object field by name;
  • Call an object's method by name.
Reflection is used in almost all modern Java technologies. It's hard to imagine whether Java as a platform could have achieved such enormous adoption without reflection. Most likely I couldn't. You have become familiar with the general theoretical idea of ​​reflection, now let’s get down to its practical application! We will not study all the methods of the Reflection API, only what is actually encountered in practice. Since the reflection mechanism involves working with classes, we will have a simple class - MyClass:
public class MyClass {
   private int number;
   private String name = "default";
//    public MyClass(int number, String name) {
//        this.number = number;
//        this.name = name;
//    }
   public int getNumber() {
       return number;
   }
   public void setNumber(int number) {
       this.number = number;
   }
   public void setName(String name) {
       this.name = name;
   }
   private void printData(){
       System.out.println(number + name);
   }
}
As we can see, this is the most common class. The constructor with parameters is commented out for a reason, we will return to this later. If you looked closely at the contents of the class, you probably saw the absence of getter'a for the name. The field itself nameis marked with an access modifier private; we won’t be able to access it outside the class itself; =>we can’t get its value. "So what's the problem? - you say. “Add getteror change the access modifier.” And you will be right, but what if MyClassit is in a compiled aar library or in another closed module without editing access, and in practice this happens extremely often. And some inattentive programmer simply forgot to write getter. It's time to remember about reflection! Let's try to get to the class privatefield : nameMyClass
public static void main(String[] args) {
   MyClass myClass = new MyClass();
   int number = myClass.getNumber();
   String name = null; //no getter =(
   System.out.println(number + name);//output 0null
   try {
       Field field = myClass.getClass().getDeclaredField("name");
       field.setAccessible(true);
       name = (String) field.get(myClass);
   } catch (NoSuchFieldException | IllegalAccessException e) {
       e.printStackTrace();
   }
   System.out.println(number + name);//output 0default
}
Let's figure out what happened here now. There is a wonderful class in java Class. It represents classes and interfaces in an executable Java application. We will not touch on the connection between Classand ClassLoader. this is not the topic of the article. Next, to get the fields of this class, you need to call the method getFields(), this method will return to us all the available fields of the class. This is not suitable for us, since our field is private, so we use the method getDeclaredFields(). This method also returns an array of class fields, but now both privateand protected. In our situation, we know the name of the field that interests us, and we can use the method getDeclaredField(String), where Stringis the name of the desired field. Note: getFields()and getDeclaredFields()do not return the fields of the parent class! Great, we received an object Field with a link to our name. Because the field was not публичным(public), access should be given to work with it. The method setAccessible(true)allows us to continue working. Now the field nameis completely under our control! You can get its value by calling get(Object)the object Field, where Objectis an instance of our class MyClass. We cast it Stringand assign it to our variable name. In case we suddenly don't have setter'a, we can use the method to set a new value for the name field set:
field.set(myClass, (String) "new value");
Congratulations! You have just mastered the basic mechanism of reflection and were able to access the privatefield! Pay attention to the block try/catchand types of exceptions handled. The IDE itself will indicate their mandatory presence, but their name makes it clear why they are here. Go ahead! As you may have noticed, ours MyClassalready has a method for displaying information about the class data:
private void printData(){
       System.out.println(number + name);
   }
But this programmer left a legacy here too. The method is under the access modifier private, and we had to write the output code ourselves each time. It’s not in order, where is our reflection?... Let’s write the following function:
public static void printData(Object myClass){
   try {
       Method method = myClass.getClass().getDeclaredMethod("printData");
       method.setAccessible(true);
       method.invoke(myClass);
   } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
       e.printStackTrace();
   }
}
Here the procedure is approximately the same as with getting a field - we get the desired method by name and give access to it. And to call the object Methodwe use invoke(Оbject, Args), where Оbjectis also an instance of the class MyClass. Args- method arguments - ours does not have any. Now we use the function to display information printData:
public static void main(String[] args) {
   MyClass myClass = new MyClass();
   int number = myClass.getNumber();
   String name = null; //?
   printData(myClass); // outout 0default
   try {
       Field field = myClass.getClass().getDeclaredField("name");
       field.setAccessible(true);
       field.set(myClass, (String) "new value");
       name = (String) field.get(myClass);
   } catch (NoSuchFieldException | IllegalAccessException e) {
       e.printStackTrace();
   }
   printData(myClass);// output 0new value
}
Hurray, now we have access to the private method of the class. But what if the method still has arguments, and why is there a commented-out constructor? Everything has its time. From the definition at the beginning it is clear that reflection allows you to create instances of a class in a mode runtime(while the program is running)! We can create an object of a class by the fully qualified name of that class. The fully qualified class name is the name of the class, given the path to it in package.
Reflection API.  Reflection.  The Dark Side of Java - 3
In my hierarchy, packagethe full name MyClasswill be “ reflection.MyClass”. You can also find out the class name in a simple way (it will return the class name as a string):
MyClass.class.getName()
Let's create an instance of the class using reflection:
public static void main(String[] args) {
   MyClass myClass = null;
   try {
       Class clazz = Class.forName(MyClass.class.getName());
       myClass = (MyClass) clazz.newInstance();
   } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
       e.printStackTrace();
   }
   System.out.println(myClass);//output created object reflection.MyClass@60e53b93
}
At the time the java application starts, not all classes are loaded into the JVM. If your code does not refer to the class MyClass, then the person who is responsible for loading classes into the JVM, and that is ClassLoader, will never load it there. Therefore, we need to force ClassLoaderit to load and receive a description of our class in the form of a variable of type Class. For this task, there is a method forName(String), where Stringis the name of the class whose description we require. Having received Сlass, the method call newInstance()will return Object, which will be created according to the same description. It remains to bring this object to our class MyClass. Cool! It was difficult, but I hope it is understandable. Now we can create an instance of a class literally from one line! Unfortunately, the described method will only work with the default constructor (without parameters). How to call methods with arguments and constructors with parameters? It's time to uncomment our constructor. As expected, newInstance()it doesn't find the default constructor and doesn't work anymore. Let's rewrite the creation of a class instance:
public static void main(String[] args) {
   MyClass myClass = null;
   try {
       Class clazz = Class.forName(MyClass.class.getName());
       Class[] params = {int.class, String.class};
       myClass = (MyClass) clazz.getConstructor(params).newInstance(1, "default2");
   } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
       e.printStackTrace();
   }
   System.out.println(myClass);//output created object reflection.MyClass@60e53b93
}
To obtain class constructors, call the method from the class description getConstructors(), and to obtain constructor parameters, call getParameterTypes():
Constructor[] constructors = clazz.getConstructors();
for (Constructor constructor : constructors) {
   Class[] paramTypes = constructor.getParameterTypes();
   for (Class paramType : paramTypes) {
       System.out.print(paramType.getName() + " ");
   }
   System.out.println();
}
This way we get all the constructors and all the parameters to them. In my example, there is a call to a specific constructor with specific, already known parameters. And to call this constructor, we use the method newInstance, in which we specify the values ​​of these parameters. The same will happen invokefor calling methods. The question arises: where can reflective calling of constructors be useful? Modern java technologies, as mentioned at the beginning, cannot do without the Reflection API. For example, DI (Dependency Injection), where annotations combined with reflection of methods and constructors form the Dagger library, popular in Android development. After reading this article, you can confidently consider yourself enlightened in the mechanisms of the Reflection API. It’s not for nothing that reflection is called the dark side of java. It completely breaks the OOP paradigm. In java, encapsulation serves to hide and limit access of some program components to others. By using the private modifier we mean that access to this field will only be within the class where this field exists, based on this we build the further architecture of the program. In this article, we saw how you can use reflection to get anywhere. A good example in the form of an architectural solution is the generative design pattern - Singleton. Its main idea is that throughout the entire operation of the program, the class that implements this template should have only one copy. This is done by setting the default access modifier to private for the constructor. And it will be very bad if some programmer with his own reflection creates such classes. By the way, there is a very interesting question that I recently heard from my employee: can a class that implements a template have Singletonheirs? Is it possible that even reflection is powerless in this case? Write your feedback on the article and the answer in the comments, and also ask your questions! The true power of the Reflection API comes in combination with Runtime Annotations, which we'll probably talk about in a future article about the dark side of Java. Thank you for your attention!
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION