Java反射机制——类的加载方法、创建对象、获取方法以及结构

Reflection (反射)被视为动态语言的关键,为什么这么说呢,是因为它在运行时就确定下来了。反射机制允许程序在执行期间借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。

框架=反射+注解+设计模式

一、什么是反射?

加载完类之后,在堆内的方法区中就产生了一个Class类型的对象(一个类只有一个class对象),这个对象就包含了完整类的结构信息,我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象称之为:反射。

二、 反射中类的加载

1、什么是类的加载

什么是类的加载呢?类的加载就是将类的class文件读入内存,并为之创建一个java.lang.Class对象,也就是说当程序使用任何类时,系统都会为之建立一个java.lang.Class对象,创建对象是则系统会通过加载、连接、初始化这三个步骤对该类进行初始化。

Java反射机制——类的加载方法、创建对象、获取方法以及结构插图

2、类加载的过程

程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾),接着我们使用java.exe命令对某些字节码文件进行解释运行,相当于将某个字节码文件加载到内存中。此过程就称为类的加载。加载到内存中的类,我们就称为运行时的类,此运行时类,就作为Class的一个实例。

加载到内存中的运行时类,会缓存一定的时间,在此时间内,我们可以通过的方式来获取此运行时的类。

3、获取Class实例的几种方式

Java反射机制——类的加载方法、创建对象、获取方法以及结构插图2

1)方式一:调用运行时类的属性:.class

1
2
Class clazz1 = Person.class;
System.out.println(clazz1);

2)方式二:通过运行时类的对象,调用:getClass()

1
2
3
Person p1 = new Person();
Class clazz2 = p1.getClass();
System.out.println(clazz2);

3)方式三:调用Class的静态方法:forName(String classPath)

1
2
3
4
5
6
Class clazz3 = Class.forName("com.atguigu.java.Person");
//clazz3 = Class.forName("java.lang.String");
System.out.println(clazz3);
 
System.out.println(clazz1 == clazz2);
System.out.println(clazz1 == clazz3);

4)方式四:使用类的加载器:ClassLoader

1
2
3
4
5
ClassLoader classLoader = ReflectionTest.class.getClassLoader();
Class clazz4 = classLoader.loadClass("com.atguigu.java.Person");
System.out.println(clazz4);
 
System.out.println(clazz1 == clazz4);
Java反射机制——类的加载方法、创建对象、获取方法以及结构插图4

4、类的连接

类被加载之后,系统会为之生成一个Class对象,接着会进入连接阶段,连接阶段负责把类的二进制数据合并到JRE中。类的连接又分为下面三个阶段:

  • 验证:确保被加载类的正确性
  • 准备:负责为类的静态成员分配内存,并设置默认初始化值
  • 解析:将类中的符号引用替换为直接引用
Java反射机制——类的加载方法、创建对象、获取方法以及结构插图6

5、类的加载器

Java反射机制——类的加载方法、创建对象、获取方法以及结构插图8

类加载器负责将.class文件加载到内存中,并为之生成对应的Class对象。类加载器负责加载所有的类,系统为所有加载到内存中的类生成一个java.lang.Class 的实例。

1)类加载器的组成

  • Bootstrap ClassLoader 根类加载器: 也被称为引导类加载器,负责Java核心类的加载,比如System类,在JDK中JRE的lib目录下rt.jar文件中的类
  • Extension ClassLoader 扩展类加载器: 负责JRE的扩展目录中jar包的加载,在JDK中JRE的lib目录下ext目录System ClassLoader
  • 系统类加载器: 负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包和类路径,主要是我们开发者自己写的类。
Java反射机制——类的加载方法、创建对象、获取方法以及结构插图10

2)类加载器的作用

将class文集爱你字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时的数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。

类缓存:标准的javaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间,不过JVM垃圾回收机制可以回收这些Class对象。

Java反射机制——类的加载方法、创建对象、获取方法以及结构插图12

3)Java反射机制提供的功能

  • 在运行时判断任意一个对象所属的类
  • 在运行时构造任意一个类的对象
  • 在运行时判断任意一个类所具有的成员变量和方法
  • 在运行时获取泛型信息
  • 在运行时调用任意一个对象的成员变量和方法
  • 在运行时处理注解
  • 生成动态代理

4)相关的API

1
2
3
4
java.lang.Class:反射的源头
java.lang.reflect.Method
java.lang.reflect.Field
java.lang.reflect.Constructor

三、创建类的对象几种方式

1、创建类的对象的方式

  • 方式一:new+构造器
  • 方式二:要创建Xxx类的对象。
  • 方式三:通过反射

2、代码举例

1
2
3
Class<Person> clazz=Person.class;
Person obj=clazz.newInstance();
System.out.println(obj);

3、说明

newInstance():调用此方法,创建对应的运行时类的对象。内部调用了运行时类的空参的构造器。

1)要想此方法正常的创建运行时类的对象的要求

  • 运行时类必须提供空参的构造器
  • 空参的构造器的访问权限得够。通常,设置为public。

2)在javabean中要求提供一个public的空参构造器的原因

  • 便于通过反射,创建运行时类的对象
  • 便于子类继承此运行时类时,默认调用super()时,保证父类此构造器

四、获取运行时类的完整结构

通过反射,获取对应的运行时类中所有的属性、方法、构造器、父类、接口、父类的泛型、包、注解、异常等。典型代码:

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
@Test
public void test1(){
    Class clazz = Person.class;
    //获取属性结构
    //getFields():获取当前运行时类及其父类中声明为public访问权限的属性
    Field[] fields = clazz.getFields();
    for(Field f : fields){
        System.out.println(f);
    }
    System.out.println();
  
    //getDeclaredFields():获取当前运行时类中声明的所属性。(不包含父类中声明的属性
    Field[] declaredFields = clazz.getDeclaredFields();
    for(Field f : declaredFields){
        System.out.println(f);
    }
}
 
