文章目录
什么是注解?
内置注解
- @Override
- @Deprecated
- @SuppressWarnings()
- …
原注解
- 元注解的作用就是负责解释其它注解,java定义了4个标准的meta-annotation类型,它们被用来提供对其它annotation类型说明
- 这些类型和他们所在的类在
java.lang.annotation
包可以找到@Target
: 用于描述注解的使用范围(他可以使用在什么地方, 方法/字段/类/包…)@Retention
: 表示需要在什么级别保存该信息,用于描述注解的声明周期 SOURCE < CLASS < RUNTIME@Documented
:说明该注解将被包含在javadoc
中@Inherited
:说明子类可以继承父类中的该注解
反射
java反射机制概述
- java反射机制的核心就是在运行程序时动态加载类并获取类的详细信息,而从操作类或对象的属性和方法.本质
JVM
得到class
对象后,在通过class
对象进行反编译,从而获得对象的各种信息 - 通过反射可以在运行时动态的创建对象并调用其属性,不需要在提前在编译器知道运行的对象是谁
java是静态语言,但是反射机制体现了它的动态性.也可以叫做准动态语言
Java Reflection
Reflection
反射是java被视为动态语言的关机啦,反射允许程序在执行器借助于Reflection API
取得任何类的内部信息,并能直接操作任意对象的内部属性以及方法
Class c=Class.forName("com.jasper.thread.Car");
加载完类后,在堆内存的方法区就产生了一个
Class
类型的对象(一个类只有一个class对象),这个对象包含了完整的类的内部信息,我们可以通过这个对象看到类的结构,这个对象就像一个镜子,透过这个镜子可以看到这个类的结构。所以,我们称之为:反射
反射的优缺点
- 优点: 在运行时获取类的各种内容,进行反编译,能够有让我们很方便的创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的连接,更加容易面向对象
- 缺点:
- 反射会消耗一定的系统资源,因此如果不需要动态的创建一个对象,那么就不需要使用反射
- 反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题.
理解class类并获取class实例
Class类
对象照镜子可以得到的信息:某个类的属性,方法,构造器,某个类型到底实现了哪些接口,对于每个类而言,JRE都为其保留一个不变的Class类型的对象,一个Class对象包含了特定某个结构(class/interface/enum/annotation)等有关信息
- Class本身也是一个类
- Class对象只能有系统建立对象
- 一个加载的类在JVM中只会有一个Class实例
- 每个类的实例都会记得自己是由哪个Class实例所生成的
- 通过Class可以完整的得到一个类中的所有被加载的结构
- Class类是Reflection的根源,针对任何你想动态加载.运行的类,唯有先获得相应的Class对象
获取Class
类的实例
- 若已知具体的类,通过类的class属性获取,该方法是最为可靠,程序性能最高
Class<Car> carClass = Car.class;
- 已知某个类的实例,调用该实例的
getClass()
方法获取Class对象
Car car=new Car();
Class<? extends Car> carClass = car.getClass();
- 已知一个类的全类名,且该类在类路径下,可通过
Class
类的静态方法forName()
获取,可能会抛出ClassNotFoundException
Class c=Class.forName("com.jasper.thread.Car");
- 内置基本数据类型可以直接使用
类名.Type
Class<Integer> type = Integer.TYPE;
- 通过类的加载器
ClassLoader
(有各种各样的加载器)
ClassLoader classLoader = Test.class.getClassLoader();
Class<?> aClass = classLoader.loadClass("com.jasper.thread.Car");
- 通过子类获得父类类型
getSuperClass();
Calss c=car.getSuperClass();
类的加载与ClassLoader
类加载内存分析
当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对类进行初始化
- 类的加载 Load: 将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数结构,然后生成一个代表这个类的Class对象
- 类的连接 Link: 将Java类的二进制代码合并到JVM的运行状态之中的过程
- 验证: 确保加载的类信息符合JVM规范,没有安全方面的问题
- 准备: 正式为类变量(static)分配内存并设置默认初始值的阶段,这些内存都将在方法区中进行分配
- 解析: 虚拟机常量池内的符号引用(常量名)替换为直接应用(地址)过程
- 类的初始化 Initialize
- 执行类构造器
<clinit>()
方法的过程,类构造器<clinit>()
方法是由编译期自动收集类中所有的类变量的复制动作和静态代码块中的语句产生的,(类构造器是构造类信息的m,不是构造该对象的) - 当初始化一个类的时候,如果发现其父类还没有进行初始化,这需要先触发其父类的初始化
- 虚拟机会保证一个类的
<clinit>()
方法在多线程环境中被正确加锁和同步
- 执行类构造器
什么时候会发生类的初始化
- 类的主动引用(一定会发生类的初始化)
- 当虚拟机启动时,先初始化
main
方法所在的类 new
一个类的对象- 调用类的静态成员(除了
final
常量)和静态方法 - 使用
java.lang.reflect
包的方法对类进行反射调用 - 当初始化一个类,如果其父类没有初始化,则会先初始化它的父类
- 当虚拟机启动时,先初始化
- 类的被动引用(不会发生初始化)
- 当访问一个静态域时,只有真正声明这个域的类才会被初始化,如:当通过子类引用父类的静态变量,不会导致父类的初始化
- 通过数组定义引用类,不会触发类的初始化
- 引用常量不会触发此类的初始化(常量在链接截断就存入调用类的常量池中了,)
当类中的基本类型或
string
类型在被static final
修饰时,在只获取其属性时,不需要初始化它的类,而当被static final
修饰的属性为引用属性时,在获取该属性石这个类还是会被初始化的
类加载器
-
类加载器的作用: 将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的
java.lang.ClassLoader
对象,作为方法区中类数据访问入口 -
类缓存: 标准的
javaSE
类加载器可以按要求查找类,但某一个类被加载到类加载器中,它将维持加载(缓存)一段时间,不过JVM垃圾回收机制可以回收这些Class对象 -
类加载器的作用就是用来把类class装载进内存的,JVM规范定义了如下类型的加载器(java1.8之前)
- 引导类加载器,
Bootstrap Classloader
用C++编写的,是JVM自带的类加载器,负责Java平台核心库,用来装载核心类库,该加载器无法直接获取 - 扩展类加载器
Extension Classloader/ExtClassloader
,负责jre/lib/ext目录下的jar包或者-D java.ext,dirs
指定目录下的jar包装入工作库 - 系统类加载器
System Classloader/AppClassloader
负责java -classpath
或-D java.class.path
所指向目录下的类与jar包封装入工作,是最常用的加载器 - 自定义类加载器.系统的ClassLoader只会加载指定目录下的class文件,如果你想加载自己的class文件,那么就可以自定义一个ClassLoader.而且我们可以根据自己的需求,对class文件进行加密和解密,新建一个类继承自java.lang.ClassLoader,重写它的findClass方法
- 引导类加载器,
其中扩展类加载器和应用类加载器都是URLClassloader
的实例
//获取系统内加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@14dad5dc
//获取获取系统内加载器父类-扩展类加载器
ClassLoader parent = systemClassLoader.getParent();
System.out.println(parent);//sun.misc.Launcher$ExtClassLoader@1b6d3586
//获取扩展类加载器的父类加载器
ClassLoader parentParent = parent.getParent();
System.out.println(parentParent);//null,引导类加载器无法直接加载
//测试当前类的加载器
ClassLoader classLoader = Class.forName("com.jasper.reflect.Test02").getClassLoader();
System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@14dad5dc
classLoader=Class.forName("java.lang.Object").getClassLoader();
System.out.println(classLoader);//null,引导类加载器无法直接加载
//获取当前系统加载器加载类的位置
System.out.println(System.getProperty("java.class.path"));
/*
C:\Program Files\Java\jdk1.8.0_66\jre\lib\charsets.jar;
C:\Program Files\Java\jdk1.8.0_66\jre\lib\deploy.jar;
C:\Program Files\Java\jdk1.8.0_66\jre\lib\ext\access-bridge-64.jar;
C:\Program Files\Java\jdk1.8.0_66\jre\lib\ext\cldrdata.jar;
C:\Program Files\Java\jdk1.8.0_66\jre\lib\ext\dnsns.jar;
C:\Program Files\Java\jdk1.8.0_66\jre\lib\ext\jaccess.jar;
C:\Program Files\Java\jdk1.8.0_66\jre\lib\ext\jfxrt.jar;
C:\Program Files\Java\jdk1.8.0_66\jre\lib\ext\localedata.jar;
C:\Program Files\Java\jdk1.8.0_66\jre\lib\ext\nashorn.jar;
C:\Program Files\Java\jdk1.8.0_66\jre\lib\ext\sunec.jar;
C:\Program Files\Java\jdk1.8.0_66\jre\lib\ext\sunjce_provider.jar;
C:\Program Files\Java\jdk1.8.0_66\jre\lib\ext\sunmscapi.jar;
C:\Program Files\Java\jdk1.8.0_66\jre\lib\ext\sunpkcs11.jar;
C:\Program Files\Java\jdk1.8.0_66\jre\lib\ext\zipfs.jar;
C:\Program Files\Java\jdk1.8.0_66\jre\lib\javaws.jar;
C:\Program Files\Java\jdk1.8.0_66\jre\lib\jce.jar;
C:\Program Files\Java\jdk1.8.0_66\jre\lib\jfr.jar;
C:\Program Files\Java\jdk1.8.0_66\jre\lib\jfxswt.jar;
C:\Program Files\Java\jdk1.8.0_66\jre\lib\jsse.jar;
C:\Program Files\Java\jdk1.8.0_66\jre\lib\management- agent.jar;
C:\Program Files\Java\jdk1.8.0_66\jre\lib\plugin.jar;
C:\Program Files\Java\jdk1.8.0_66\jre\lib\resources.jar;
C:\Program Files\Java\jdk1.8.0_66\jre\lib\rt.jar;
E:\git\loadClass\out\production\loadClass;
C:\Program Files\JetBrains\IntelliJ IDEA 2020.3.2\lib\idea_rt.jar
*/
在java9以后,类加载器有了变化
java9
仍然保留了3层类加载器结构,不过为了支持模块系统,做了一些调整,扩展机制被移除,扩展类加载器由于向后兼容的原因被移除,不过被重新命名为平台类加载器platfrom class loader
.可以通过Classloader的新方法ClassLoader.getPlatformClassLoader();
获得,当然ClassLoader.getSystemClassLoader().getParent();
也能.它的双亲委派机制是这样的
- 检查当前类是否被JVM加载
- 没有被加载,就去寻找能够加载此包的对应已经加载的模块
- 找到该模块,拿到该模块的构造器
- 如果类的所属模块是当前模块,直接加载此类
- 如果这个木块不是当前模块,调用3步骤中的构造器进行加载
- 如果没有找到这个包对应的模块加载器,就使用父类的加载器
- 如果父类加载器没有成功加载,并且有与这个类装入器关联的类路径,就尝试用当前类加载器加载
- 如果当前类已经被加载了,就直接解析这个类
JAVA9可以访问到引导类加载器了,getName()
方法来获取
创建运行时对象
通过反射获取运行时类的完整结构
Class<?> c1 = Class.forName("com.jasper.annotation.User");
//类名
System.out.println(c1.getName());//com.jasper.annotation.User 获得包名+类名
System.out.println(c1.getSimpleName());//User 获得类名
//属性
Arrays.stream(c1.getFields()).forEach(System.out::println);//啥都没打印,只能找到public属性
Arrays.stream(c1.getDeclaredFields()).forEach(System.out::println);//能获得所有属性
/*
*java.lang.String com.jasper.annotation.User.name
*int com.jasper.annotation.User.age
*/
动态创建对象
Class<?> c1 = Class.forName("com.jasper.annotation.User");
//构造一个对象,直接用无参构造
User user = (User) c1.newInstance();
System.out.println(user.toString());//User{name='null', age=0}
//构建对象,先获取它的有参数的构造器
Constructor<?> declaredConstructor = c1.getDeclaredConstructor(String.class, int.class);
User user2 =(User) declaredConstructor.newInstance("zhangsan", 19);
System.out.println(user2);//User{name='zhangsan', age=19}
调用指定的方法 invoke
- 调用指定的方法时,若方法声明为
private
则需要关闭安全检查的开关setAccessible(true)
代表关闭安全检测