反射

类加载机制


当调用 java 命令来运行某个 java 程序时,会启动一个 JVM 进程,同一个 JVM 中所有线程、变量都处于同一个进程中,共享 JVM 的内存区域。

以下情况 JVM 会退出:

  1. 程序正常执行结束
  2. 使用System.exit(0)方法
  3. 出现异常时,没有捕获异常
  4. 平台强制结束 JVM 进程。

JVM 进程一旦结束,该进程中内存中的数据将会丢失

1
2
3
4
5
6
7
8
class Demo {
static int age;
static int num = 10;

static {
age = 0;
}
}

主程序中使用到某个类时,如果该类还没有加载到内存中,系统会通过加载、连接、初始化三个步骤对该类初始化操作。

  1. 类的加载

    • 将 class 文件(字节码文件)载入内存中,并创建一个 java.lang.Class对象,我们成为 字节码对象。
    • 此过程由**类加载器(ClassLoader)**完成,类加载器由 JVM 提供,该类是系统的,我们也可以集成 ClassLoader 类来提供自定义的类加载器。
    • 不同的类加载器可以实现加载本地字节码文件、jar 包中的字节码、网络加载字节码等。
  2. 类的连接:

    • 当类被加载进入内存后,系统会产生一个对应的 class 对象,接着把类的二进制数据合并到 JRE 中
    • 验证被夹在的类是否有正确的内部结构,然后为类的 static 变量分配内存,设置默认值,
> 符号引用:符号引用给出了被引用的内容的名字和该内容的一些信息--这些信息足以唯一的识别一个类、字段、方法。对于其他类的符号引用必须给出类的全名。 3. 类的初始化: - JVM 负责对类进行初始化,主要就是对 static 变量进行初始化 - 如果该类还未被加载和连接,程序先加载并连接该类。 - 如果该类的直接父类还未被初始化,先初始化父类 - 如果类中有初始化语句 (static 代码块) ,则依次执行这些初始化代码块 # 反射的概念 --- **元数据:用来描述数据类型的数据,元数据(metadata)** **反射:得到类的元数据的过程,运行时动态获取某一个类中的成员信息(构造器、方法、字段、内部类、接口、父类等)** - Class:所有的类, - Constructor 所有的构造器 - Method 所有的方法 - Field 所有的字段 类一旦加载进入内存,就会变为 Class 对象 # Class 类和 Class 实例 --- **Class 类:用来描述类或者接口的字节码文件的结构信息的类型** **Class类的实例:在 JVM 中的字节码对象就是 Class 实例,枚举是一种特殊的类,注解是特殊的接口** 当程序第一次使用 java.util.Date 类时,会把该类的字节码加载入 JVM 并且创建一个 Class 对象, 为了区分 Class 类的实例表示 哪个类的字节码对象,设计者提供了 Class 比如 java.lang.String 类的字节码类型: Class *完全限定名:包名.类名。说到获取全限定名,就是要使用反射来做一些操作* > 获取字节码对象
1
2
3
4
5
6
7
8
9
// 获取字节码对象 

// 方法一:
Class<java.util.Date> clz1 = java.util.Date.class;
// 方法二:
java.util.Date date = new java.util.Date();
Class<?> clz2 = date.getClass();
// 方法三:(使用最多)
Class<?> clz3 = Class.forName("java.util.Date");
> 注意:基本数据类型没有对象一说,因此,想获取基本数据类型的字节码对象 > 九大内置Class实例:JVM 中预先提供好了的 Class 实例:byte、short、int、long、float、double、boolean、char、void,表示byte.class ... > 8大基本数据类型的包装类中,都有一个常量 TYPE,用于返回包装类对应的基本数据类型的字节码对象
1
2
System.out.println(Integer.TYPE == int.class);  true
System.out.println(Integer.class == int.class); false
> 数组的 Class 实例:方式1 数组类型.class 方式2 数组对象.class,所有相同的维数且元素类型相同的数组共享同一份字节码对象。 # 获取类中的构造器 --- 如何通过反射获取一个类的构造器: 1. 获取该类的字节码对象 2. 从该字节码对象中去找需要获取的构造器 **Constructor类:表示类中的构造器类型,Constructor 的实例就是某一个类中的某一个构造器** `public Constructor