@Test
public void test1(){
    Class clazz = Person.class;
    //getMethods():获取当前运行时类及其所父类中声明为public权限的方法
    Method[] methods = clazz.getMethods();
    for(Method m : methods){
        System.out.println(m);
    }
    System.out.println();
    //getDeclaredMethods():获取当前运行时类中声明的所方法。(不包含父类中声明的方法
    Method[] declaredMethods = clazz.getDeclaredMethods();
    for(Method m : declaredMethods){
        System.out.println(m);
    }
}
 
/*
获取构造器结构
 */@Test
public void test1(){
    Class clazz = Person.class;
    //getConstructors():获取当前运行时类中声明为public的构造器
    Constructor[] constructors = clazz.getConstructors();
    for(Constructor c : constructors){
        System.out.println(c);
    }
 
    System.out.println();
    //getDeclaredConstructors():获取当前运行时类中声明的所的构造器
    Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
   for(Constructor c : declaredConstructors){
        System.out.println(c);
    }
}
 
/*
获取运行时类的父类
 */@Test
public void test2(){
    Class clazz = Person.class;
    Class superclass = clazz.getSuperclass();
    System.out.println(superclass);
}
 
/*
获取运行时类的带泛型的父类
 */@Test
public void test3(){
    Class clazz = Person.class;
    Type genericSuperclass = clazz.getGenericSuperclass();
    System.out.println(genericSuperclass);
}
 
/*
获取运行时类的带泛型的父类的泛型
代码:逻辑性代码  vs 功能性代码
 */@Test
public void test4(){
    Class clazz = Person.class;
    Type genericSuperclass = clazz.getGenericSuperclass();
   ParameterizedType paramType = (ParameterizedType) genericSuperclass;
 
    //获取泛型类型
    Type[] actualTypeArguments = paramType.getActualTypeArguments();
//        System.out.println(actualTypeArguments[0].getTypeName());
    System.out.println(((Class)actualTypeArguments[0]).getName());
}
 
 
 
/*
获取运行时类实现的接口
 */@Test
public void test5(){
    Class clazz = Person.class;
    Class[] interfaces = clazz.getInterfaces();
    for(Class c : interfaces){
        System.out.println(c);
    }
    System.out.println();
 
    //获取运行时类的父类实现的接口
    Class[] interfaces1 = clazz.getSuperclass().getInterfaces();
    for(Class c : interfaces1){
        System.out.println(c);
    }
}
 
/*
    获取运行时类所在的包
 */@Test
public void test6(){
    Class clazz = Person.class;
    Package pack = clazz.getPackage();
    System.out.println(pack);
}
 
/*
    获取运行时类声明的注解
 */@Test
public void test7(){
    Class clazz = Person.class;
    Annotation[] annotations = clazz.getAnnotations();
    for(Annotation annos : annotations){
        System.out.println(annos);
    }
}

五、调用运行时类的指定结构

1、调用指定的属性

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
@Test
public void testField1() throws Exception {
    Class clazz = Person.class;
  
    //创建运行时类的对象
    Person p = (Person) clazz.newInstance();
    //1. getDeclaredField(String fieldName):获取运行时类中指定变量名的属性
    Field name = clazz.getDeclaredField("name");
  
    //2.保证当前属性是可访问的
     name.setAccessible(true);
    //3.获取、设置指定对象的此属性值
    name.set(p,"Tom");
    System.out.println(name.get(p));
}

2、调用指定的方法

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Test
public void testMethod() throws Exception {
    Class clazz = Person.class;
 
    //创建运行时类的对象
    Person p = (Person) clazz.newInstance();
    /*
    1.获取指定的某个方法
    getDeclaredMethod():参数1 :指明获取的方法的名称  参数2:指明获取的方法的形参列表
     */    Method show = clazz.getDeclaredMethod("show", String.class);
    //2.保证当前方法是可访问的
    show.setAccessible(true);
 
 
    /*
    3. 调用方法的invoke():参数1:方法的调用者  参数2:给方法形参赋值的实参
    invoke()的返回值即为对应类中调用的方法的返回值。
     */    Object returnValue = show.invoke(p,"CHN"); //String nation = p.show("CHN");
    System.out.println(returnValue);
    System.out.println("*************如何调用静态方法*****************");
    // private static void showDesc()
 
    Method showDesc = clazz.getDeclaredMethod("showDesc");
    showDesc.setAccessible(true);
    //如果调用的运行时类中的方法没返回值,则此invoke()返回null
//        Object returnVal = showDesc.invoke(null);
      Object returnVal = showDesc.invoke(Person.class);
      System.out.println(returnVal);//null
}

3、调用指定的构造器

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
@Test
public void testConstructor() throws Exception {
     Class clazz = Person.class;
    //private Person(String name)
  
    /*
    1.获取指定的构造器
    getDeclaredConstructor():参数:指明构造器的参数列表
     */    Constructor constructor = clazz.getDeclaredConstructor(String.class);
  
    //2.保证此构造器是可访问的
    constructor.setAccessible(true);
  
    //3.调用此构造器创建运行时类的对象
    Person per = (Person) constructor.newInstance("Tom");
    System.out.println(per);
}

发表评论

欢迎阅读『Java反射机制——类的加载方法、创建对象、获取方法以及结构|Java、反射机制|Nick Tan-梓潼Blog』