零.類的加載時機
一.類加載器
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