[] getConstructors() `
该方法只能获取当前 Class 所表示类的 public修饰的构造器

public Constructor<?>[] getDeclaredConstructors()
获取当前 Class 所表示类的所有构造器,和访问权限无关

`public Constructor getConstructor(Class

… parameterTypes)`
获取当前 Class 所表示类中指定的一个 public 的构造器参数; parameterTypes 表示:构造器参数的 Class 类型

1
2
3
4
public User(String name)

// 获取上述构造器对象
Constructor c = clz.getConstructor(String.class);

public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
获取当前 Class 所表示类中指定的一个构造器

获取一个类中的构造器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class User {
public User() {}
public User(String name) {}
private User(String name, int age) {}
}

// 获取指定的一个构造器
private static void getOne() throws Exception {
// 1. 获取构造器所在类的字节码对象
Class<User> clz = User.class;
// 2. 获取 clz 对象中 public User()
Constructor<User> con = clz.getConstructor();
System.out.println(con); // public java进阶.com.lizhaoloveit._Constructor.User()
// 3. 获取 public User(String name)
con = clz.getDeclaredConstructor(String.class);
System.out.println(con);// public java进阶.com.lizhaoloveit._Constructor.User(java.lang.String)
// 4. 获取 private User(String name, int age)
con = clz.getDeclaredConstructor(String.class, int.class);
System.out.println(con); // 抛异常
}

// 获取所有的构造器
private static void getAll() {
// 1. 获取构造器所在的字节码对象
Class<User> clz = User.class;
// 2. 获取clz对象中所有的构造器
Constructor<User>[] cs = clz.getDeclaredConstructors();
System.out.println(cs.length);
for (Constructor<User> c : cs) {
System.out.println(c);
}
// public java进阶.com.lizhaoloveit._Constructor.User()
// public java进阶.com.lizhaoloveit._Constructor.User(java.lang.String)
// private java进阶.com.lizhaoloveit._Constructor.User(java.lang.String,int)
}

使用反射调用构造器创建对象


  1. 找到构造器所在的类的字节码对象
  2. 获取构造器对象
  3. 使用反射,创建对象

Constructor 类的常用方法 public T newInstance(Object... initargs) 如果调用带参构造器,只能使用该方法。
参数: initargs:表示调用构造器的实际参数
返回: 返回创建的实例, T 表示 Class 所代表的字节码对象的类型
如果一个类中的构造器是外界可以直接访问,同时没有参数,可以直接用 Class 类中的 newInstance 方法创建对象。

public Object newInstance() 相当于 new 类名();

Class 类有 newInsatnce() 方法 :创建字节码对象的实例, 等价于 constructor 的 newInstance()方法, 注意:需要当前类有公共的无参构造器方法。

调用私有构造器


1
2
3
4
5
6
7
8
9
10
Class<Person> cls = Person.class;
// 调用 public Person()
Constructor<Person> con = cls.getConstructor();
con.newInstance();
// 调用 public Person(String name)
con.newInstance("李朝");
// 调用 private Person(String name, int age)
con.cls.getDeclaredConstructor(String.class, int.class);
con.setAccessible(true); // 设置当前构造器为可访问
con.newInstance("李朝", 17);

意味着任何的私有变量和私有方法都可以用字节码对象来访问和调用。

使用反射调用方法


public Method[] getMethods()
获取包括自身和继承过来的所有 public 方法

public Method[] getDeclaredMethods()
获取自身类中所有的方法(不包括继承的,和访问权限无关)

public Method getMethod(String methodName, Class<?>... parameterTypes)
表示调用指定的一个公共方法(包括继承的)

public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
表示调用指定的一个本类中的方法(不包括继承的)

methodName: 表示被调用方法的名字, parameterTypes: 表示调用方法的参数的 Class 类型 String.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class User {
public void doWork(){}
public static void doWork(String name) {}
private String doWork(String name, int age) {}
}

// 获取 User 类中指定的一个方法
private static void getOne() throws Exception {
Class cls = User.class;
Method m = cls.getMethod("doWork");
System.out.println(m);

m = cls.getMethod("doWork", String.class);
System.out.println(m);

m = cls.getDeclareMethod("doWork", String.class, int.class);
System.out.println(m);
}

Method[] ms = cls.getMethods();
System.out.println(ms.length);

for (Method m : ms) {
System.out.println(m); // 11
}

// 表示调用指定的一个本类中的方法(不包括继承的)
ms = cls.getDeclaredMethods();
System.out.println(ms.length);
for (Method m : ms) {
System.out.println(m); // 3
}

public Object invoke(Object obj, Object… args) 表示调用当前 Method 所表示的方法参数

obj: 表示拥有该方法的类的对象, args:表示调用方法时传递的实际参数, 返回:方法返回的结果。

注意: 调用私有方法时,需要调用 Method 对象的 setAccessible 方法设置访问权限

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) throws Exception {
class cls = Person.class;
Method m = cls.getMethod("doWork");
Object res = m.invoke(cls.newInstance());
System.out.println(res); // null 无返回值

m = cls.getDeclaredMethod("doWork", String.class, int.class);
m.setAccessible(true);
res = m.invoke(cls.newInstance(), "Will", 17);
System.out.println(res); // 打印返回对象的 toString() 方法
}

如果方法是静态方法,则第一个 obj 参数,传入 null 即可。

1
2
3
Method m1 = cls.getMethod("doWork", String.class);
System.out.println(m1);
m1.invoke(null, "Will");

调用参数时,把实际参数 都装到 Object[] 数组中传入即可

操作字段(不经常用)


Field 专门描述字段,一般用 get set 方法操作成员变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public Field[] getFields() // 获取所有 public 修饰的变量
public Field getField(String name) // 根据字段名获取字段

Class cls = Person.class;
Field f1 = cls.getField("name");
Field f2 = cls.getDeclaredField("id");
Object obj = cls.newInstance();

f1.set(obj, "丽霞");
f2.setAccessible(true);
f2.set(obj, 1L);

f1.get(obj);
f2.get(obj);

反射获取内部类


如何通过反射构造出内部类的实例,内部类分为成员内部类,局部内部类,静态内部类,匿名内部类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
public class MathImpl implements IMath {
public MathImpl() {
}
public class InnerA {
public InnerA() {
}
public void name() {
System.out.println("我是公共的");
}
@Override
public String toString() {
return "InnerA";
}
}
private class InnerB {
public InnerB() {
}
private void name() {
System.out.println("我是私有的");
}
@Override
public String toString() {
return "InnerB";
}
}
private static class InnerC {
public InnerC() {
}
private static void name() {
System.out.println("我是静态的");
}
@Override
public String toString() {
return "InnerC";
}
}
private IMath m = new IMath() {
@Override
public void minus(int a, int b) {
System.out.println("minus");
}
@Override
public void add(int a, int b) {
System.out.println("add");
}
};
@Override
public void add(int a, int b) {
System.out.println(a + b);
}
@Override
public void minus(int a, int b) {
System.out.println(a - b);
}
}
public interface IMath {
/**
* 计算2个数的加法
* @param a
* @param b
*/
public void add(int a, int b);
/**
* 计算2个数的减法
* @param a
* @param b
*/
public void minus(int a, int b);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Test
public void testClass() throws Exception {
Class cls = Class.forName("com.lizhaoloveit._Junit.MathImpl");
Class<?>[] clss = cls.getDeclaredClasses();
for (Class<?> class1 : clss) {
// 获取方法的修饰符
int mod = class1.getModifiers();
String modifier = Modifier.toString(mod);
if (modifier.contains("static")) {
// 构造静态成员内部类实例
Constructor con = class1.getConstructor();
Object obj = con.newInstance();
System.out.println(obj);
Method m = class1.getDeclaredMethod("name");
m.setAccessible(true);
m.invoke(class1); // name 方法被执行
} else {
// 构造成员内部类实例
Constructor con1 = cls.getConstructor();
// 获取构造器,需要传入外部类 Class 参数
Constructor con = class1.getConstructor(cls);
// 创建对象,需要外部类的构造器对象
Object obj = con.newInstance(container);
System.out.println(obj);
Method m = class1.getDeclaredMethod("name");
m.setAccessible(true);
m.invoke(obj);
}
}
}

System 类中,数组拷贝方法及实现


Class 类

  • int getModifiers() 获得修饰符
  • String getName() 返回类的 全限定名
  • Package getPackage() 获得该类的包
  • String getSimpleName() 获得类的简单名字
  • Class getSuperclass() 获得类的父类
  • boolean isArray() 判断该 Class 实例是否是数组
  • boolean isEnum() 判断该 Class 实例是否是枚举
  • getComponentType() Returns the Class representing the component type of an array. If this class does not represent an array class this method returns null.

返回这个 Class 代表的数组的元素类型, 如果这个 class 没有代表一个数组, 这个方法将返回 null

System 类中,数组拷贝方法

public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length) {
if (src == null || dest == null) {
throw new NullPointerException("源数组和目标数组都不能为 null");
}

if (!src.getClass().isArray || !dest.getClass().isArray()) {
throw new ArrayStoreException("源和目标必须都是数组");
}

if (src.getClass().getComponentType() != dest.getClass().getComponentType()) {
throw new ArrayStoreException("源和目标元素类型必须相同");
}

for (int index = srcPos; index < srcPos + length; index++) {
// 获取需要拷贝的元素
Object val = Array.get(src, index);
// 给目标设置值
Array.set(dest, destPos, val);
destPos++;
}

}

加载资源文件路径


加载 properties 文件,只能用 Properties 的 load 方法。

使用 ClassLoader 类加载器,类加载器默认是从 classPath 根路径去寻找文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 使用相对路径-相对于 classPath 的根路径(字节码输出目录)
private static void test() throws Exception {
Properties p = new Properties();
ClassLoader loader = Thread.currentTread().getContextClassLoader();
p.load(inStream);
System.out.println(p);
}

// 使用相对路径-相对于当前加载资源文件的字节码路径
private static void test2() throws Exception {
InputStream p = new Properties();
InputStream inStream = LoadResourceDemo.class.getResourceAsStream("db.properties");
p.load(inStream);
System.out.println(p);
}
文章作者: Ammar
文章链接: http://lizhaoloveit.cn/2019/06/26/%E5%8F%8D%E5%B0%84/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Ammar's Blog
打赏
  • 微信
  • 支付宝

评论