零.类的加载时机
一.类加载器
1. 概述:
在jvm中, 负责将本地上的class 文件加载到内存的对象
2. 分类:
- BootstrapClassLoader 根类加载器-- > C语言写的, 我们获取不到
也被称为引导类加载器,负责Java核心类的加载
比如System, String等。
jre/ lib/ rt. jar下的类都是核心类
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -
- ExtClassLoader 扩展类加载器
负责JRE的扩展目录中jar包的加载。
在JDK中JRE的lib目录下ext目录
- AppClassLoader 系统类加载器
负责在JVM启动时加载来自java命令的class 文件,以及classpath环境变量所指定的jar包( 第三方jar包) 和类路径。或者自定义的类
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
AppClassLoader的父加载器是ExtClassLoader
ExtClassLoader的父加载器是BootstrapClassLoader
但是他们不是子父类继承关系, 他们有一个共同的爹-- > ClassLoader
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
3. 获取加载器对象
类名. class . getClassLoader
4. 双亲委派( 全盘负责委托机制) 谁用谁加载
a. Person中有一个String
Person本身是AppClassLoader加载
String是BootstrapClassLoader
b. 加载顺序:
Person本身是AppClassLoader加载, String本来按理来说是AppClassLoader
但是AppClassLoader加载String的时候, 会去问一问ExtClassLoader, 嗨, ext, 你加载吗?
ext说: 偶no, 我不负责加载核心类, 我负责的是扩展类, 所以你别急, 我给你找人( BootstrapClassLoader)
ext说: boot, 你加载String吗?
boot: 哎, 正好我加载核心类, 行吧, 我加载吧
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -
假如: ext和boot都不加载, app才会自己加载
class Test {
new Person ( )
}
a. AppClassLoader负责加载Test, 然后按道理来讲Person也是由AppClassLoader加载到内存
但是App先不加载, 先去找ExtClassLoader, Ext不负责加载自定义类, Ext就去找Boot
但是Boot只负责加载核心类, 所以Boot也不加载
b. 最后App看两个双亲不加载, 自己就加载了Person
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -
c. 类加载器的cache ( 缓存) 机制:如果cache中保存了这个类就直接返回它,如果没有才加载这个类,然后存入cache中,下一次如果有其他类在使用的时候就不会在加载了,直接去cache缓存拿即可。这就是为什么每个类只加载一次,内存只有一份的原因。
举例:还是上述代码中,当第一次使用System类的时候,那么System类就会被加载了,那么System类就会存储到内存中了,当下面代码中我们再一次使用System类的时候,由于内存中已经有了,那么就不会在去加载了,这时会直接拿过来用即可。
因此方法区中每一个类的字节码文件只有一份的原因由全盘负责、委托机制和类加载器的cache ( 缓存) 机制共同决定。也称为双亲委派机制
5. 双亲委派作用: ( 一个类在内存中加载几次呢? - > 1 次)
能够让Class类加载一次
创建一个Class对象
public class ClassLoader {
@Test
public void classLorder ( ) {
ClassLoader c1 = ClassLoader. class . getClassLoader ( ) ;
System. out. println ( c1) ;
ClassLoader c2 = c1. getParent ( ) ;
System. out. println ( c2) ;
ClassLoader c3 = c2. getParent ( ) ;
System. out. println ( c3) ;
}
@Test
public void app ( ) {
ClassLoader c1 = ClassLoader. class . getClassLoader ( ) ;
System. out. println ( c1) ;
}
@Test
public void ext ( ) {
ClassLoader c1 = DNSNameService. class . getClassLoader ( ) ;
System. out. println ( c1) ;
}
@Test
public void boot ( ) {
ClassLoader cl = String. class . getClassLoader ( ) ;
System. out. println ( cl) ;
}
}
二.反射
1.概述:根据Class对象操作Class对象中的成员
三.反射之获取Class对象
获取Class对象的方式:
1. new 对象 调用 getClass ( ) - > Object中的方法
2. 类名. class - > class - > 每个数据类型, 不管是基本的还是引用的, jvm都赋予了他们一个静态属性
名字就叫做class
3. Class 中方法- > forName ( String className)
注意:
class 就加载一次, Class对象也就一个, 用3 中方式获取出来的Class是同一个
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
问题:
3 种获取Class对象的方式, 哪个在开发中最常用: forName
使用forName ( Stirng className) - > 扩展性更好, 灵活性更高
因为: 参数是一个字符串, 将来我们可以将类的全限定名放在配置文件中, 然后
用io流读取, 读取出来的字符串( 全限定名) 可以当做参数放在forName中
这样, 我们想获取不同的Class对象, 直接改文件名就可以了, 不用修改
可以代码实现一下:
a. 在模块下创建prop. properties
className= cn. itcast. day21. e_fanshe04. Student
b. 创建测试类:
public class Demo02_ForName {
public static void main ( String[ ] args) throws Exception {
Properties properties = new Properties ( ) ;
FileInputStream fis = new FileInputStream ( "day21\\pro.properties" ) ;
properties. load ( fis) ;
String className = properties. getProperty ( "className" ) ;
Class person = Class. forName ( className) ;
System. out. println ( person) ;
}
}
public class Person {
private String name;
private int age;
public Person ( ) {
}
public Person ( String name, int age) {
this . name = name;
this . age = age;
}
private Person ( String name) {
this . name = name;
System. out. println ( "我是私有的构造" ) ;
}
public String getName ( ) {
return name;
}
public void setName ( String name) {
this . name = name;
}
public int getAge ( ) {
return age;
}
public void setAge ( int age) {
this . age = age;
}
@Override
public String toString ( ) {
return name+ "..." + age;
}
}
public class Test01 {
public static void main ( String[ ] args) throws ClassNotFoundException {
Class aClass = Person. class ;
Class aClass1 = Class. forName ( "cn.itcast.day15.class02.Person" ) ;
Person person = new Person ( ) ;
Class aClass2 = person. getClass ( ) ;
System. out. println ( aClass== aClass1) ;
System. out. println ( aClass== aClass2) ;
}
}
四.获取Class对象中的构造方法
一.获取所有public的构造方法
反射通用的使用方式:
1. 获取要操作类的class 对象
2. 利用Class类中的方法获取类中的成员( 构造, 变量, 方法)
3. 运行获取出来的成员
获取Class对象中的构造方法:
Constructor< ? > [ ] getConstructors ( ) - > 获取所有的构造方法( public 修饰的)
public class Demo01_Constructor {
public static void main ( String[ ] args) throws Exception{
Class pClass = Class. forName ( "cn.itcast.day20.fanshe02.Person" ) ;
Constructor[ ] constructors = pClass. getConstructors ( ) ;
for ( Constructor constructor : constructors) {
System. out. println ( constructor) ;
}
}
}
二.获取空参构造
1. 获取指定的构造方法:
Constructor< T> getConstructor ( Class< ? > . . . parameterTypes) : 获取指定public 的构造
parameterTypes: 需要传递参数类型的class 对象
如果获取的是无参构造, 参数不写
2. Constructor类中的方法
T newInstance ( Object. . . initargs) -- > 创建对象- > new Person ( "柳岩" , 1 )
如果使用此方法创建的是无参构造, 参数不用写
此方法相当于: new Person ( ) 或者 new Person ( "柳岩" , 1 )
private static void method01 ( ) throws Exception {
Class pClass = Class. forName ( "cn.itcast.day20.fanshe02.Person" ) ;
Constructor constructor = pClass. getConstructor ( ) ;
Object o = constructor. newInstance ( ) ;
System. out. println ( o) ;
}
三.利用空参构造创建对象的快捷方式
1. 利用空参构造创建对象的快捷方式
直接调用Class类中的newInstance方法
前提: 被反射的类, 必须具有public 权限的无参构造
private static void method02 ( ) throws Exception {
Class pClass = Class. forName ( "cn.itcast.day20.fanshe02.Person" ) ;
Object o = pClass. newInstance ( ) ;
System. out. println ( o) ;
}
四.利用反射获取有参构造并创建对象
1. 获取指定的构造方法:
Constructor< T> getConstructor ( Class< ? > . . . parameterTypes)
parameterTypes: 需要传递参数类型的class 对象
如果获取的是无参构造, 参数不写
2. Constructor类中的方法
T newInstance ( Object. . . initargs) -- > 创建对象- > new Person ( "柳岩" , 1 )
如果使用此方法创建的是无参构造, 参数不用写
public class Demo03_Constructor {
public static void main ( String[ ] args) throws Exception{
Class pClass = Class. forName ( "cn.itcast.day20.fanshe02.Person" ) ;
Constructor constructor = pClass. getConstructor ( String. class , int . class ) ;
System. out. println ( constructor) ;
Object o = constructor. newInstance ( "柳岩" , 36 ) ;
System. out. println ( o) ;
}
}
五.利用反射获取私有构造(暴力反射)
获取私有的构造( 扩展) :
Constructor< ? > [ ] getDeclaredConstructors ( ) - > 获取所有的构造, 包括私有的
Constructor< T> getDeclaredConstructor ( Class< ? > . . . parameterTypes) - > 获取指定的构造
AccessibleObject类中的方法-- > 暴力反射
void setAccessible ( boolean flag)
flag: false - > 代表不能访问私有的成员
flag: true - > 解除私有权限
public class Demo04_Constructor {
public static void main ( String[ ] args) throws Exception{
Class pClass = Class. forName ( "cn.itcast.day20.fanshe02.Person" ) ;
method02 ( pClass) ;
}
private static void method02 ( Class pClass ) throws Exception {
Constructor dds = pClass. getDeclaredConstructor ( String. class ) ;
dds. setAccessible ( true ) ;
Object o = dds. newInstance ( "郭磊" ) ;
System. out. println ( o) ;
}
public static void method01 ( Class pClass ) {
Constructor[ ] dds = pClass. getDeclaredConstructors ( ) ;
for ( Constructor dd : dds) {
System. out. println ( dd) ;
}
}
}
六.利用反射获取所有成员方法
利用反射获取类中的方法:
Method[ ] getMethods ( ) 获取所有的方法, public
public class Demo05_Method {
public static void main ( String[ ] args) throws Exception {
Class c = ClassUtils. getC ( ) ;
Method[ ] methods = c. getMethods ( ) ;
for ( Method method : methods) {
System. out. println ( method) ;
}
}
}
七.反射之获取方法(有参,无参)
利用反射获取类中的方法:
Method[ ] getMethods ( ) 获取所有的方法, public
Method getMethod ( String name, Class< ? > . . . parameterTypes)
name: 获取的方法名
parameterTypes: 该方法的参数类型
Method中有一个方法
Object invoke ( Object obj, Object. . . args)
obj: 反射的类的对象
args: 运行方法传递的实参
如果执行的方法是void , 调用invoke方法没必要必须用返回值接收
如果执行的方法没有参数, 那么args不用写
public class Demo06_Method {
public static void main ( String[ ] args) throws Exception {
Class c = ClassUtils. getC ( ) ;
System. out. println ( "---------获取setName方法-------------" ) ;
Method setName = c. getMethod ( "setName" , String. class ) ;
Object o = c. newInstance ( ) ;
setName. invoke ( o, "柳岩" ) ;
System. out. println ( o) ;
System. out. println ( "---------获取getName方法-------------" ) ;
Method getName = c. getMethod ( "getName" ) ;
Object invoke = getName. invoke ( o) ;
System. out. println ( invoke) ;
}
}
五.反射练习(编写一个小框架)
利用反射, 解析配置文件中的信息
文件中配置的信息是
类的全限定名 className= cn. itcast. day20. fanshe03_test. Person
类中的某一个方法名 methodName= eat
步骤:
1. 创建配置文件- > properties
存的信息键值对的形式
问题1 : 配置文件放在哪里? 放在src下- > 切记
问题2 : 项目开发完, 交给用户使用, 给用户的是编译后的class 文件, 而out目录存放的就是class 文件
问题3 : 如果我们将配置文件放在模块下, out目录下是没有配置文件的, 那么代码运行需要读配置文件的信息
所以给了用户, 用户一执行, 卡, 报错了, 因为没读到配置文件的信息
解 决: 如果将配置文件放在src下面, idea生成的out目录中这个配置文件会显示在out的项目路径下
注意的是: src 存放的是源代码 编译后产生的class 文件, 是同步的, 但是生成的out路径, 下没有src这个目录, 因为src存放源代码的
问题: 如何读取src目录下的文件?
直接new FileInputStream ( 模块名\\src\\配置文件名) 是不行的
因为这样写, 而我们给的用户是out下的资源, 而out下存放的class 文件路径是没有src
所以这样写, 给了用户, 用户一使用, 直接就读不到这个配置文件了
解决:
使用类的加载器
ClassLoader类中的方法
InputStream getResourceAsStream ( "直接写文件名" ) 返回一个字节输入流
此流会自动扫描src下的配置文件
2. 利用IO流读取配置文件
读到Properties集合中
3. 获取Properties中对应的值
获取类的全限定名
方法名
4. 利用反射获取类的Class对象
利用反射去指定方法
className= cn. itcast. day15. class02. Person
methodName= eat
public class Test {
public static void main ( String[ ] args) throws Exception{
ClassLoader classLoader = Test. class . getClassLoader ( ) ;
InputStream in = classLoader. getResourceAsStream ( "config.properties" ) ;
Properties properties = new Properties ( ) ;
properties. load ( in) ;
String className = properties. getProperty ( "className" ) ;
String methodName = properties. getProperty ( "methodName" ) ;
Class aClass = Class. forName ( className) ;
Method method = aClass. getMethod ( methodName) ;
Object o = aClass. newInstance ( ) ;
method. invoke ( o) ;
}
}
六.注解
一.注解的介绍
1. jdk1. 5 版本的新特性- > 一个引用数据类型
和类, 接口, 枚举是同一个层次的
2. 作用:
说明: 对代码进行说明, 生成doc文档( API文档) ( 不会用)
检查: 检查代码是否有错误 @Override ( 会用)
分析: 对代码进行分析, 起到了代替配置文件的作用( 会用)
3. JDK中的注解:
@Override - > 检测此方法是否为重写方法
jdk1. 5 版本, 支持父类的方法重写
jdk1. 6 版本, 支持接口的方法重写
@Deprecated - > 方法已经过时, 不推荐使用
调用方法的时候, 方法上会有横线, 但是能用
@SuppressWarnings - > 消除警告 @SuppressWarnings ( "all" )
二.注解的定义以及属性的定义格式
1. 自定义注解格式:
修饰符 @interface 注解名{
属性
}
2. 属性的定义格式: 为了提高注解作用
- 格式1 :数据类型 属性名( ) ; -- > 没有默认值的-- > 需要后面赋值
- 格式2 :数据类型 属性名( ) default 默认值; -- > 可以改变的
3. 注解中能够定义什么样的属性
- 八种基本数据类型(int , float , boolean , byte , double , char , long , short ) 。
- String类型,Class类型,枚举类型,注解类型。
- 以上所有类型的一维数组。int [ ] [ ] arr = { { 1 , 2 } , { 3 , 4 } } [ 0 ] [ 0 ] [ 1 , 0 ] - > 3
public @interface Book {
String bookName ( ) ;
double price ( ) ;
String[ ] author ( ) ;
}
三.注解的使用
@Book ( booName = "红楼梦" , price = 200.9 , author = { "曹雪芹" , "高鹗" } )
pubilc class BookShelf {
}
注解注意事项:
1. 空注解可以直接使用
2. 一个对象中不能连续使用同一个注解多次, 但是一个对象中可以使用多个不同的注解
( 不同的位置可以使用一样的注解, 但是同样的位置不能使用一样的注解)
3. 使用注解时, 如果此注解中有属性, 注解中的属性一定要赋值, 如果有多个属性, 用, 隔开
如果注解中的属性有数组, 那么如果数组只有一个元素值, 那么{ } 不用写, 反之用写
4. 如果注解中的属性值有默认值, 那么我们不必要写, 也不用重新赋值, 反之必须写上
5. 如果注解中只有一个属性, 并且属性名叫value, 那么使用注解的时候, 属性名不用写
四.注解解析的方法
注解解析: 获取注解中的属性值
接口: AnnotatedElement接口中的方法( 用于解析注解的接口)
boolean isAnnotationPresent ( Class< ? extends Annotation > annotationClass)
判断当前class 对象是否有指定的注解
返回值: boolean 返回的是true , 证明该class 对象上有注解; 返回的是false , 证明该class 对象没有注解
Class< ? extends Annotation > annotationClass
传递的是class 对象, 传递的其实就是该class 对象上对应注解的class 对象
< T extends Annotation > getAnnotation ( Class< T> annotationClass) - > 获得当前class 对象上指定的注解对象。
解释:
参数传递的是注解的class 对象
传递哪个注解类型, 返回的就是哪个注解对象
AnnotatedElement毕竟是个接口, 你有实现类
实现类: Class类 Constructor构造方法 Field成员变量 Method成员方法
结论: 注解的解析, 和哪个技术密切相关-- > 反射
需求: 获取BookShelf1类上的注解Book的属性值
== == == == == == == == == == == == == == == == == == == == == == == == == == =
注解的解析思想:
1. 反射带有注解的类
2. 判断这个类上是否有注解
3. 获取这个注解
4. 获取注解中的属性值
-- -- -- -- -- -- -- -- --
假如我们要是解析方法上的注解属性值
1. 反射带有注解的类
2. 反射方法- > getMethod-- > Method
3. 判断方法上是否有注解
4. 获取这个注解
5. 获取注解的属性值
@Book ( booName = "红楼梦" , price = 200.9 , author = { "曹雪芹" , "高额" } )
pubilc class BookShelf {
}
public class Test01 {
public static void main ( String[ ] args) throws Exception {
Class c = Class. forName ( "cn.itcast.day21.zhujie02.BookShelf01" ) ;
boolean b = c. isAnnotationPresent ( Book. class ) ;
System. out. println ( b) ;
if ( b) {
Book book = ( Book) c. getAnnotation ( Book. class ) ;
System. out. println ( "书名:" + book. bookName ( ) ) ;
System. out. println ( "价格:" + book. price ( ) ) ;
System. out. println ( "作者:" + Arrays. toString ( book. author ( ) ) ) ;
}
}
}
七.元注解
* *
* 自定义的注解 Book
* 定义属性
*
* JDK的元注解, 比喻注解的总管
* 管理其他的注解
*
* 元注解对我们的注解进行控制
* 1 : 控制我们的注解, 可以写在哪里, ( 类, 方法, 变量上, 包. . . )
* 2 : 控制我们的注解的生命周期
*
* JDK的2 个元注解
*
* @Target 指示其他注解, 出现的位置- > 点进Target底层
* ElementType[ ] value ( ) ; 数组, 可以赋值多个- > 点ElementType底层
* ElementType是数据类型, 是枚举
* 枚举的属性, 都是静态修饰, 直接类名调用
* TYPE, 其他注解可以写在类上
* FIELD, 其他注解可以写在成员变量
* METHOD, 其他注解可以写在方法上
* PARAMETER, 其他注解可以写在方法参数上
* CONSTRUCTOR, 其他注解可以写在构造方法上
*
* @Retention 指示其他注解的生命周期- > 点到Retention底层
* RetentionPolicy value ( ) ; 不是数组, 赋值一个- > 点到RetentionPolicy底层
* RetentionPolicy数据类型
* 枚举的属性, 都是静态修饰, 直接类名调用
* SOURCE ( 默认级别) 注解仅存在于源码中java文件中( 不在class 文件中, 也不在方法区中) - > @Override 只是检 测方法是否为重写方法
* CLASS 注解存在于编译后的class 文件中- > Class文件中出现了, 方法区中没有
* RUNTIME 运行时期的内存中-- > 方法区中出现了, 一旦在方法区中出现了, 我们才能利用反射获取到注解
所以当我们在注解上写SOURCE 运行上面的Test案例判断类上有没有注解, 才会返回false
* /
@Target ( { ElementType. METHOD, ElementType. TYPE} )
@Retention ( RetentionPolicy. RUNTIME)
public @interface Book {
String bookName ( ) ;
double price ( ) ;
String[ ] author ( ) ;
}
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
@Book ( booName = "红楼梦" , price = 200.9 , author = { "曹雪芹" , "高额" } )
pubilc class BookShelf {
@Book ( booName = "红楼梦" , price = 200.9 , author = { "曹雪芹" , "高额" } )
public void lookBook ( ) {
System. out. println ( "看书" ) ;
}
}
八.注解再次解析
@Target ( ElementType. TYPE)
@Retention ( RetentionPolicy. RUNTIME)
public @interface Book {
String bookName ( ) ;
double price ( ) ;
String[ ] author ( ) ;
}
@Book ( booName = "红楼梦" , price = 200.9 , author = { "曹雪芹" , "高鹗" } )
pubilc class BookShelf {
}
== == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == == =
public class Test01 {
public static void main ( String[ ] args) throws Exception {
Class c = Class. forName ( "cn.itcast.day21.zhujie03.BookShelf" ) ;
boolean b = c. isAnnotationPresent ( Book. class ) ;
System. out. println ( b) ;
if ( b) {
Book book = ( Book) c. getAnnotation ( Book. class ) ;
System. out. println ( "书名:" + book. bookName ( ) ) ;
System. out. println ( "价格:" + book. price ( ) ) ;
System. out. println ( "作者:" + Arrays. toString ( book. author ( ) ) ) ;
}
}
}
九.模拟Junit练习
@Target ( ElementType. METHOD)
@Retention ( RetentionPolicy. RUNTIME)
public @interface MyTest {
}
public class Test01 {
@MyTest
public void method01 ( ) {
System. out. println ( "我是method01" ) ;
}
public void method02 ( ) {
System. out. println ( "我是method02" ) ;
}
@MyTest
public void method03 ( ) {
System. out. println ( "我是method03" ) ;
}
public static void main ( String[ ] args) throws Exception{
Class aClass = Test01. class ;
Object o = aClass. newInstance ( ) ;
Method[ ] methods = aClass. getMethods ( ) ;
for ( Method method : methods) {
boolean b = method. isAnnotationPresent ( MyTest. class ) ;
if ( b) {
method. invoke ( o) ;
}
}
}
}
十.Lombok(day28讲解)
使用的注解有:
@Data - > 包含get/ set,toString,hashCode,equals,无参构造方法
@Getter - > 生成get方法
@Setter - > 生成set方法
@NoArgsConstructor 和@AllArgsConstructor
- @NoArgsConstructor :无参数构造方法。
- @AllArgsConstructor :满参数构造方法。
@@EqualsAndHashCode - > 生成hashCode和Equals