JavaRush /Java Blog /Random EN /Reflection in Java - Usage Examples

Reflection in Java - Usage Examples

Published in the Random EN group
You might have come across the concept of “reflection” in everyday life. Usually this word refers to the process of studying oneself. In programming, it has a similar meaning - it is a mechanism for examining data about a program, as well as changing the structure and behavior of the program during its execution. The important thing here is that it is done at runtime, not at compile time. But why examine the code at runtime? You already see it :/ Examples of using Reflection - 1The idea of ​​reflection may not be immediately clear for one reason: until this moment, you always knew the classes you were working with. Well, for example, you could write a class Cat:
package learn.javarush;

public class Cat {

   private String name;
   private int age;

   public Cat(String name, int age) {
       this.name = name;
       this.age = age;
   }

   public void sayMeow() {

       System.out.println("Meow!");
   }

   public void jump() {

       System.out.println("Jump!");
   }

   public String getName() {
       return name;
   }

   public void setName(String name) {
       this.name = name;
   }

   public int getAge() {
       return age;
   }

   public void setAge(int age) {
       this.age = age;
   }

@Override
public String toString() {
   return "Cat{" +
           "name='" + name + '\'' +
           ", age=" + age +
           '}';
}

}
You know everything about it, you see what fields and methods it has. Surely you can create an inheritance system with a common class for convenience Animal, if suddenly the program needs other classes of animals. Previously, we even created a veterinary clinic class in which you could pass a parent object Animal, and the program would treat the animal depending on whether it was a dog or a cat. Although these tasks are not very simple, the program learns all the information it needs about the classes at compile time. Therefore, when you main()pass an object in a method Catto the methods of the veterinary clinic class, the program already knows that this is a cat, not a dog. Now let's imagine that we are faced with another task. Our goal is to write a code analyzer. We need to create a class CodeAnalyzerwith a single method - void analyzeClass(Object o). This method should:
  • determine what class the object was passed to it and display the class name in the console;
  • determine the names of all fields of this class, including private ones, and display them in the console;
  • determine the names of all methods of this class, including private ones, and display them in the console.
It will look something like this:
public class CodeAnalyzer {

   public static void analyzeClass(Object o) {

       //Вывести название класса, к которому принадлежит an object o
       //Вывести названия всех переменных этого класса
       //Вывести названия всех методов этого класса
   }

}
Now the difference between this problem and the rest of the problems that you solved before is visible. In this case, the difficulty lies in the fact that neither you nor the program know what exactly will be passed to the method analyzeClass(). You write a program, other programmers will start using it, who can pass anything into this method - any standard Java class or any class they have written. This class can have any number of variables and methods. In other words, in this case we (and our program) have no idea what classes we will be working with. And yet, we must solve this problem. And here the standard Java library comes to our aid - the Java Reflection API. The Reflection API is a powerful language feature. The official Oracle documentation states that this mechanism is recommended to be used only by experienced programmers who understand very well what they are doing. You will soon understand why we are suddenly given such warnings in advance :) Here is a list of what can be done using the Reflection API:
  1. Find out/determine the class of an object.
  2. Get information about class modifiers, fields, methods, constants, constructors and superclasses.
  3. Find out which methods belong to the implemented interface/interfaces.
  4. Create an instance of a class when the class name is unknown until the program is executed.
  5. Get and set the value of an object field by name.
  6. Call an object's method by name.
Impressive list, huh? :) Pay attention:The reflection mechanism is able to do all this “on the fly” regardless of which class object we pass to our code analyzer! Let's look at the capabilities of the Reflection API with examples.

How to find out / determine the class of an object

Let's start with the basics. The entry point to Java's reflection mechanism is the Class. Yes, it looks really funny, but that’s what reflection is for :) Using a class Class, we first of all determine the class of any object passed to our method. Let's try this:
import learn.javarush.Cat;

public class CodeAnalyzer {

   public static void analyzeClass(Object o) {
       Class clazz = o.getClass();
       System.out.println(clazz);
   }

