4.类的加载过程
类的加载过程
当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过以下三个步骤来对类进行初始化:
类的加载详细过程
类的加载: 将类的字节码文件加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象(此过程由类加载器完成)(此时在内存结构中:方法区中存放类的字节码的二进制数据,堆中存放该Class对象)
类的链接: 将Java类的二进制数据合并到JVM的运行状态之中的过程。
验证:确保加载的类信息符合JVM规范,没有安全方面的问题。
准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。
解析:JVM常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
类的初始化: JVM负责对类进行初始化
执行类构造器的()方法的过程。这个方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的(类构造器是构造类信息的,不是构造该类对象的构造器)
当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
JVM会保证一个类的()方法在多线程环境中被正确加锁和同步。
什么时候会发生类的初始化
1、类的主动引用(一定会发生类的初始化)
当虚拟机启动,先初始化main方法所在的类。
new一个类的对象。
调用类的静态成员(除了final常量)和静态方法。
使用java.lang.reflect包的方法对类进行反射调用。
当初始化一个类,如果其父类没有被初始化,则先会初始化他的父类。
2、类的被动引用(不会发生类的初始化)
当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导致子类初始化。
通过数组定义类引用,不会触发此类的初始化。
引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)。
5.类加载器
类加载器的作用
将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。
类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象。
类加载器分类
java的核心库是rt.jar包
JVM规范定义了以下的类加载器(类加载器的作用就是把类装载进内存的):
(Bootstrap Classloader)引导类加载器:用C++编写的,是JVM自带的类加载器,负责Java平台核心库,用来加载核心类库。该加载器无法直接获取。
(Extension Classloader)扩展类加载器:负责jre/lib/ext目录下的jar包或-D java.ext.dirs指定目录下的jar包装入工作库。
(System Classloader)系统类加载器:负责java -classpath或-D java.class.path所指的目录下的类与jar包装入工作,是最常用的加载器。
查看类加载器
public class Test07 { public static void main(String[] args) throws ClassNotFoundException { // 获取系统类的加载器 ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); System.out.println(systemClassLoader);// 打印:sun.misc.Launcher$AppClassLoader@18b4aac2 // 获取系统类加载器的父类加载器-->扩展类加载器 ClassLoader parent = systemClassLoader.getParent(); System.out.println(parent);// 打印:sun.misc.Launcher$ExtClassLoader@1b6d3586 // 获取扩展类加载器的父类加载器-->根加载器(C/C++) 根加载器无法直接获取 ClassLoader parent1 = parent.getParent(); System.out.println(parent1);// 打印:null // 测试当前类是哪个加载器加载的 ClassLoader classLoader = Class.forName("com.sywl.reflection.Test07").getClassLoader(); System.out.println(classLoader);// 打印:sun.misc.Launcher$AppClassLoader@18b4aac2 // 测试jdk内部类是哪个加载器加载的。(由根加载器加载的,所以打印不出来,为null) ClassLoader classLoader1 = Class.forName("java.lang.Object").getClassLoader(); System.out.println(classLoader1);// 打印:null // 如何获得系统类加载器可以加载的路径 System.out.println(System.getProperty("java.class.path")); // 打印: /* C:\Program Files\Java\jdk1.8.0_241\jre\lib\charsets.jar; C:\Program Files\Java\jdk1.8.0_241\jre\lib\deploy.jar; C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\access-bridge-64.jar; C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\cldrdata.jar; C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\dnsns.jar; C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\jaccess.jar; C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\jfxrt.jar; C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\localedata.jar; C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\nashorn.jar; C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\sunec.jar; C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\sunjce_provider.jar; C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\sunmscapi.jar; C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\sunpkcs11.jar; C:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\zipfs.jar; C:\Program Files\Java\jdk1.8.0_241\jre\lib\javaws.jar; C:\Program Files\Java\jdk1.8.0_241\jre\lib\jce.jar; C:\Program Files\Java\jdk1.8.0_241\jre\lib\jfr.jar; C:\Program Files\Java\jdk1.8.0_241\jre\lib\jfxswt.jar; C:\Program Files\Java\jdk1.8.0_241\jre\lib\jsse.jar; C:\Program Files\Java\jdk1.8.0_241\jre\lib\management-agent.jar; C:\Program Files\Java\jdk1.8.0_241\jre\lib\plugin.jar; C:\Program Files\Java\jdk1.8.0_241\jre\lib\resources.jar; C:\Program Files\Java\jdk1.8.0_241\jre\lib\rt.jar; D:\Stu\dev\annotationAndReflect\target\classes; C:\Program Files\JetBrains\IntelliJ IDEA 2020.1.1\lib\idea_rt.jar * */ // 双亲委派机制(了解) } }
6.获取运行类的完整结构
通过反射可以获取运行时类的完整结构,包含:
- Field、 Method、 Constructor.、 Superclass、 Interface、 Annotation
- 实现的全部接口
- 所继承的父类
- 全部的构造器
- 全部的方法
- 全部的Feld
- 注解
- ……
package com.wang.Annotation.Demo03; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; public class TestClassInfo { public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException { Class c1 = Class.forName("github.Annotation.Demo03.User"); User user = new User(); c1 = user.getClass(); // 获得类的名字 System.out.println(c1.getName());// 获得包名 + 类名 System.out.println(c1.getSimpleName());// 获得类名 System.out.println("======================="); // 获得类的属性 Field[] fields = c1.getFields();// 只能找到public属性 for (Field field : fields) { System.out.println("getFields:" + field); } fields = c1.getDeclaredFields();// 找到全部的属性 for (Field field : fields) { System.out.println("getDeclaredFields:" + field); } // 获得指定属性的值 Field name = c1.getDeclaredField("name"); System.out.println(name); System.out.println("======================="); // 获得类的方法 Method[] methods = c1.getMethods(); // 获得本类及父类的全部public方法 for (Method method : methods) { System.out.println("getMethods:" + method); } methods = c1.getDeclaredMethods(); // 获得本类的所有方法 for (Method method : methods) { System.out.println("getDeclaredMethods:" + method); } System.out.println("======================="); // 获得指定的方法 // 重载 Method getName = c1.getMethod("getName", null); Method setName = c1.getMethod("setName", String.class); System.out.println(getName); System.out.println(setName); // 获得类的构造器 System.out.println("======================="); Constructor[] constructors = c1.getConstructors(); for (Constructor constructor : constructors) { System.out.println("getConstructors:" + constructor); } constructors = c1.getDeclaredConstructors(); for (Constructor constructor : constructors) { System.out.println("getDeclaredConstructors:" + constructor); } // 获得指定的构造器 Constructor declaredConstructor = c1.getDeclaredConstructor(String.class, int.class, int.class); System.out.println("指定构造器" + declaredConstructor); } }
注意:
在实际的操作中,取得类的信息的操作代码,并不会经常开发。
一定要熟悉 java. lang .reflect包的作用,反射机制。
如何取得属性、方法、构造器的名称,修饰符等。
7.调用运行类的指定结构
有Class对象,能做什么
创建类的对象:调用 Class对象的 newInstance()方法,需满足:
类必须有一个无参数的构造器。
类的构造器的访问权限需要足够。
没有无参的构造器情况下,只要在操作的时候明确的调用类中的构造器,并将参数传递进去之后,也可以进行实例化操作,步骤如下:
(1)通过class类的 getDeclaredConstructor( Class…,parameterTypes)取得本类的指定形参类型的构造器;
(2)向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数;
(3)通过 Constructo实例化对象。
调用类中的方法
通过反射,调用类中的方法,通过 Method类完成。
①通过Cas类的 getMethod( String name, Class… parameterTypes)方法取得一个Method对象,并设置此方法操作时所需要的参数类型。
②之后使用 Object invoke( Object obj,Object[] args)进行调用,并向方法中传递要设置的ob对象的参数信息。
代码示例如下:
package com.wang.Annotation.Demo03; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * 动态的创建对象,通过反射 */ public class TestDynamicCreateObject { public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException { // 获得Class对象 Class c1 = Class.forName("github.Annotation.Demo03.User"); // 构造一个对象 User user = (User) c1.newInstance(); // 本质上调用了类的无参构造器 System.out.println(user); // 通过构造器创建对象 Constructor constructor = c1.getDeclaredConstructor(String.class, int.class, int.class); User user1 = (User) constructor.newInstance("光荣时代",001,17); System.out.println(user1); // 通过反射调用普通方法 User user2 = (User) c1.newInstance(); // 通过反射获取一个方法 Method setName = c1.getDeclaredMethod("setName", String.class); // invoke:激活 // (对象,"方法值") setName.invoke(user2, "some"); System.out.println(user2.getName()); // 通过反射操作属性 User user3 = (User) c1.newInstance(); Field name = c1.getDeclaredField("name"); // 不能直接操作私有属性,我们需要关闭程序的安全检测,属性或方法的setAccessible(true) // 设置安全检测 name.setAccessible(true); name.set(user3, "some2"); System.out.println(user3.getName()); } }
调用指定的方法
Object invoke(object obj, Object. args);
Object对应原方法的返回值,若原方法无返回值,此时返回null;
若原方法若为静态方法,此时形参 Object obj可为null;
若原方法形参列表为空,则 Object[] args为null; 若原方法声明为 private,则需要在调用此invoke()方法前,显式调用方法对象的setAccessible(true)方法,将可访问 private的方法。
setAccessible
Method和 Field、 Constructor对象都有 setAccessible()方法。
setAccessible作用是启动和禁用访问安全检查的开关。
参数值为true则指示反射的对象在使用时应该取消Java语言访问检査。提高反射的效率。如果代码中必须用反射,而该句代码需要频繁的被调用,那么请设置为true; 使得原本无法访问的私有成员也可以访问;
参数值为false则指示反射的对象应该实施Java语言访问检查。
8.反射获取泛型信息
Java采用泛型擦除的机制来引入泛型,Java中的泛型仅仅是给编译器jvac使用的,确保数据的安全性和免去强制类型转换问题,但是,一旦编译完成,所有和泛型有关的类型全部擦除。
为了通过反射操作这些类型,Java新增了 ParameterizedType, GenericArray Type, Type Variable和 WildcardType几种类型来代表不能被归一到clas类中的类型但是又和原始类型齐名的类型。
ParameterizedType:表示一种参数化类型,比如 Collection< String>。
GenericArray Type:表示一种元素类型是参数化类型或者类型变量的数组类型。
Type Variable:是各种类型变量的公共父接口。
WildcardType:代表一种通配符类型表达式。
package com.wang.Annotation.Demo03; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.List; import java.util.Map; /** - 通过反射获取泛型 */ public class Test01 { public void test02(Map<String,User> map, List<User> list){ System.out.println("test02"); } public Map<String,User> test03(){ System.out.println("Test03"); return null; } public static void main(String[] args) throws NoSuchMethodException { Method method = Test01.class.getMethod("test02", Map.class, List.class); Type[] genericParameterTypes = method.getGenericParameterTypes(); for (Type genericParameterType : genericParameterTypes){ System.out.println("#" + genericParameterType); if(genericParameterType instanceof ParameterizedType){ Type[] typeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments(); for (Type typeArgument : typeArguments){ System.out.println(typeArgument); } } } method = Test01.class.getMethod("test03", null); Type returnType = method.getGenericReturnType(); if(returnType instanceof ParameterizedType){ Type[] typeArguments = ((ParameterizedType) returnType).getActualTypeArguments(); for (Type typeArgument : typeArguments){ System.out.println(typeArgument); } } } }
9.反射获取注解信息
- getAnnotations
- getAnnotation
package com.wang.Annotation.Demo03; import java.lang.annotation.*; import java.lang.reflect.Field; /** * 练习反射操作注解 */ public class TestORM { public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException { Class c1 = Class.forName("github.Annotation.Demo03.Student2"); //通过反射获取注解 Annotation[] annotations = c1.getAnnotations(); for (Annotation annotation : annotations) { System.out.println(annotation); } //获得注解value TableDoris tableDoris = (TableDoris) c1.getAnnotation(TableDoris.class); String value = tableDoris.value(); System.out.println(value); //获得类指定的注解 Field name = c1.getDeclaredField("name"); FiledDoris annotation = name.getAnnotation(FiledDoris.class); System.out.println(annotation.columnName()); System.out.println(annotation.type()); System.out.println(annotation.length()); } } @TableDoris("db_student") class Student2 { @FiledDoris(columnName = "db_id", type = "int", length = 10) private int id; @FiledDoris(columnName = "db_age", type = "int", length = 3) private int age; @FiledDoris(columnName = "db_name", type = "varchar", length = 200) private String name; public Student2() { } public Student2(int id, int age, String name) { this.id = id; this.age = age; this.name = name; } @Override public String toString() { return "Student2{" + "id=" + id + ", age=" + age + ", name='" + name + '\'' + '}'; } public int getId() { return id; } public void setId(int id) { this.id = id; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } } // 类名注解 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @interface TableDoris { String value(); } // 属性注解 @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @interface FiledDoris { String columnName(); String type(); int length(); }
后记
Java全栈学习路线可参考:【Java全栈学习路线】最全的Java学习路线及知识清单,Java自学方向指引,内含最全Java全栈学习技术清单~