一、類的加載
1、當程序主動使用某個類時,如果該類還未被加載到內存中,系統就會通過加載、連接和初始化三個步驟對類進行初始化,如果不出意外地話,JVM會連續完成這三個步驟,所以有時候會把這三個步驟痛稱爲類的加載或者類的初始化。
(1)、類的加載
是指將類的class文件讀入內存並創建一個對應的java.lang.Class實例對象。類的加載由類加載器完成,類加載器通常有JVM提供,JVM提供的這些加類載器通常稱爲系統類加載器,不過我們也可以自定義類加載器通過使用不同的類加載器,可以從不同的來源加載類的二進制數據:
●從本地文件系統加載class文件
●從jar包中加載class文件
●通過網絡加載class文件
●把一個java源文件動態編譯並執行加載
●某些類是可以預加載的
(2)、類的連接
通過加載生成Class對象以後就進入了連接階段,此階段負責將類的二進制數據合併到jre中去,這一過程分爲三個階段:
●驗證:檢驗被加載的類是否有正確的內部結構並與其它類協調
●準備:爲類的靜態屬性分配內存,並設置默認值
●解析:將二進制數據中的符號引用替換成直接引用
(3)、類的初始化
主要就是對靜態屬性進行初始化,初始化時機有:
●創建類的實例。
●調用類的靜態方法。
●訪問某個類或接口的靜態屬性或者爲該屬性賦值。
●使用反射方式來強制創建某個類或接口的java.lang.Class實例。
●初始化某個類的子類時引起的父類初始化。
●直接使用java.exe命令來運行某個主類時,程序會先初始化該主類。
例外情況有:
●對於一個final修飾的靜態屬性,如果該屬性在編譯時期就可以得到屬性值的話,則被認爲是編譯時常量,使用該屬性不會引起類的初始化,反之,如果此final型屬性不能再編譯時獲得初始值,只有在運行時纔可以確定其值,那麼當訪問該屬性時將會導致類的初始化。如下:
static final String currentTime = System.currentTimeMills()+""
●當使用ClassLoader類的loadClass方法加載類時,只加載類,不會初始化類,使用Class類的forName方法是會強制初始化類。
二、類加載器
1、當JVM啓動時,會形成由三個類加載器組成的初始類加載器層次結構:
(1)、Bootstrap ClassLoader:根類加載器(又稱引導類加載器),負責加載java的核心類,它多負責的查找路徑爲系統屬性sun.boot.class.path指定的值
(2)、Extension ClassLoader:擴展類加載器,負責加載jre的擴展目錄或者由系統屬性java.ext.dirs指定的目錄中的jar包中的類
(3)、System ClassLoader:系統類加載器,負責在JVM啓動時加載系統屬性java.class.path或者環境變量classpath指定的路徑下的類或者jar包
通過下面的小程序可以得到各個類加載器的默認加載路徑信息:
public class DeepClassLoader {
public static void main(String[] args){
System.out.println("Bootstrap ClassLoader的默認加載路徑:");
String[] bootPath = System.getProperty("sun.boot.class.path").split(";");
for(String path : bootPath){
System.out.println("\t"+path+";");
}
System.out.println();
System.out.println("Extension ClassLoader的默認加載路徑:");
String[] extPath = System.getProperty("java.ext.dirs").split(";");
for(String path : extPath){
System.out.println("\t"+path+";");
}
System.out.println();
System.out.println("System ClassLoader的默認加載路徑:");
System.out.println("\t"+System.getProperty("java.class.path"));
}
}
執行結果爲:
Bootstrap ClassLoader的默認加載路徑:
D:\Java\Jdk\jre\lib\resources.jar;
D:\Java\Jdk\jre\lib\rt.jar;
D:\Java\Jdk\jre\lib\sunrsasign.jar;
D:\Java\Jdk\jre\lib\jsse.jar;
D:\Java\Jdk\jre\lib\jce.jar;
D:\Java\Jdk\jre\lib\charsets.jar;
D:\Java\Jdk\jre\lib\modules\jdk.boot.jar;
D:\Java\Jdk\jre\classes;
Extension ClassLoader的默認加載路徑:
D:\Java\Jdk\jre\lib\ext;
C:\Windows\Sun\Java\lib\ext;
System ClassLoader的默認加載路徑:
D:\Eclipse\workspace\CustomizeClassLoader\bin
2、了加載機制
(1)、父類委託機制:先讓父類加載器嘗試加載該類,只有父類加載失敗是才嘗試從自己的類路徑中加載該類,其委託關係看下面層次圖:
(2)、緩存機制:此機制會保證多有加載過的類被緩存,當程序需要用到某個類時,類加載器先從緩存中查找,當緩存中沒有時纔會重讀此類的二進制數據,並將其裝換爲Class實例並緩存,這就是爲什麼修給了Class以後要重新啓動JVM,程序所做的修改才生效。
3、自定義類加載器
(1)、ClassLoader有三個關鍵方法
●loadClass(String name)該方法爲ClassLoader的入口,根據指定的二進制名稱來加載類,系統就是調用ClassLoader的該方法來獲取指定類的Class對象。
●findClass(String name)根據二進制名稱來查找類
●defineClass(String name,byte[] b,int off,int len)將指定類的字節碼文件讀入字節數組b,並把它轉化爲Class對象。
(2)、loadClass方法的執行步驟如下:
●用findLoadedClass(String name)來檢查是否已經加載類,如果已經加載則直接返回。
●在父類加載器上調用loadClass方法,如果父類加載器爲null,則使用根類加載器來加載。
●調用findClass方法查找類。
所以,當我們自定義類時,推薦重寫findClass方法,以避免覆蓋默認類加載器的父類委託、緩衝機制兩種策略。
三、註解
1、註解相當於一種標記,在程序中加了註解就等於爲程序打上了某種標記,沒加,則等於沒有某種標記,以後,javac編譯器,開發工具和其他程序可以用反射來了解你的類及各種元素上有無何種標記,看你有什麼標記,就去幹相應的事。標記可以加在包,類,字段,方法,方法的參數以及局部變量上。
2、註解就相當於一個你的源程序中要調用的一個類,要在源程序中應用某個註解,得先準備好了這個註解類。就像你要調用某個類,得先有開發好這個類。
3、三個基本Annotation
● @Override--用來指定方法覆蓋,告訴編譯器檢查這個方法,並從父類查找是否包含一個被該方法重寫的方法,它可以強制一個子類必須覆蓋父類方法。
● @Deprecated--用於表示某個程序元素已過時。
● @SuppressWarnings--指示被Annotation標識的元素取消顯示指定的編譯器警告。
4、四個元Annotation
● @Retention--用於指定該Annotation可以保留多長時間,其中包含一個RetentionPolicy類型的value變量,所以使用該註解時一定要爲value成員變量賦值,它的值只有三個:
RetentionPolicy.CLASS--編譯器將把註解記錄在class文件中,當運行java程序時,JVM不再保留註釋,這也是默認值。
RetentionPolicy.RUNTIME--編譯器將把註解記錄在class文件中,當運行java程序時,JVM也會保留註釋,可以通過反射獲取該註解。 @Deprecated即爲此類註解。
RetentionPolicy.SOURCE--編譯器將直接丟棄該策略的註解。 @Override、 @SuppressWarnings即爲此類註解。
● @Target--用於指定被修飾的Annotation能夠修飾哪些程序元素。其中包含一個ElementType[]類型的value變量,它的值可以爲:
ElementType.ANNOTATION_TYPE--註釋類型聲明
ElementType.CONSTRUCTOR--構造方法聲明
ElementType.FIELD--字段聲明(包括枚舉常量)
ElementType.LOCAL_VARIABLE--局部變量聲明
ElementType.METHOD--方法聲明
ElementType.PACKAGE--包聲明
ElementType.PARAMETER--參數聲明
ElementType.TYPE--類、接口(包括註釋類型)或枚舉聲明
● @Documented--用於指定被該元Annotation修飾的Annotation類可以被javadoc工具提取成文檔,即API文檔中將會包含該Annotation。
● @Inherited--被它修飾的Annotation將具有繼承性。
5、反射裏面與Annotation相關的方法:
(1)、getAnnotation(Class<T> annotationClass)如果存在該元素的指定類型的註釋,則返回這些註釋,否則返回 null。
(2)、getDeclaredAnnotations()、getAnnotations()返回直接存在於此元素上的所有註釋。
(3)、isAnnotationPresent(Class<? extends Annotation> annotationClass)如果指定類型的註釋存在於此元素上,則返回 true,否則返回 false。
6、自定義註解
註解的自定義形式與接口相似,只是在定義屬性和訪問屬性時都是以方法的形式,如下:
@Retention(RetentionPolicy.RUNTIME)//定義該註解運行時可通過反射獲取
@Target(ElementType.METHOD) //定義該註解只可作用於方法
public @interface MyAnnotation { //類似於接口定義形式
String value() default "jzk"; //以方法的形式定義了一個字符類型的成員變量,且指定默認值
}
public class AnnotationTest {
public static void main(String[] args) throws Exception{
System.out.println(MainClass.class.getMethod("test") //判斷指定方法是否有指定類型的註解
.isAnnotationPresent(MyAnnotation.class));
System.out.println(MainClass.class.getMethod("test") //獲取註解實例,然後獲取成員值
.getAnnotation(MyAnnotation.class).value());
}
//指定value值,此時指定值覆蓋默認值,當自定義註解只有一個value成員變量時
//也可以使用 @MyAnnotation("jkk")這種形式
@MyAnnotation(value="jkk")
// @MyAnnotation() 使用value成員的默認值
public void test(){}
}
自定義註解的成員變量類型可以爲:8中基本類型,枚舉類型,註解類型,Class類型,或者前面任何一種類型的數組,如下
public @interface AnnoElement {
String value();
}
public enum EunmElement {
A,B,C;
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnnotation {
String strElement();
EunmElement enumEemelms();
int[] intArray();
AnnoElement annoElement();
Class<?> classElement();
}
//要使用就得逐個賦值
@MyAnnotation(strElement="jzk",
enumEemelms=EunmElement.A,
annoElement= @AnnoElement("jkk"),
classElement=AnnoElement.class,
intArray={1,2,3})