Appearance
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.