1.什么是反射?
Java之所以被称为动态语言,其中有个特点就是因为反射机制。Java的反射是指在运行时检查类、方法、属性等结构的能力,以及在运行时实例化对象、调用方法、获取或设置属性等。通过反射,可以在编译时未知类的情况下操作类的方法和属性。这使得程序可以动态地加载、检查和使用类,以及在运行时修改类的行为。
2.反射的例子
import java.lang.reflect.Constructor;
class MyClass {
private String message;
public MyClass() {
this.message = "你好,反射!";
}
public void showMessage() {
System.out.println(message);
}
}
public class Main {
public static void main(String[] args) {
try {
Class<?> myClass = Class.forName("MyClass");
Constructor<?> constructor = myClass.getConstructor();
Object myObject = constructor.newInstance();
System.out.println("类名: " + myClass.getName());
myClass.getMethod("showMessage").invoke(myObject);
} catch (Exception e) {
e.printStackTrace();
}
}
}
3.类加载原理
相信大家都经历不少了面向对象编程的语言,会发现大多数语言中定义类的都是大同小异:
- 所有的类都有类名;
- 所有的类都有0或者n个属性,每个属性都有一个数据类型和名称;
- 所有的类都有0或者n个方法,每个方法都有对应的参数列表和返回值。
- 所有的类都有静态方法、静态成员变量。
- 所有的类都有对应修饰符修饰。
所有的类在以上几点都是相同,只是根据实际的需求,导致他们的方法名、属性、类名有不同。所以针对这样一个公共特性,java用一个特殊的类Class去表示这些自定义类的行为。
public final class Class<T> implements java.io.Serializable,
GenericDeclaration,
Type,
AnnotatedElement,
TypeDescriptor.OfField<Class<?>>,
Constable {
private static final int ANNOTATION= 0x00002000;
private static final int ENUM = 0x00004000;
private static final int SYNTHETIC = 0x00001000;
private static native void registerNatives();
static {
registerNatives();
}
private Class(ClassLoader loader, Class<?> arrayComponentType);
public String toString();
public String toGenericString();
static String typeVarBounds(TypeVariable<?> typeVar);
@CallerSensitive
public static Class<?> forName(String className);
@CallerSensitive
public static Class<?> forName(String name, boolean initialize,
ClassLoader loader);
...
}
这里源码来自于Class.java,可以使用IDEA进行跳转查看。
Class也有相应的数据结构去存储这些的类的公共特性。首先看看Field,这里面主要存储类的属性里面的名称和类型。那么如何获得Fields呢,首先看看下面的两个方法:getFields和getDeclaredFields。
两个方法的不同点在于:
-
getFields
方法用于获取类及其父类中所有的公共(public)字段。这包括类声明的公共字段以及从父类继承而来的公共字段。 -
getDeclaredFields
方法用于获取类中声明的所有字段,包括公共、受保护、默认(包内可见)和私有字段,但不包括从父类继承而来的字段。
@CallerSensitive
public Field[] getFields() throws SecurityException {
@SuppressWarnings("removal")
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkMemberAccess(sm, Member.PUBLIC, Reflection.getCallerClass(), true);
}
return copyFields(privateGetPublicFields());
}
@CallerSensitive
public Field[] getDeclaredFields() throws SecurityException {
@SuppressWarnings("removal")
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkMemberAccess(sm, Member.DECLARED, Reflection.getCallerClass(), true);
}
return copyFields(privateGetDeclaredFields(false));
}
Field这块代码在Field.java中,只截取我们感兴趣的部分的:
public final
class Field extends AccessibleObject implements Member {
private final Class<?> clazz;
private final int slot;
// This is guaranteed to be interned by the VM in the 1.4
// reflection implementation
private final String name;
private final Class<?> type;
}
这里我们主要关注的是如何利用field去获得类的属性的名字和类型,关注下两个方法:getName,getType。同样的在getType以后我们还可以利用getName拿到类型的名称:
try {
Class<?> myClass = Class.forName("MyClass");
Field[] fields = myClass.getDeclaredFields();
for(Field field : fields){
String fieldName = field.getName();
System.out.println(fieldName);
Class<?> fieldType = field.getType();
System.out.println(fieldType.getName());
}
} catch (Exception e) {
e.printStackTrace();
}
其次是方法,方法主要是由Method.java里的Method类进行封装的,同样我们也只截取源码中关键的部分:
public final class Method extends Executable {
private final String name;
private final Class<?> returnType;
private final Class<?>[] parameterTypes;
private final Class<?>[] exceptionTypes;
}
那么method总共包含以下基本信息:name,returnType,paramterTypes,exceptionTypes,对于这些信息我们同样可以get的方式提取出这些关键信息,这里不再赘述。
主要说明一下如何提取method,同样存在两个方法,一个是getDeclaredMethods,第二个是getMethods,这两个函数的区别和Field是类似的。
整个类的生命周期如下图所示:
1)类的加载阶段
根据类的全限定名找到对应的二进制字节流。可以在ZIP文件、网络的Applet、运算时生成、JSP、数据库读取。
2)验证阶段
确保Class文件包含的字节流形式符合虚拟机要求,并且不会危害虚拟机自身健康。
其中包括以下验证方式:
a.文件格式验证。(验证是否当前版本的文件格式要求)
b和c.元数据验证和字节码验证。(语义上的验证)
d.符号引用验证。
3)准备阶段
JVM开始首先为类变量(静态变量)分配内存。
例如下面的类成员变量:
static String str="abc";
首先为它分配一段空间,并将其置为NULL。而不是"abc"
但若该类变量(静态变量)被final修饰的话
static final String str="abc";
就会在一开始赋成你的想要的值。
4)初始化阶段
- 遇到 new、getstatic、putstatic、invokestatic 这四条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这4条指令的最常见的Java代码场景是:使用new关键字实例化对象的时候、读取或设置一个类的静态字段(被final修饰、已在编译器把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。
- 使用 java.lang.reflect 包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。
- 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
- 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。
- 当使用 JDK1.7 动态语言支持时,如果一个 java.lang.invoke.MethodHandle实例最后的解析结果 REF_getstatic,REF_putstatic,REF_invokeStatic 的方法句柄,并且这个方法句柄所对应的类没有进行初始化,则需要先出触发其初始化。
顺序:
父类静态变量声明时 -> 父类静态代码块 -> 子类静态变量声明时 -> 子类静态代码块 -> 父类普通成员变量声明时 -> 父类代码块 -> 父类构造器 -> 子类普通成员变量声明时 -> 子类代码块 -> 子类构造器
class Grandpa
{
static
{
System.out.println("爷爷在静态代码块");
}
}
class Father extends Grandpa
{
static
{
System.out.println("爸爸在静态代码块");
}
public static int factor = 25;
public Father()
{
System.out.println("我是爸爸~");
}
}
class Son extends Father
{
static
{
System.out.println("儿子在静态代码块");
}
public Son()
{
System.out.println("我是儿子~");
}
}
public class InitializationDemo
{
public static void main(String[] args)
{
System.out.println("爸爸的岁数:" + Son.factor); //入口
}
}
5)使用阶段
从入口方法中使用代码
6)卸载
代码执行完毕,销货Class对象,释放对应的内存空间。