[Java] Reflection Class

What is reflection?

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

Normally, if we want to call a method of an object, or access a field of an object, we usually pass in the object instance:

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

public class Main {
    
    
    String getFullName(Person p) {
    
    
        return p.getFirstName() + " " + p.getLastName();
    }
}
但是,如果不能获得Person类,只有一个Object实例,比如这样:

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

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

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

When you force the transformation, you will find a problem: to compile the above code, you still need to reference Personthe class. Otherwise, remove importthe statement, can you see if it can be compiled?
Therefore, reflection is to solve how to call its method without knowing anything about an instance at runtime.

class class

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

String
Object
Runnable
Exception
...

After careful consideration, we can conclude that the essence of class (including interface) is data type (Type). Data types with no inheritance relationship cannot be assigned:

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

Instead,class it JVMis loaded dynamically during execution. JVMWhen a type is read for the first time class, it is loaded into memory.

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

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

Taking Stringa class as an example, when JVMloading Stringa class, it first reads String.classthe file into memory, then Stringcreates an Classinstance of the class and associates it:

Class cls = new Class(String);

This Class instance is created inside the JVM. If we look at the JDK source code, we can find that the construction method of the Class class is private. Only the JVM can create Class instances, and 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 the complete information of 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., if After obtaining a certain Class instance, we can obtain all the information of the class corresponding to the instance through this Class instance.

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

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

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

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:

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

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

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

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

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:

Integer n = new Integer(123);

boolean b1 = n instanceof Integer; // true,因为n是Integer类型
boolean b2 = n instanceof Number; // true,因为n是Number类型的子类

boolean b3 = n.getClass() == Integer.class; // true,因为n.getClass()返回Integer.class
boolean b4 = n.getClass() == Number.class; // false,因为Integer.class!=Number.class

Using instanceof not only matches the specified type, but also matches the subclass of the specified type. Using == to judge the class instance can accurately judge the data type, but it cannot be used for subtype comparison.

Normally, we should use instanceof to judge 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 the class instance.

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

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

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

// 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 (eg String[]) is also a class, and unlike String.class, its class name is [Ljava.lang.String;. In addition, the JVM also creates a Class instance for each basic type such as int, through int.classaccess.

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

// 获取String的Class实例:
Class cls = String.class;
// 创建一个String实例:
String s = (String) cls.newInstance();

The above code is equivalent to new String(). Through Class.newInstance()the class instance can be created, its limitation is: only the public parameterless constructor can be called. Constructors with parameters, or non-public constructors cannot Class.newInstance(be called via ).

dynamic loading

When the JVM executes a Java program, it does not load all the classes used into the memory at one time, but only loads the class when it needs to be used for the first time. For example:

// 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 executing Main.java, since Main is used, the JVM will first load Main.class into memory. However, Person.class will not be loaded unless the program executes to 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, then Person.class will not be loaded at all.

This is the feature of JVM dynamically loading classes.

The feature of dynamically loading classes is very important for Java programs. Using the feature of JVM to dynamically load classes, we can load different implementation classes according to conditions at runtime. For example, Commons Loggingalways prefer to use Log4j, and only use JDK's logging if Log4j is not present. Using the JVM dynamic loading feature, the approximate implementation code is as follows:

// Commons Logging优先使用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;
    }
}
这就是为什么我们只需要把Log4j的jar包放到classpath中,Commons Logging就会自动使用Log4j的原因。

# 小结
JVM为每个加载的classinterface创建了对应的Class实例来保存classinterface的所有信息;

获取一个class对应的Class实例后,就可以获取该class的所有信息;

通过Class实例获取class信息的方法称为反射(Reflection);

JVM总是动态加载class,可以在运行期根据条件来控制加载class

Guess you like

Origin blog.csdn.net/ihero/article/details/132223054