   public static void main(String[] args) {

       analyzeClass(new Cat("Barsik", 6));
   }
}
Console output:

class learn.javarush.Cat
Pay attention to two things. Firstly, we deliberately put the class Catin a separate package. learn.javarush;Now you can see that it getClass()returns the full name of the class. Secondly, we named our variable clazz. Looks a little strange. Of course, it should be called “class”, but “class” is a reserved word in the Java language, and the compiler will not allow variables to be called that way. I had to get out of it :) Well, not a bad start! What else did we have on the list of possibilities?

How to get information about class modifiers, fields, methods, constants, constructors and superclasses

This is already more interesting! In the current class we have no constants and no parent class. Let's add them for completeness. Let's create the simplest parent class Animal:
package learn.javarush;
public class Animal {

   private String name;
   private int age;
}
And let’s add Catinheritance from Animaland one constant to our class:
package learn.javarush;

public class Cat extends Animal {

   private static final String ANIMAL_FAMILY = "Семейство кошачьих";

   private String name;
   private int age;

   //...остальная часть класса
}
Now we have a complete set! Let's try out the possibilities of reflection :)
import learn.javarush.Cat;

import java.util.Arrays;

public class CodeAnalyzer {

   public static void analyzeClass(Object o) {
       Class clazz = o.getClass();
       System.out.println("Name класса: " + clazz);
       System.out.println("Поля класса: " + Arrays.toString(clazz.getDeclaredFields()));
       System.out.println("Родительский класс: " + clazz.getSuperclass());
       System.out.println("Методы класса: " +  Arrays.toString(clazz.getDeclaredMethods()));
       System.out.println("Конструкторы класса: " + Arrays.toString(clazz.getConstructors()));
   }

   public static void main(String[] args) {

       analyzeClass(new Cat("Barsik", 6));
   }
}
This is what we get in the console:
Name класса: class learn.javarush.Cat
Поля класса: [private static final java.lang.String learn.javarush.Cat.ANIMAL_FAMILY, private java.lang.String learn.javarush.Cat.name, private int learn.javarush.Cat.age]
Родительский класс: class learn.javarush.Animal
Методы класса: [public java.lang.String learn.javarush.Cat.getName(), public void learn.javarush.Cat.setName(java.lang.String), public void learn.javarush.Cat.sayMeow(), public void learn.javarush.Cat.setAge(int), public void learn.javarush.Cat.jump(), public int learn.javarush.Cat.getAge()]
Конструкторы класса: [public learn.javarush.Cat(java.lang.String,int)]
We received so much detailed information about the class! And not only about public, but also about private parts. Pay attention: private-variables are also displayed in the list. Actually, the “analysis” of the class can be considered complete at this point: now, using the method, analyzeClass()we will learn everything that is possible. But these are not all the possibilities that we have when working with reflection. Let's not limit ourselves to simple observation and move on to active action! :)

How to create an instance of a class if the class name is unknown before the program is executed

Let's start with the default constructor. It's not in our class yet Cat, so let's add it:
public Cat() {

}
Here's what the code would look like to create an object Catusing reflection (method createCat()):
import learn.javarush.Cat;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Main {

   public static Cat createCat() throws IOException, IllegalAccessException, InstantiationException, ClassNotFoundException {

       BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
       String className = reader.readLine();

       Class clazz = Class.forName(className);
       Cat cat = (Cat) clazz.newInstance();

       return cat;
   }

public static Object createObject() throws Exception {

   BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
   String className = reader.readLine();

   Class clazz = Class.forName(className);
   Object result = clazz.newInstance();

   return result;
}

   public static void main(String[] args) throws IOException, IllegalAccessException, ClassNotFoundException, InstantiationException {
       System.out.println(createCat());
   }
}
Enter into the console:

learn.javarush.Cat
Console output:

