零.类的加载时机
一.类加载器
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