目录
1、反射定义
2、class 类
class 类的本质
JVM 对 class 的加载
Class 实例与 class 的关系
获取 Class 实例的方法
Class 实例比较与 instanceof 的区别
通过 Class 实例获取基本信息
通过 Class 实例创建对象
3、访问字段、调用方法
访问字段
调用方法
4、调用构造方法、获取继承关系
调用构造方法
获取继承关系
5、小结
在 Java 中,只有 JVM 能创建Class
实例,我们自己的 Java 程序无法直接创建。当 JVM 加载一个 class 时,会为其创建一个对应的Class
类型实例,该实例保存了该 class 的所有完整信息,包括类名、包名、父类、实现的接口、所有方法、字段等。通过Class
实例获取 class 信息的方法被称为反射(Reflection)。利用反射,我们可以在运行时动态获取对象的类型信息、操作对象的字段和方法,甚至创建对象实例,极大地增强了 Java 程序的灵活性。
除了int
、double
等基本类型外,Java 的其他类型(包括接口)全部都是class
。例如:String
、Object
、Runnable
、Exception
等。从本质上讲,class
(包括interface
)是数据类型(Type),不同数据类型之间若没有继承关系则无法进行赋值操作,如:
Number n = new Double(123.456); // 正确,Double是Number的子类
String s = new Double(123.456); // 编译错误,String与Double无继承关系
JVM 在执行过程中会动态加载class
。当第一次读取到某一class
类型时,JVM 会将其加载进内存,并为该class
创建一个Class
类型的实例,建立起关联。Class
类是 JDK 中的一个特殊类,其定义如下:
public final class Class {
private Class() {}
}
可以看到,Class
类的构造方法是私有的,这意味着只有 JVM 能创建Class
实例,我们自己的 Java 程序无法直接创建。
JVM 持有的每个Class
实例都唯一指向一个数据类型(class 或 interface)。一个Class
实例包含了对应class
的所有完整信息,例如对于String
类的Class
实例,其包含的信息可能有:
java.lang.String
java.lang
java.lang.Object
CharSequence
等value[]
、hash
等indexOf()
等获取一个class
的Class
实例有三种方法:
1、直接通过 class 的静态变量 class 获取
Class cls = String.class;
2、通过实例变量的 getClass () 方法获取
String s = "Hello";
Class cls = s.getClass();
3、通过 Class.forName () 方法获取
Class cls = Class.forName("java.lang.String");
需要注意的是,由于Class
实例在 JVM 中是唯一的,因此通过上述三种方法获取的Class
实例是同一个实例,可以使用==
进行比较:
Class cls1 = String.class;
String s = "Hello";
Class cls2 = s.getClass();
boolean sameClass = cls1 == cls2; // 结果为true
Class
实例比较(使用==
)和instanceof
操作符在功能上有明显差别:
instanceof
:用于判断对象是否是某个类型或其子类型的实例,具有继承关系的判断能力。Integer n = new Integer(123);
boolean b1 = n instanceof Integer; // true,n是Integer类型
boolean b2 = n instanceof Number; // true,n是Number类型的子类
==
判断Class
实例:用于精确判断数据类型是否完全一致,不能进行子类型比较。Integer n = new Integer(123);
boolean b3 = n.getClass() == Integer.class; // true
Class c1 = n.getClass();
Class c2 = Number.class;
boolean b4 = c1 == c2; // false,Integer.class与Number.class不同
通常情况下,我们使用instanceof
判断数据类型,因为它更符合面向对象编程中面向抽象的思想。只有在需要精确判断一个类型是否为某个具体class
时,才使用==
判断Class
实例。
当我们拿到一个Class
实例后,可以通过它获取该class
的基本信息,以下是一个示例代码:
// 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());
}
}
在上述代码中,需要注意以下几点:
String[]
)也是一种类,其类名与普通类不同,例如String[]
的类名是[Ljava.lang.String;
。int
)也创建了对应的Class
实例,可以通过int.class
等方式访问。获取到Class
实例后,我们可以通过它来创建对应类型的实例:
// 获取String的Class实例
Class cls = String.class;
// 创建一个String实例,相当于new String()
String s = (String) cls.newInstance();
需要注意的是,Class.newInstance()
方法存在一定的局限性:它只能调用类的public
无参数构造方法,带参数的构造方法或非public
的构造方法无法通过该方法调用。
通过反射,我们可以在运行时访问和修改对象的字段,即使这些字段是私有的。下面是一个访问和修改字段的示例:
import java.lang.reflect.Field;
class Person {
private String name;
public int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
public class FieldAccess {
public static void main(String[] args) throws Exception {
// 创建Person实例
Person person = new Person("张三", 20);
// 获取Class实例
Class> cls = person.getClass();
// 获取public字段age
Field ageField = cls.getField("age");
System.out.println("修改前age: " + ageField.getInt(person));
ageField.setInt(person, 25);
System.out.println("修改后age: " + ageField.getInt(person));
// 获取private字段name
Field nameField = cls.getDeclaredField("name");
// 由于name是private字段,需要设置访问权限
nameField.setAccessible(true);
System.out.println("修改前name: " + nameField.get(person));
nameField.set(person, "李四");
System.out.println("修改后name: " + nameField.get(person));
}
}
在上述代码中:
getField(String name)
:用于获取类的public
字段(包括从父类继承的字段)。getDeclaredField(String name)
:用于获取类自身声明的字段(无论访问修饰符是什么),但不能获取从父类继承的字段。setAccessible(true)
:用于设置字段的访问权限,使我们可以访问私有的字段。通过反射,我们还可以在运行时调用对象的方法,同样可以调用私有的方法。下面是一个调用方法的示例:
import java.lang.reflect.Method;
class Calculator {
private int add(int a, int b) {
return a + b;
}
public int multiply(int a, int b) {
return a * b;
}
}
public class MethodInvocation {
public static void main(String[] args) throws Exception {
// 创建Calculator实例
Calculator calculator = new Calculator();
// 获取Class实例
Class> cls = calculator.getClass();
// 调用public方法multiply
Method multiplyMethod = cls.getMethod("multiply", int.class, int.class);
int result1 = (int) multiplyMethod.invoke(calculator, 3, 4);
System.out.println("3 * 4 = " + result1);
// 调用private方法add
Method addMethod = cls.getDeclaredMethod("add", int.class, int.class);
// 设置访问权限
addMethod.setAccessible(true);
int result2 = (int) addMethod.invoke(calculator, 5, 6);
System.out.println("5 + 6 = " + result2);
}
}
在上述代码中:
getMethod(String name, Class>... parameterTypes)
:用于获取类的public
方法(包括从父类继承的方法),需要指定方法名和参数类型。getDeclaredMethod(String name, Class>... parameterTypes)
:用于获取类自身声明的方法(无论访问修饰符是什么),但不能获取从父类继承的方法。invoke(Object obj, Object... args)
:用于调用方法,第一个参数是方法的调用者,后面的参数是方法的参数。通过反射,我们可以在运行时调用类的构造方法,包括私有的构造方法,这比使用Class.newInstance()
方法更加灵活。下面是一个调用构造方法的示例:
import java.lang.reflect.Constructor;
class Person {
private String name;
private int age;
// 无参构造方法
public Person() {
this.name = "默认姓名";
this.age = 0;
}
// 有参构造方法
private Person(String name, int age) {
this.name = name;
this.age = age;
}
}
public class ConstructorInvocation {
public static void main(String[] args) throws Exception {
// 获取Person的Class实例
Class> cls = Person.class;
// 调用无参构造方法
Constructor> defaultConstructor = cls.getConstructor();
Person person1 = (Person) defaultConstructor.newInstance();
System.out.println("通过无参构造方法创建的Person: " + person1);
// 调用私有的有参构造方法
Constructor> parameterizedConstructor = cls.getDeclaredConstructor(String.class, int.class);
// 设置访问权限
parameterizedConstructor.setAccessible(true);
Person person2 = (Person) parameterizedConstructor.newInstance("张三", 20);
System.out.println("通过有参构造方法创建的Person: " + person2);
}
}
在上述代码中:
getConstructor(Class>... parameterTypes)
:用于获取类的public
构造方法,需要指定参数类型。getDeclaredConstructor(Class>... parameterTypes)
:用于获取类自身声明的构造方法(无论访问修饰符是什么),但不能获取从父类继承的构造方法。newInstance(Object... args)
:用于调用构造方法创建对象,参数是构造方法的参数。通过反射,我们可以获取类的继承关系,包括父类和实现的接口。下面是一个获取继承关系的示例:
public class InheritanceDemo {
public static void main(String[] args) {
// 获取String的Class实例
Class> cls = String.class;
System.out.println("类名: " + cls.getName());
// 获取父类
Class> superClass = cls.getSuperclass();
System.out.println("父类: " + (superClass != null ? superClass.getName() : "没有父类"));
// 获取实现的接口
Class>[] interfaces = cls.getInterfaces();
System.out.println("实现的接口:");
for (Class> iface : interfaces) {
System.out.println(" " + iface.getName());
}
// 递归获取所有父类
System.out.println("所有父类:");
printSuperclasses(cls);
}
// 递归打印所有父类
private static void printSuperclasses(Class> cls) {
if (cls != null && cls != Object.class) {
System.out.println(" " + cls.getName());
printSuperclasses(cls.getSuperclass());
}
}
}
在上述代码中:
getSuperclass()
:用于获取类的直接父类,如果没有父类(如Object
类),则返回null
。getInterfaces()
:用于获取类实现的所有接口。通过反射获取类的继承关系,我们可以在运行时动态了解类的层次结构,这对于一些需要动态处理类关系的场景非常有用。
class
及interface
创建对应的Class
实例,该实例保存了class
及interface
的所有信息。Class
实例后,我们可以通过反射获取class
的所有信息,包括字段、方法、构造方法、继承关系等。class
的方式,我们可以在运行期根据条件来控制加载class
,这为许多 Java 框架和库的实现提供了基础。参考链接:
Class类 - Java教程 - 廖雪峰的官方网站