Cat{name='null', age=0}
This is not an error: the values name​​and ageare displayed in the console because we programmed their output in the toString()class method Cat. Here we read the name of the class whose object we will create from the console. The running program learns the name of the class whose object it will create. Examples of using Reflection - 3For the sake of brevity, we have omitted the code for proper exception handling so that it does not take up more space than the example itself. In a real program, of course, it is definitely worth handling situations where incorrect names are entered, etc. The default constructor is a fairly simple thing, so creating an instance of a class using it, as you can see, is not difficult :) And using the method, newInstance()we create a new object of this class. It's another matter if the class constructor Cattakes parameters as input. Let's remove the default constructor from the class and try to run our code again.

null
java.lang.InstantiationException: learn.javarush.Cat
  at java.lang.Class.newInstance(Class.java:427)
Something went wrong! We received an error because we called a method to create an object through the default constructor. But now we don’t have such a designer. This means that when the method works, newInstance()the reflection mechanism will use our old constructor with two parameters:
public Cat(String name, int age) {
   this.name = name;
   this.age = age;
}
But we didn’t do anything with the parameters, as if we had completely forgotten about them! To pass them to the constructor using reflection, you will have to tweak it a little:
import learn.javarush.Cat;

import java.lang.reflect.InvocationTargetException;

public class Main {

   public static Cat createCat()  {

       Class clazz = null;
       Cat cat = null;

       try {
           clazz = Class.forName("learn.javarush.Cat");
           Class[] catClassParams = {String.class, int.class};
           cat = (Cat) clazz.getConstructor(catClassParams).newInstance("Barsik", 6);
       } catch (ClassNotFoundException e) {
           e.printStackTrace();
       } catch (InstantiationException e) {
           e.printStackTrace();
       } catch (IllegalAccessException e) {
           e.printStackTrace();
       } catch (NoSuchMethodException e) {
           e.printStackTrace();
       } catch (InvocationTargetException e) {
           e.printStackTrace();
       }

       return cat;
   }

   public static void main(String[] args) {
       System.out.println(createCat());
   }
}
Console output:

Cat{name='Barsik', age=6}
Let's take a closer look at what's happening in our program. We have created an array of objects Class.
Class[] catClassParams = {String.class, int.class};
They correspond to the parameters of our constructor (we have just the parameters Stringand int). We pass them to the method clazz.getConstructor()and get access to the required constructor. After this, all that remains is to call the method newInstance()with the necessary parameters and do not forget to explicitly cast the object to the class we need - Cat.
cat = (Cat) clazz.getConstructor(catClassParams).newInstance("Barsik", 6);
As a result, our object will be successfully created! Console output:

Cat{name='Barsik', age=6}
Let's move on :)

How to get and set the value of an object field by name

Imagine that you are using a class written by another programmer. However, you do not have the opportunity to edit it. For example, a ready-made class library packaged in a JAR. You can read the class code, but you can’t change it. The programmer who created the class in this library (let it be our old class Cat) did not get enough sleep before the final design and removed the getters and setters for the field age. Now this class has come to you. It fully meets your needs, because you just need objects in the program Cat. But you need them with that same field age! This is a problem: we cannot reach the field, because it has a modifier private, and the getters and setters were removed by the would-be developer of this class :/ Well, reflection can help us in this situation too! CatWe have access to the class code : we can at least find out what fields it has and what they are called. Armed with this information, we solve our problem:
import learn.javarush.Cat;

import java.lang.reflect.Field;

public class Main {

   public static Cat createCat()  {

       Class clazz = null;
       Cat cat = null;
       try {
           clazz = Class.forName("learn.javarush.Cat");
           cat = (Cat) clazz.newInstance();

           //с полем name нам повезло - для него в классе есть setter
           cat.setName("Barsik");

           Field age = clazz.getDeclaredField("age");

           age.setAccessible(true);

           age.set(cat, 6);

       } catch (IllegalAccessException e) {
           e.printStackTrace();
       } catch (InstantiationException e) {
           e.printStackTrace();
       } catch (ClassNotFoundException e) {
           e.printStackTrace();
       } catch (NoSuchFieldException e) {
           e.printStackTrace();
       }

       return cat;
   }

