- Java的反射机制是Java特性之一,反射机制是构建框架技术的基础所在。灵活掌握Java反射机制,对以后学习框架技术有很大的帮助。
- 本篇文章用到的代码在我的github上面:BitHachi/JJava_core_book/tree/master/src/JavaSE/Chapter5/Section57。
1.什么是Java的反射呢?
大家都知道,要让Java程序能够运行,那么就得让Java类要被Java虚拟机加载。Java类如果不被Java虚拟机加载,是不能正常运行的。现在我们运行的所有的程序都是在编译期的时候就已经知道了你所需要的那个类的已经被加载了。<br /> Java的反射机制是在编译并不确定是哪个类被加载了,而是在程序运行的时候才加载、探知、自审。使用在编译期并不知道的类。这样的特点就是反射。<br /> 反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,属性、构造器、方法,并可以调用对应的属性和方法。<br /> Reflection(反射)是被视为`动态语言`的关键。<br /> 加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为: 反射<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/21663578/1653570813899-2b1767b0-ee8a-47c6-8169-5c8cde3254a0.png#clientId=u699de279-7617-4&crop=0&crop=0&crop=1&crop=1&id=SzM65&name=image.png&originHeight=180&originWidth=1134&originalType=binary&ratio=1&rotation=0&showTitle=false&size=51337&status=done&style=none&taskId=u6b57dcaa-e315-4796-b48e-46d73866586&title=)<br />**补充:动态语言 vs 静态语言**<br />**动态语言**<br /> `是一类在运行时可以改变其结构的语言`:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是 `在运行时代码可以根据某些条件改变自身结构`。主要动态语言:Object-C、C#、JavaScript、PHP、Python、Erlang。<br />**静态语言**<br /> 与动态语言相对应的,`运行时结构不可变的语言就是静态语言`。如Java、C、C++。
Java不是动态语言,但Java可以称之为
“准动态语言”
。即Java有一定的动态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性
。Java的动态性让编程的时候更加灵活!
2.Java反射有什么作用呢?
假如我们有两个程序员,一个程序员在写程序的时候,需要使用第二个程序员所写的类,但第二个程序员并没完成他所写的类。那么第一个程序员的代码能否通过编译呢?这是不能通过编译的。利用Java反射的机制,就可以让第一个程序员在没有得到第二个程序员所写的类的时候,来完成自身代码的编译。<br /> `Java的反射机制它知道类的基本结构,这种对Java类结构探知的能力,我们称为Java类的“自审”。`大家都用过IDEA和eclipse。当我们构建出一个对象的时候,去调用该对象的方法和属性的时候。一按点,编译工具就会自动的把该对象能够使用的所有的方法和属性全部都列出来,供用户进行选择。这就是利用了Java反射的原理,是对我们创建对象的探知、自审。(反射是一种功能强大且复杂的机制。使用它的主要人员是工具构造者,而不是应用程序员。)<br />**Java 反射机制提供的功能:**
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时获取泛型信息
- 在运行时调用任意一个对象的成员变量和方法
- 在运行时处理注解
- 生成动态代理
反射相关的主要API:
- java.lang.Class: 代表一 个类,用来描述类的类
- java.lang.reflect.Method: 代表类 的 方法
- java.lang.reflect.Field: 代表类的 成员 变量
- java.lang.reflect.Constructor: 代表类 的 构造
-
3.Class类
要正确使用Java反射机制就得使用java.lang.Class这个类。它是Java反射机制的起源。当一个类被加载以后,Java虚拟机就会自动产生一个Class对象。通过这个Class对象我们就能获得加载到虚拟机当中这个Class对象对应的方法、成员变量以及构造方法的声明和定义等信息。<br /> 对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含了特定某个结构(class/interface/enum/annotation/primitive type/void/[])的有关信息。
Class本身也是一个类
- Class 对象只能由系统建立对象
- 一个加载的类在 JVM 中只会有一个Class实例
- 一个Class对象对应的是一个加载到JVM中的一个.class文件
- 每个类的实例都会记得自己是由哪个 Class 实例所生成
- 通过Class可以完整地得到一个类中的所有被加载的结构
- Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的Class对象
4.获取class类对象
首先我们得知道哪些类型可以有Class类对象?
(1)class:
外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
(2)interface:接口
(3)[]:数组
(4)enum:枚举
(5)annotation:注解_@_interface
(6)primitive type:基本数据类型
(7)void
示例:
public static void main(String[] args) {
Class c1 = Object.class;
Class c2 = Comparable.class;
Class c3 = String[].class;
Class c4 = int[][].class;
Class c5 = ElementType.class;
Class c6 = Override.class;
Class c7 = int.class;
Class c8 = void.class;
Class c9 = Class.class;
System.out.println(c1);
System.out.println(c2);
System.out.println(c3);
System.out.println(c4);
System.out.println(c5);
System.out.println(c6);
System.out.println(c7);
System.out.println(c8);
System.out.println(c9);
System.out.println("--------");
int[] a = new int[10];
int[] b = new int[100];
Class c10 = a.getClass();
Class c11 = b.getClass();
// 只要数组的元素类型与维度一样,就是同一个Class
System.out.println(c10 == c11);
}
运行结果:
class java.lang.Object
interface java.lang.Comparable
class [Ljava.lang.String;
class [[I
class java.lang.annotation.ElementType
interface java.lang.Override
int
void
class java.lang.Class
--------
true
**那么我们如何获取class对象呢?这里有四种方法:**
4.1 使用类对象的getClass()方法
-
4.2 Class.forName(classname)
使用 Class.forName(classname) 静态方法。当你知道该类的全路径名时,你可以使用该方法获取 Class 类对象
- 如果className不是类名或接口名,则forname抛出一个checked exception异常所以应该给这个方法提供一个异常处理器
4.3 .class
4.4 类的加载器:ClassLoader
ClassLoader cl = 类名.getClass().getClassLoader();
Class clazz4 = cl.loadClass("类的全类名");
5.通过反射创建类对象
- 既然通过上文我们知道了如何获取class对象,那么我们是不是就可以根据这个类对象来创建实例对象呢?当然可以
通过反射创建类对象主要有两种方式:通过 Class 对象的 newInstance() 方法、通过 Constructor 对象的 newInstance() 方法。
5.1 Class 对象的 newInstance() 方法
newlnstance方法调用默认的构造器(没有参数的构造器)初始化新创建的对象。如果这个类没有默认的构造器, 就会抛出一个异常。
5.2 Constructor 对象的 newInstance() 方法
通过 Constructor 对象创建类对象可以选择特定构造方法,而通过 Class 对象则只能使用默认的无参数构造方法。
这里getConstructor和newInstance使用时需要设置异常处理,我这里是直接在main后面throws了
6.获取类属性、方法、构造器的结构
我们已经成功获取了class类对象,并学会了如何创建对象,现在我们还可以看看对象内部的结构是什么样的,比如属性、方法和构造器。
在java.lang.reflect 包中有三个类 Field、Method 和 Constructor分别用于描述类的属性、 方法和构造器
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Constructor;
下面介绍一下 Field、Method 和 Constructor三个类的常用方法
Field类的getType 方法, 用来返回属性所属类型的 Class 对象
- Method 类有一个getReturnType方法,返回return值所属类型的Class对象
- Method 和 Constructor 类有一个共同的方法getParameterTypes,返回方法参数所属类型的Class对象
- Field、Method 和 Constructor都有一个getName 方法,返回方法名的字符串
Field、Method 和 Constructor都有一个getModifiers方法,它将返回一个整型数值,用不同的位开关描述 public 和 static 这样 的修饰符使用状况。可以利用 Modifier.toString方法将 修饰符打印出来。
for (Method m : methods) {
String modifiers = Modifier.toString(m.getModifiers());
}
该修饰符是java.lang.reflect.Modifier的静态属性。这里是用十进制表示的,源码里面是十六进制表示的。 对应表如下: PUBLIC: 1 PRIVATE: 2 PROTECTED: 4 STATIC: 8 FINAL: 16 SYNCHRONIZED: 32 VOLATILE: 64 TRANSIENT: 128 NATIVE: 256 INTERFACE: 512 ABSTRACT: 1024 STRICT: 2048
可以使用Modifiei类中的 isPublic、 isPrivate 或 isFinal 判断方法或构造器是否是 public、 private 或 final
- Class类中的 getFields、 getMethods 和 getConstructors方 法将 分 别 返 回 类 提 供 的 所有public 属性、 方法和构造器数组, 其中
包括超类的公有成员
。 - Class 类的 getDeclareFields、 getDeclareMethods 和getDeclaredConstructors方法将分别返回类中声明的全部属性、 方法和构 造器, 其中包括private和protected成员,但
不包括超类的成员
。
下面是一个代码案例,显示了如何打印一个类的全部信息的方法。
这个程序提醒用户输入一个类名,然后输出类中所有的属性、方法、构造器。里面有一些数字是我用来测试,类似-m8-
package JavaSE.Chapter5.Section57.cs573;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Scanner;
/**
* This program uses reflection to print all features of a class.
*
* @author Cay Horstmann
* @version 1.1 2004-02-21
* 利用反射分析类的能力,查看属性、构造器、方法的结构
*/
public class ReflectionTest {
public static void main(String[] args) {
String name = "JavaSE.Chapter5.Section57.cs571.Employee";
try {
//获取输入字符串的类对象
Class cl = Class.forName(name);
System.out.println(cl + "-1-");
//获取父类对象
Class supercl = cl.getSuperclass();
System.out.println(supercl + "-2-");
//获取类的访问修饰符和属于public、private、还是final
String modifiers = Modifier.toString(cl.getModifiers());
System.out.println(cl.getModifiers() + "-----cl.getModifiers");
System.out.println(modifiers + "-4-");
if (modifiers.length() > 0) System.out.print(modifiers + " ");
System.out.print("class " + name);
if (supercl != null && supercl != Object.class) System.out.print(" extends "
+ supercl.getName());
System.out.print("\n{\n");
System.out.println("------------打印构造器方法-----------");
printConstructors(cl);
System.out.println("------------打印非构造器方法-----------");
printMethods(cl);
System.out.println("------------打印属性信息-----------");
printFields(cl);
System.out.println("}");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.exit(0);
}
/**
* Prints all constructors of a class
*
* @param cl a class
*/
public static void printConstructors(Class cl) {
/*返回反映Constructor对象表示的类声明的所有Constructor对象的数组类 。
这些是public,protected,default(package)访问和私有构造函数。
返回的数组中的元素不会排序,并且不是任何特定的顺序。
如果类有一个默认构造函数,它将包含在返回的数组中。
如果类对象表示接口,原始类型,数组类或空值,则此方法返回长度为0的数组。
*/
Constructor[] constructors = cl.getDeclaredConstructors();
System.out.println(Arrays.toString(constructors) + "-c5-");
for (Constructor c : constructors) {
String name = c.getName();
System.out.print(" ");
String modifiers = Modifier.toString(c.getModifiers());
System.out.println(c.getModifiers() + "-----Counstructor.getModifiers");
//打印构造方法的访问修饰符
if (modifiers.length() > 0) System.out.print(modifiers + " ");
//打印构造方法的名字
System.out.print(name + "(");
//获取类构造器的参数类型数组
Class[] paramTypes = c.getParameterTypes();
System.out.println("-6-" + Arrays.toString(paramTypes) + "-c6-");
for (int j = 0; j < paramTypes.length; j++) {
if (j > 0) System.out.print(", ");
//打印参数类型名字
System.out.print(paramTypes[j].getName());
}
System.out.println(");");
}
}
/**
* Prints all methods of a class
*
* @param cl a class
*/
public static void printMethods(Class cl) {
Method[] methods = cl.getDeclaredMethods();
System.out.println(Arrays.toString(methods) + "-m7-");
for (Method m : methods) {
Class retType = m.getReturnType();
System.out.println(retType + "-m8-");
String name = m.getName();
System.out.print(" ");
// print modifiers, return type and method name
String modifiers = Modifier.toString(m.getModifiers());
System.out.println(m.getModifiers() + "-----Method.getModifiers");
//打印方法的访问修饰符
if (modifiers.length() > 0) System.out.print(modifiers + " ");
//打印方法返回类型和方法名
System.out.print(retType.getName() + " " + name + "(");
// print parameter types
Class[] paramTypes = m.getParameterTypes();
System.out.println("-m9-" + Arrays.toString(paramTypes) + "-m9-");
for (int j = 0; j < paramTypes.length; j++) {
if (j > 0) System.out.print(", ");
//打印方法参数类型
System.out.print(paramTypes[j].getName());
}
System.out.println(");");
}
}
/**
* Prints all fields of a class
*
* @param cl a class
*/
public static void printFields(Class cl) {
Field[] fields = cl.getDeclaredFields();
for (Field f : fields) {
Class type = f.getType();//返回属性所属类型的 Class 对象
System.out.println(type + "-f10-");
String name = f.getName();
System.out.print(" ");
String modifiers = Modifier.toString(f.getModifiers());
System.out.println(f.getModifiers() + "-----Field.getModifiers");
//打印属性的访问修饰符
if (modifiers.length() > 0) System.out.print(modifiers + " ");
//打印属性的类型名和属性名字
System.out.println(type.getName() + " " + name + ";");
}
}
}
7.获取或设置类对象的属性值
- 在编写程序时, 如果知道想要査看的属性和类型,查看指定的属性值是一件很容易的事情。而利用反射机制可以查看在编译时还不清楚的属性值。
Class t = f.getType();
//获取属性类型,f为Field对象。- 我们可以用
f.get(obj)
获取 obj 属性的当前值。f为Field对象,obj是一个Object对象。 - 可以获得就可以设置。调用
f.set(obj,value)
可以将 obj 对象的 f 属性设置成新值。f为Field对象。 - (1)如果我们要查看某个private属性的值,由于受限于java的访问机制,我们需要调用Field、Method 或 Constructor 对象的 setAccessible 方法,来设置private的值的可访问性,
x. setAccessible(true);
,x为Field、Method 或 Constructor的对象。
(2)也可以使用AccessibleObject.setAccessible(x, true);
来设置private值的可访问性,它是 Field、 Method 和 Constructor 类的公共超类,x为Field、Method 或 Constructor 对象的数组引用。
接下来的一个例子将使用上面所说的方法,来查看访问对象的属性值
如下一个可供任意类使用的通用 toString方法。 其中使用 getDeclaredFileds 获得所有的数据属性, 然后使用 setAccessible 将所有的属性设置为可访问的。 对于每个属性,获得了名字和值。递归调用 toString方法,将每个值转换成字符串。(这个例子是java核心技术卷一里面的,这个例子看懂我感觉还是需要花时间的,有的地方我还没看懂……)
package JavaSE.Chapter5.Section57.cs574;
import JavaSE.Chapter5.Section57.cs571.Employee;
import java.lang.reflect.Field;
/**
* 在运行时使用反射分析对象,查看对象当前的各个属性值
*/
public class ObjectAnalyzerTest {
public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException, InstantiationException {
Employee s = new Employee();
System.out.println(new ObjectAnalyzer().toString(s));
System.out.println("--------------------------------");
String[] str = {"str11", "str22", "str33"};
System.out.println(new ObjectAnalyzer().toString(str));
System.out.println("--------------------------------");
Class em = s.getClass();
Object obj = em.newInstance();
Field f = em.getDeclaredField("name");
f.setAccessible(true);
Object val = f.get(obj);//获取属性的值
System.out.println(val);
f.set(obj, "BitHachi");
Employee em2 = (Employee) obj;
System.out.println(em2.getName());
}
}
package JavaSE.Chapter5.Section57.cs574;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
public class ObjectAnalyzer {
private ArrayList<Object> visited = new ArrayList<>();
public String toString(Object obj) {
if (obj == null) return "null";
if (visited.contains(obj)) return "...";
visited.add(obj);
Class cl = obj.getClass();
//如果对象是一个字符串对象,则直接打印其值
if (cl == String.class) return (String) obj;
//判断类对象是否是一个数组
if (cl.isArray()) {
//getComponentType返回对象数组的的Class类对象。 如果此类不表示数组类,则此方法返回null。
String r = cl.getComponentType() + "[]{";
System.out.println(r + "-1-");
//返回指定数组对象的长度Array.getLength(obj)
for (int i = 0; i < Array.getLength(obj); i++) {
if (i > 0) r += ",";
//返回指定数组对象中的索引组件的值。
Object val = Array.get(obj, i);
System.out.println(val + " -val -2-");
/*确定指定类对象表示一个基本类型。
有九个预定类对象代表八个原始类型和void。
这些是由Java虚拟机创建,并且具有相同的名称为他们所代表的基本类型,
即boolean , byte , char , short , int , long , float和double 。
isPrimitive返回一个boolean值*/
if (cl.getComponentType().isPrimitive()) r += "@" + val + "@";
else r += toString(val);
System.out.println(r + "-3-");
}
return r + "}";
}
String r = cl.getName();
System.out.println(r + "-4-");
// 检查此类和所有超类的字段
do {
r += "[";
//获取类的所有属性得一个数组
Field[] fields = cl.getDeclaredFields();
/*setAccessible()为反射对象设置可访问标志。 true 表明屏蔽 Java语言的访问检查,
使得对象的 private私有属性也可以被査询和设置。 */
AccessibleObject.setAccessible(fields, true);
//获取所有属性的名字和值
for (Field f : fields) {
if (!Modifier.isStatic(f.getModifiers())) {
if (!r.endsWith("[")) r += ",";
r += f.getName() + "=";
System.out.println(r + "-5-");
try {
Class t = f.getType();//获取属性类型
Object val = f.get(obj);//获取属性的值
System.out.println(val + " -val -6-");
if (t.isPrimitive()) {
r += val;
System.out.println(r + "-7-");
} else {
r += toString(val);
System.out.println(r + "-7-");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
r += "]";
cl = cl.getSuperclass();
}
while (cl != null);
return r;
}
}
8.利用反射调用任意方法
通过反射,调用类中的方法,通过Method类完成。步骤:
- 通过Class类的getMethod(String name,Class…parameterTypes)方法取得一个Method对象,并设置此方法操作时所需要的参数类型。
- 之后使用Object invoke(Object obj, Object[] args)进行调用,并向方法中传递要设置的obj对象的参数信息。
Object invoke(Object obj, Object … args)
说明:
- Object 对应原方法的返回值,若原方法无返回值,此时返回null
- 若原方法若为静态方法,此时形参Object obj可为null
- 若原方法形参列表为空,则Object[] args为null
- 若原方法声明为private,则需要在调用此invoke()方法前,显式调用方法对象的setAccessible(true)方法,将可访问private的方法。
下面是一个代码示例:
package JavaSE.Chapter5.Section57.cs576;
import java.lang.reflect.Method;
public class MethodTableTest {
public static void main(String[] args) throws Exception {
// 获取相应方法的Method对象,通过类对象来获取方法对象
Method square = MethodTableTest.class.getMethod("square", double.class);
Method sqrt = Math.class.getMethod("sqrt", double.class);
// 打印x和y值表
printTable(1, 10, 10, square);
printTable(1, 10, 10, sqrt);
}
public static double square(double x) {
return x * x;
}
/**
* Prints a table with x- and y-values for a method
*
* @param from the lower bound for the x-values 上限
* @param to the upper bound for the x-values 下限
* @param n the number of rows in the table 个数
* @param f a method with a double parameter and double return value
*/
public static void printTable(double from, double to, int n, Method f) {
// print out the method as table header
System.out.println("方法: " + f);
double dx = (to - from) / (n - 1);//按上下限设置每次加的值
for (double x = from; x <= to; x += dx) {
try {
double y = (Double) f.invoke(null, x);//调用这个方法对象进行计算
System.out.printf("%10.4f | %10.4f%n", x, y);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
9. 使用反射编写泛型数组代码,复制数组
我们可以利用反射来扩充一个数组的容量
package JavaSE.Chapter5.Section57.cs575;
import java.lang.reflect.Array;
import java.util.Arrays;
public class CopyOfTest {
public static void main(String[] args) {
int[] a = {1, 2, 3};
a = (int[]) goodCopyOf(a, 10);
System.out.println(Arrays.toString(a));
System.out.println("--------------------");
String[] b = {"Tom", "Dick", "Harry"};
b = (String[]) goodCopyOf(b, 10);
System.out.println(Arrays.toString(b));
}
public static Object goodCopyOf(Object a, int newLength) {
//第一步:获取a数组的类对象
Class cl = a.getClass();
System.out.println(cl + "---1");
//第二步:判断a数组的类对象是否是一个数组
if (!cl.isArray()) return null;
//第三步:使用Class类的getComponentType方法确定数组对应的类型
Class componentType = cl.getComponentType();
System.out.println(componentType + "---2");
//获取数组的长度
int length = Array.getLength(a);
System.out.println(length + "---3");
//构造新数组newInstance方法
//返回一个具有给定类型、给定长度的新数组
Object newArray = Array.newInstance(componentType, newLength);
/*arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
将指定源数组中的数组从指定位置复制到目标数组的指定位置。*/
System.arraycopy(a, 0, newArray, 0, Math.min(length, newLength));
return newArray;
}
}
10. 可以用==比较两个Class对象
虚拟机为每个类型管理一个 Class 对象。因此, 可以利用== 运算符实现两个类对象比较的操作。 例如:
11. 类的加载与ClassLoader理解
11.1 了解类的加载过程
当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化。加载
:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口(即引用地址)。所有需要访问和使用类数据只能通过这个Class对象。这个加载的过程需要类加载器参与。链接
:将Java类的二进制代码合并到JVM的运行状态之中的过程。
- 验证:确保加载的类信息符合JVM规范,例如:以cafe开头,没有安全方面的问题
- 准备:正式为类变量(static)分配内存并
设置类变量默认初始值的阶段
,这些内存都将在方法区中进行分配。 - 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
初始化
:
- 执行
类构造器<clinit>()方法
的过程。类构造器<clinit>()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的
。(类构造器是构造类信息的,不是构造该类对象的构造器)。 - 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
- 虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步。
举个例子:
public class ClassLoadingTest {
public static void main(String[] args) {
System.out.println(A.m);//100
}
}
class A {
static {
m = 300;
}
static int m = 100;
}
//第一步:将A.class读入内存,并创建一个Class对象,此过程有ClassLoader完成
//第二步:链接结束后m=0
//第三步:初始化后,m的值由<clinit>()方法执行决定
// 这个A的类构造器<clinit>()方法由类变量的赋值和静态代码块中的语句按照**顺序**合并产生,类似于
// <clinit>(){
// m = 300;
// m = 100;
// }
11.2 什么时候会发生类初始化 ?
类的主动引用 ( 一定会发生类的初始化 )
- 当虚拟机启动,先初始化main方法所在的类
- new一个类的对象
- 调用类的静态成员(除了final常量)和静态方法
- 使用java.lang.reflect包的方法对类进行反射调用
- 当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类
类的被动引用 ( 不会发生类的初始化 )
- 当访问一个静态域时,只有真正声明这个域的类才会被初始化
- 当通过子类引用父类的静态变量,不会导致子类初始化
- 通过数组定义类引用,不会触发此类的初始化
引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)
public class ClassLoadingTest {
public static void main(String[] args) {
// 主动引用:一定会导致A和Father的初始化
// A a = new A();
// System.out.println(A.m);
// Class.forName("xxxx");
// 被动引用
A[] array = new A[5];//不会导致A和Father的初始化
// System.out.println(A.b);//只会初始化Father
// System.out.println(A.M);//不会导致A和Father的初始化
}
static {
System.out.println("main所在的类");
}
}
class Father {
static int b = 2;
static {
System.out.println("父类被加载");
}
}
class A extends Father {
static {
System.out.println("子类被加载");
m = 300;
}
static int m = 100;
static final int M = 1;
}
11.3 类加载器的作用
类加载器的作用:
类加载的作用
:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构
,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。类缓存
:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象。
JVM 规范定义了如下类型的类的加载器。
public class ClassLoaderTest {
public static void main(String[] args) {
//对于自定义类,使用系统类加载器进行加载
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
System.out.println(classLoader);
//调用系统类加载器的getParent():获取扩展类加载器
ClassLoader classLoader1 = classLoader.getParent();
System.out.println(classLoader1);
//调用扩展类加载器的getParent():无法获取引导类加载器
//引导类加载器主要负责加载java的核心类库,无法加载自定义类的。
ClassLoader classLoader2 = classLoader1.getParent();
System.out.println(classLoader2);
ClassLoader classLoader3 = String.class.getClassLoader();
System.out.println(classLoader3);
}
}
运行结果:
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@1d44bcfa
null
null
12.收获与感受
- 反射里面用到了多态的特性,这一点真的很重要,特别是Object与其它对象之间的互转,不懂得话,很容易懵圈
- 4-8的应用是反射里面的核心点,当然还有很多的API没办法一次性讲完,其实只要懂了核心的部分,其它的API就比较好懂了
关于反射的内容和应用还有很多,以后在工作中遇到了相关内容再进行补充叭,现在作为初学者,先总结整理这么多叭。
参考
https://www.cnblogs.com/chanshuyi/p/head_first_of_reflection.html#反射常用api
- https://www.iteye.com/blog/762626559-qq-com-395402
- https://blog.csdn.net/qq_40434646/article/details/82351488
- https://blog.csdn.net/kjfcpua/article/details/8496911
- http://yuncode.net/code/c_56768be18995515
- https://www.cnblogs.com/fengmao/p/8609855.html
- https://www.cnblogs.com/daimajun/p/6545533.html