Java反射机制详解及示例
面试一家中小型公司 问到反射 居然回答不上来,不能忍 下来好好的整理了一下反射相关的东东,希望能帮到大家。
这里是目录 嘻嘻嘻
什么是反射?
反射是一种能够在程序运行时动态访问、修改某个类中任意属性(状态)和方法(行为)的机制(包括private实例和方法),Java反射机制提供了以下几个功能:
- 在运行时判断任意一个对象所属的类;
- 在运行时构造任意一个类的对象;
- 在运行时判断任意一个类所具有的成员变量和方法;
- 在运行时调用任意一个对象的方法。
反射涉及到四个核心类:
java.lang.Class.java
:类对象;java.lang.reflect.Constructor.java
:类的构造器对象;java.lang.reflect.Method.java
:类的方法对象;java.lang.reflect.Field.java
:类的属性对象;
反射的用途
反射可以用于以下几种情况:
1. 操作因访问权限限制的属性和方法
如果某个类中的属性或方法被设置为private
,则该属性或方法在类外部是无法访问的,但是通过反射可以绕过访问权限限制,实现对其进行访问或修改。
2. 实现自定义注解
通过反射,可以获取并解析类中的注解信息,进而实现自定义注解的功能。
3. 动态加载第三方jar包,解决Android开发中方法数不能超过65536个的问题
当一个应用中方法数超过一定数量时,可能会出现java.lang.IllegalArgumentException: method ID not in [0, 0xffff]:
的错误,通过反射可以动态加载第三方jar包,解决这个问题。
4. 按需加载类,节省编译和初始化APK的时间
通过反射,可以在运行时动态地加载和卸载类,从而实现按需加载类的功能,节省编译和初始化APK的时间。
反射工作原理
当我们编写完一个Java项目之后,每个java文件都会被编译成一个.class
文件,这些Class
对象承载了这个类的所有信息,包括父类、接口、构造函数、方法、属性等,这些class文件在程序运行时会被ClassLoader
加载到虚拟机中。当一个类被加载以后,Java虚拟机就会在内存中自动产生一个Class
对象。我们通过new
的形式创建对象实际上就是通过这些Class
来创建,只是这个过程对于我们是不透明的而已。
反射的工作原理就是借助Class.java
、Constructor.java
、Method.java
、Field.java
这四个类在程序运行时动态访问和修改任何类的行为和状态。当一个类被加载以后,Java虚拟机会在内存中自动产生一个Class对象。我们可以通过这个Class对象获取该类的构造器、方法和属性等信息,并对它们进行访问和修改。
其中,Class对象是反射的核心,它代表了一个类的类型信息。我们可以通过以下方式获取Class对象:
- 调用对象的getClass()方法获取Class对象
- 调用类的.class属性获取Class对象
- 使用Class.forName()方法获取Class对象
一旦获取了Class对象,我们就可以通过它来创建类的实例、调用方法和修改属性等操作。例如,我们可以通过Class对象调用newInstance()方法来创建一个类的实例:
Class clazz = Class.forName("com.demo.Student");
Object obj = clazz.newInstance();
除了创建实例外,我们还可以通过Class对象获取类的构造器、方法和属性等信息。例如,我们可以通过Class对象获取指定名称和参数类型的构造器:
Class clazz = Class.forName("com.demo.Student");
Constructor constructor = clazz.getConstructor(String.class, int.class);
通过获取类的方法和属性等信息,我们可以在运行时动态地访问和修改类的行为和状态。这为我们提供了更大的灵活性和扩展性,使得我们可以编写更加智能和灵活的程序
其中,Class类是反射的核心类,它代表了一个类的信息,包括类的名字、包名、父类、实现的接口、构造器、方法和属性等。Constructor类代表类的构造方法,Method类代表类的方法,Field类代表类的属性。
通过这些反射类,我们可以在程序运行时动态地获取并操作类的信息,从而实现一些非常有用的功能。下面将介绍如何通过反射来实现以下8个功能。
1. 获取类信息的三种方式
Java中有三种方式可以获取一个类的Class对象:
-
通过类名获取Class对象:使用Class.forName(“className”)方法,需要指定类的全限定名,例如:
Class<?> clazz = Class.forName("java.lang.String");
-
通过类的实例获取Class对象:使用Object.getClass()方法,例如:
String str = "Hello, World!"; Class<?> clazz = str.getClass();
-
直接获取类的Class对象:使用类名.class语法,例如:
Class<?> clazz = String.class;
2. 获取当前类的所有方法和获取当前类及其父类的所有方法
通过Class类的getDeclaredMethods()方法可以获取当前类声明的所有方法,不包括父类的方法。例如:
Class<?> clazz = MyClass.class;
Method[] declaredMethods = clazz.getDeclaredMethods();
通过Class类的getMethods()方法可以获取当前类及其父类所有的公共方法。例如:
Class<?> clazz = MyClass.class;
Method[] methods = clazz.getMethods();
3. 获取当前类的所有实例和获取当前类及其父类的所有实例
通过Class类的newInstance()方法可以创建当前类的一个实例,例如:
Class<?> clazz = MyClass.class;
MyClass instance = (MyClass) clazz.newInstance();
通过Class类的getDeclaredConstructors()方法可以获取当前类声明的所有构造方法,例如:
Class<?> clazz = MyClass.class;
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
通过Constructor类的newInstance()方法可以调用指定的构造方法来创建当前类的实例,例如:
Class<?> clazz = MyClass.class;
Constructor<?> constructor = clazz.getDeclaredConstructor(String.class);
MyClass instance = (MyClass) constructor.newInstance("Hello, World!");
4. 获取父类信息
通过Class类的getSuperclass()方法可以获取当前类的父类,例如:
Class<?> clazz = MyClass.class;
Class<?> superClass = clazz.getSuperclass();
5. 获取接口信息
通过Class类的getInterfaces()方法可以获取当前类实现的所有接口,例如:
Class<?> clazz = MyClass.class;
Class<?>[] interfaces = clazz.getInterfaces();
6. 操作因访问权限限制的属性和方法
反射机制可以绕过Java语言的访问控制,可以访问和修改被private、protected修饰的属性和方法。例如:
public class MyClass {
private void testMethod() {
System.out.println("privateMethod is called");
}
}
public class Main {
public static void main(String[] args) throws Exception {
MyClass obj = new MyClass();
Method method = MyClass.class.getDeclaredMethod("testMethod");
method.setAccessible(true);//将testMethod方法的访问权限设为可访问
method.invoke(obj);//方法调用了obj对象中的privateMethod方法
}
}
7.获取父类信息
可以通过 Class
对象的 getSuperclass()
方法获取当前类的父类信息,示例代码如下:
Class<?> superClass = clazz.getClass().getSuperclass();
System.out.println("Superclass: " + superClass.getName());
8.获取接口信息
可以通过 Class
对象的 getInterfaces()
方法获取当前类实现的所有接口信息,示例代码如下:
Class<?>[] interfaces = clazz.getClass().getInterfaces();
for (Class<?> interfaceClass : interfaces) {
System.out.println("Interface: " + interfaceClass.getName());
}
9.反射方法和实例性能比较
反射机制相比于直接调用方法和访问属性会有一定的性能损失,因为它需要动态解析并调用方法,而直接调用方法则是静态编译过程中已经确定了的。以下是反射方法和实例性能比较的示例代码:
public class Person {
public void sayHello(String str) {
}
}
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
Person person = new Person();
long startTime = System.currentTimeMillis();
for (int i = 0; i < 10000000; i++) {
person.sayHello("world");
}
long endTime = System.currentTimeMillis();
System.out.println("Direct call: " + (endTime - startTime) + " ms");
// 通过反射调用方法
startTime = System.currentTimeMillis();
Class personRe = Person.class;
Method sayHelloMethod = personRe.getMethod("sayHello", String.class);
for (int i = 0; i < 10000000; i++) {
sayHelloMethod.setAccessible(true);
/**
* sayHelloMethod.invoke(personRe, "world"); 我刚开始是直接将personRe字节码放入
* 会报错 :
* object is not an instance of declaring class
* 通过查看文章 除非你的对象是static 的 否则 你需要通过newInstance来调用该方法
*/
sayHelloMethod.invoke(personRe.newInstance(), "world");
}
endTime = System.currentTimeMillis();
System.out.println("Reflect call: " + (endTime - startTime) + " ms");
}
可以看到,直接调用方法的性能比反射调用方法要高得多。因此,在性能要求较高的场景下,应该尽量避免使用反射机制。