   public static void main(String[] args) {
       System.out.println(createCat());
   }
}
As stated in the comment, nameeverything is simple with the field: the class developers provided a setter for it. You also already know how to create objects from default constructors: there is a method for this newInstance(). But you will have to tinker with the second field. Let's figure out what's going on here :)
Field age = clazz.getDeclaredField("age");
Here we, using our object Class clazz, access the field ageusing the getDeclaredField(). It gives us the ability to get the age field as an object Field age. But this is not enough yet, because privatefields cannot simply be assigned values. To do this, you need to make the field “available” using the method setAccessible():
age.setAccessible(true);
Those fields for which this is done can be assigned values:
age.set(cat, 6);
As you can see, we have a kind of setter turned upside down: we assign the field Field ageits value, and also pass it the object to which this field should be assigned. Let's run our method main()and see:

Cat{name='Barsik', age=6}
Great, we did it all! :) Let's see what other possibilities we have...

How to call an object's method by name

Let's slightly change the situation from the previous example. Let's say the class developer Catmade a mistake with the fields - both are available, there are getters and setters for them, everything is ok. The problem is different: he made private a method that we definitely need:
private void sayMeow() {

   System.out.println("Meow!");
}
As a result, we will create objects Catin our program, but will not be able to call their method sayMeow(). Will we have cats that don't meow? Quite strange :/ How can I fix this? Once again, the Reflection API comes to the rescue! We know the name of the required method. The rest is a matter of technique:
import learn.javarush.Cat;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Main {

   public static void invokeSayMeowMethod()  {

       Class clazz = null;
       Cat cat = null;
       try {

           cat = new Cat("Barsik", 6);

           clazz = Class.forName(Cat.class.getName());

           Method sayMeow = clazz.getDeclaredMethod("sayMeow");

           sayMeow.setAccessible(true);

           sayMeow.invoke(cat);

       } catch (ClassNotFoundException e) {
           e.printStackTrace();
       } catch (NoSuchMethodException e) {
           e.printStackTrace();
       } catch (IllegalAccessException e) {
           e.printStackTrace();
       } catch (InvocationTargetException e) {
           e.printStackTrace();
       }
   }

   public static void main(String[] args) {
       invokeSayMeowMethod();
   }
}
Here we act in much the same way as in the situation with access to a private field. First we get the method we need, which is encapsulated in a class object Method:
Method sayMeow = clazz.getDeclaredMethod("sayMeow");
With help, getDeclaredMethod()you can “reach out” to private methods. Next we make the method callable:
sayMeow.setAccessible(true);
And finally, we call the method on the desired object:
sayMeow.invoke(cat);
Calling a method also looks like a “call in reverse”: we are used to pointing an object to the required method using a dot ( cat.sayMeow()), and when working with reflection, we pass to the method the object from which it needs to be called. What do we have in the console?

Meow!
Everything worked out! :) Now you see what extensive possibilities the reflection mechanism in Java gives us. In difficult and unexpected situations (as in the examples with a class from a closed library), it can really help us out a lot. However, like any great power, it also implies great responsibility. The disadvantages of reflection are written about in a special section on the Oracle website. There are three main disadvantages:
  1. Productivity decreases. Methods that are called using reflection have lower performance than methods that are called normally.

  2. There are safety restrictions. The reflection mechanism allows you to change the behavior of the program during runtime. But in your work environment on a real project there may be restrictions that do not allow you to do this.

  3. Risk of disclosure of inside information. It is important to understand that using reflection directly violates the principle of encapsulation: it allows us to access private fields, methods, etc. I think there is no need to explain that direct and gross violation of OOP principles should be resorted to only in the most extreme cases, when there are no other ways to solve the problem for reasons beyond your control.

Use the reflection mechanism wisely and only in situations where it cannot be avoided, and do not forget about its shortcomings. This concludes our lecture! It turned out to be quite large, but today you learned a lot of new things :)
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION