Skip to content
On this page

Class

What is reflection

Reflection is Reflection. Java's reflection means that the program can get all the information of an object during runtime.

Under normal circumstances, if we want to call a method of an object or access a field of an object, we usually pass in an object instance:

java
// Main.java
import com.itranswarp.learnjava.Person;

public class Main {
  String getFullName(Person p) {
    return p.getFirstName() + " " + p.getLastName();
  }
}

However, if the Person class cannot be obtained, there is only one Object instance, such as this:

java
String getFullName(Object obj) {
  return ???
}

what to do? Some children will say: Forced transformation!

java
String getFullName(Object obj) {
    Person p = (Person) obj;
    return p.getFirstName() + " " + p.getLastName();
}

When forcing transformation, you will find a problem: when compiling the above code, you still need to reference the Person class. Otherwise, remove the import statement and see if it can be compiled successfully?

Therefore, reflection is to solve the problem of how to call the method of an instance without knowing anything about it during runtime.

Class loading

Except for basic types such as int , all other types in Java are class (including interface ). For example:

  • String
  • Object
  • Runnable
  • Exception
  • ...

Thinking carefully, we can conclude that the essence of class (including interface ) is data type ( Type ). Data types without inheritance relationships cannot be assigned:

java
Number n = new Double(123.456); // OK
String s = new Double(123.456); // compile error!

class are dynamically loaded by the JVM during execution. The first time the JVM reads a class type, it loads it into memory.

Every time a class is loaded, the JVM creates an instance of Class type and associates it with it. Note: Class type here is a class named Class . It looks like this:

java
public final class Class {
    private Class() {}
}

Taking String class as an example, when the JVM loads String class, it first reads String.class file into memory, and then creates a Class instance for String class and associates it:

java
Class cls = new Class(String);

This Class instance is created internally by the JVM. If we look at the JDK source code, we can find that the construction method of Class class is private . Only the JVM can create Class instances. Our own Java programs cannot create Class instances.

Therefore, each Class instance held by the JVM points to a data type ( class or interface ):

┌───────────────────────────┐
│      Class Instance       │────▶ String
├───────────────────────────┤
│name = "java.lang.String"  │
└───────────────────────────┘
┌───────────────────────────┐
│      Class Instance       │────▶ Random
├───────────────────────────┤
│name = "java.util.Random"  │
└───────────────────────────┘
┌───────────────────────────┐
│      Class Instance       │────▶ Runnable
├───────────────────────────┤
│name = "java.lang.Runnable"│
└───────────────────────────┘

A Class instance contains all complete information about the class :

┌───────────────────────────┐
│      Class Instance       │────▶ String
├───────────────────────────┤
│name = "java.lang.String"  │
├───────────────────────────┤
│package = "java.lang"      │
├───────────────────────────┤
│super = "java.lang.Object" │
├───────────────────────────┤
│interface = CharSequence...│
├───────────────────────────┤
│field = value[],hash,...   │
├───────────────────────────┤
│method = indexOf()...      │
└───────────────────────────┘

Since the JVM creates a corresponding Class instance for each loaded class and saves all the information of the class in the instance, including the class name, package name, parent class, implemented interface, all methods, fields, etc., therefore, if After obtaining a Class instance, we can obtain all the information of class corresponding to the instance through this Class instance.

This method of obtaining class information through Class instances is called reflection.

How to get Class instance of a class ? There are three methods:

Method 1: Obtain directly through the static variable class of a class :

java
Class cls = String.class;

Method 2: If we have an instance variable, we can obtain it through the getClass() method provided by the instance variable:

java
String s = "Hello";
Class cls = s.getClass();

Method 3: If you know the complete class name of a class , you can obtain it through the static method Class.forName() :

java
Class cls = Class.forName("java.lang.String");

Because Class instance is unique in the JVM, Class instance obtained by the above method is the same instance. You can use == to compare two Class instances:

java
Class cls1 = String.class;

String s = "Hello";
Class cls2 = s.getClass();

boolean sameClass = cls1 == cls2; // true

Note the difference between Class instance comparison and instanceof :

java
Integer n = new Integer(123);

boolean b1 = n instanceof Integer; // true,Because n is of type Integer
boolean b2 = n instanceof Number; // true,Because n is a subclass of Number type

boolean b3 = n.getClass() == Integer.class; // true,Because n.getClass() returns Integer.class
boolean b4 = n.getClass() == Number.class; // false,Because Integer.class!=Number.class

Use instanceof not only to match the specified type, but also to match subclasses of the specified type. Using == to determine class instances can accurately determine the data type, but it cannot perform subtype comparison.

Normally, we should use instanceof to determine the data type, because when programming for abstraction, we don't care about the specific subtype. Only when we need to accurately determine whether a type is a certain class , we use == to determine class instance.

Because the purpose of reflection is to obtain information about a certain instance. Therefore, when we get an Object instance, we can obtain class information of the Object through reflection:

java
void printObjectInfo(Object obj) {
    Class cls = obj.getClass();
}

To get the basic information obtained from Class instance, refer to the following code:

java
// reflection
public class Main {
    public static void main(String[] args) {
        printClassInfo("".getClass());
        printClassInfo(Runnable.class);
        printClassInfo(java.time.Month.class);
        printClassInfo(String[].class);
        printClassInfo(int.class);
    }

    static void printClassInfo(Class cls) {
        System.out.println("Class name: " + cls.getName());
        System.out.println("Simple name: " + cls.getSimpleName());
        if (cls.getPackage() != null) {
            System.out.println("Package name: " + cls.getPackage().getName());
        }
        System.out.println("is interface: " + cls.isInterface());
        System.out.println("is enum: " + cls.isEnum());
        System.out.println("is array: " + cls.isArray());
        System.out.println("is primitive: " + cls.isPrimitive());
    }
}

Note that an array (such as String[] ) is also a class, and unlike String.class , its class name is [Ljava.lang.String; In addition, the JVM also creates Class instances for each basic type such as int , which is accessed through int.class .

If we obtain a Class instance, we can use the Class instance to create an instance of the corresponding type:

java
// Get the Class instance of String:
Class cls = String.class;
// Create a String instance:
String s = (String) cls.newInstance();

The above code is equivalent to new String() . Class instances can be created through Class.newInstance() Its limitation is that only the public parameterless constructor can be called. Constructors with parameters or non-public constructors cannot be called through Class.newInstance() .

Dynamic loading

When the JVM executes a Java program, it does not load all the classes used into the memory at once, but only loads the classes when they are needed for the first time. For example:

java
// Main.java
public class Main {
    public static void main(String[] args) {
        if (args.length > 0) {
            create(args[0]);
        }
    }

    static void create(String name) {
        Person p = new Person(name);
    }
}

When Main.java is executed, since Main is used, the JVM will first load Main.class into memory. However, Person.class will not be loaded unless the program executes the create() method and the JVM will load Person.class for the first time when it finds that the Person class needs to be loaded. If the create() method is not executed, Person.class will not be loaded at all.

This is the feature of JVM dynamically loading class .

The feature of dynamically loading class is very important for Java programs. Using the JVM's feature of dynamically loading class , we can load different implementation classes based on conditions during runtime. For example, Commons Logging always uses Log4j first, and only uses JDK logging when Log4j does not exist. Taking advantage of the JVM dynamic loading feature, the approximate implementation code is as follows:

java
// Commons Logging prefers Log4j:
LogFactory factory = null;
if (isClassPresent("org.apache.logging.log4j.Logger")) {
    factory = createLog4j();
} else {
    factory = createJdkLog();
}

boolean isClassPresent(String name) {
    try {
        Class.forName(name);
        return true;
    } catch (Exception e) {
        return false;
    }
}

This is why we only need to put the Log4j jar package in the classpath, and Commons Logging will automatically use Log4j.

Summary

The JVM creates a corresponding Class instance for each loaded class and interface to save all information about class and interface ;

After obtaining Class instance corresponding to a class , you can obtain all information about the class ;

The method of obtaining class information through Class instances is called Reflection;

The JVM always loads class dynamically and can control the loading of classes based on conditions during runtime.

Class has loaded