自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

談java中類的加載、鏈接和初始化

開發(fā) 后端
本文介紹的是java中的類的類的加載、鏈接和初始化等相關的問題,希望對你有幫助,一起來看吧!

Java字節(jié)代碼的表現形式是字節(jié)數組(byte[]), 而Java類在JVM中的表現形式是java. lang. Class類的對象。 一個Java類從字節(jié)代碼到能夠在JVM中被運用, 需要經過加載、鏈接和初始化這三個步驟。

這三個步驟中, 對開發(fā)人員直接可見的是Java類的加載, 通過運用Java類加載器(class loader)可以在運行時辰靜態(tài)的加載一個Java類;而鏈接和初始化則是在運用Java類之前會發(fā)生的舉措。 本文會詳細引見Java類的加載、鏈接和初始化的進程。

Java類的加載

Java類的加載是由類加載器來完成的。 普通來說, 類加載器分成兩類:啟動類加載器(bootstrap)和用戶自定義的類加載器(user-defined)。 兩者的區(qū)別在于啟動類加載器是由JVM的原生代碼實現的, 而用戶自定義的類加載器都繼承自Java中的java. lang. ClassLoader類。 在用戶自定義類加載器的部分, 普通JVM都會提供一些根本實現。 應用順序的開發(fā)人員也可以依據需要編寫自己的類加載器。 JVM中最常運用的是零碎類加載器(system), 它用來啟動Java應用順序的加載。 通過java. lang. ClassLoader的getSystemClassLoader()方法可以獲取到該類加載器對象。 

類加載器需要完成的最終功能是定義一個Java類, 即把Java字節(jié)代碼轉換成JVM中的java. lang. Class類的對象。 但是類加載的進程并不是這么簡單。 Java類加載器有兩個比較重要的特征:層次組織構造和代理形式。 層次組織構造指的是每個類加載器都有一個父類加載器, 通過getParent()方法可以獲取到。 類加載器通過這種父親-后代的方式組織在一起, 構成樹狀層次構造。 代理形式則指的是一個類加載器既可以自己完成Java類的定義任務, 也可以代理給其它的類加載器來完成。

由于代理形式的存在, 啟動一個類的加載進程的類加載器和最終定義這個類的類加載器能夠并不是一個。 前者稱為初始類加載器, 然后者稱為定義類加載器。 兩者的關聯在于:一個Java類的定義類加載器是該類所導入的其它Java類的初始類加載器。 比方類A通過import導入了類 B, 那么由類A的定義類加載器負責啟動類B的加載進程。

普通的類加載器在嘗試自己去加載某個Java類之前, 會首先代理給其父類加載器。 當父類加載器找不到的時候, 才會嘗試自己加載。 這個邏輯是封裝在java. lang. ClassLoader類的loadClass()方法中的。 普通來說, 父類優(yōu)先的戰(zhàn)略就足夠好了。 在某些狀況下, 能夠需要采取相反的戰(zhàn)略, 即先嘗試自己加載, 找不到的時候再代理給父類加載器。

這種做法在Java的Web容器中比較常見, 也是Servlet規(guī)范推薦的做法。 比方, Apache Tomcat為每個Web應用都提供一個獨立的類加載器, 運用的就是自己優(yōu)先加載的戰(zhàn)略。 IBM WebSphere Application Server則允許Web應用選擇類加載器運用的戰(zhàn)略。

類加載器的一個重要用途是在JVM中為相同名稱的Java類創(chuàng)立隔離空間。 在JVM中, 判斷兩個類是否相同, 不僅是依據該類的二進制名稱, 還需要依據兩個類的定義類加載器。 只有兩者完全一樣, 才認為兩個類的是相同的。 因此, 即便是異樣的Java字節(jié)代碼, 被兩個不同的類加載器定義之后, 所失掉的Java類也是不同的。 假如試圖在兩個類的對象之間停止賦值操作, 會拋出java. lang. ClassCastException。

這個特性為異樣名稱的Java類在JVM中共存創(chuàng)造了條件。 在實際的應用中, 能夠會要求同一名稱的Java類的不同版本在JVM中可以同時存在。 通過類加載器就可以滿足這種需求。 這種技術在OSGi中失掉了廣泛的應用。

Java類的鏈接

Java類的鏈接指的是將Java類的二進制代碼合并到JVM的運行狀態(tài)之中的進程。 在鏈接之前, 這個類必需被成功加載。 類的鏈接包括驗證、準備和解析等幾個步驟。 驗證是用來確保Java類的二進制表示在構造上是完全正確的。 假如驗證進程出現錯誤的話, 會拋出java. lang. VerifyError錯誤。 準備進程則是創(chuàng)立Java類中的靜態(tài)域, 并將這些域的值設為默許值。 準備進程并不會執(zhí)行代碼。

在一個Java類中會包含對其它類或接口的形式援用, 包括它的父類、所實現的接口、方法的形式參數和前往值的Java類等。 解析的進程就是確保這些被援用的類能被正確的找到。 解析的進程能夠會導致其它的Java類被加載。

不同的JVM實現能夠選擇不同的解析戰(zhàn)略。 一種做法是在鏈接的時候, 就遞歸的把所有依賴的形式援用都停止解析。 而另外的做規(guī)律能夠是只在一個形式援用真正需要的時候才停止解析。 也就是說假如一個Java類只是被援用了, 但是并沒有被真正用到, 那么這個類有能夠就不會被解析。 思索上面的代碼:

  1. public class LinkTest . . . {  
  2. public static void main(String[] args) . . . {  
  3. ToBeLinked toBeLinked = null;  
  4. System. out. println(Test link. );  
  5. }  

類 LinkTest援用了類ToBeLinked, 但是并沒有真正運用它, 只是聲明了一個變量, 并沒有創(chuàng)立該類的實例或是訪問其中的靜態(tài)域。 在 Oracle的JDK 6中, 假如把編譯好的ToBeLinked的Java字節(jié)代碼刪除之后, 再運行LinkTest, 順序不會拋出錯誤。 這是由于ToBeLinked類沒有被真正用到, 而Oracle的JDK 6所采用的鏈接戰(zhàn)略使得ToBeLinked類不會被加載, 因此也不會發(fā)現ToBeLinked的Java字節(jié)代碼實際上是不存在的。 假如把代碼改成ToBeLinked toBeLinked = new ToBeLinked();之后, 再按照相同的方法運行, 就會拋出異常了。 由于這個時候ToBeLinked這個類被真正運用到了, 會需要加載這個類。

Java類的初始化

當一個Java類第一次被真正運用到的時候, JVM會停止該類的初始化操作。 初始化進程的主要操作是執(zhí)行靜態(tài)代碼塊和初始化靜態(tài)域。 在一個類被初始化之前, 它的直接父類也需要被初始化。 但是, 一個接口的初始化, 不會引起其父接口的初始化。 在初始化的時候, 會按照源代碼中從上到下的順序依次執(zhí)行靜態(tài)代碼塊和初始化靜態(tài)域。 思索上面的代碼:

  1. public class StaticTest . . . {  
  2. public static int X = 10;  
  3. public static void main(String[] args) . . . {  
  4. System. out. println(Y); //輸入60  
  5. }  
  6. static . . . {  
  7. X = 30;  
  8. }  
  9. public static int Y = X * 2;  

在上面的代碼中, 在初始化的時候, 靜態(tài)域的初始化和靜態(tài)代碼塊的執(zhí)行會從上到下依次執(zhí)行。 因此變量X的值首先初始化成10, 后來又被賦值成30;而變量Y的值則被初始化成60。

Java類和接口的初始化只有在特定的機遇才會發(fā)生, 這些機遇包括:

創(chuàng)立一個Java類的實例。 如:MyClass obj = new MyClass()

調用一個Java類中的靜態(tài)方法。 如:MyClass. sayHello()

在頂層Java類中執(zhí)行assert語句。 

通過Java反射API也能夠形成類和接口的初始化。 需要注意的是, 當訪問一個Java類或接口中的靜態(tài)域的時候, 只有真正聲明這個域的類或接口才會被初始化。 思索上面的代碼:

  1. class B . . . {  
  2. static int value = 100;  
  3. static . . . {  
  4. System. out. println(Class B is initialized. ); //輸入  
  5. }  
  6. }  
  7. class A extends B . . . {  
  8. static . . . {  
  9. System. out. println(Class A is initialized. ); //不會輸入  
  10. }  
  11. }  
  12. public class InitTest . . . {  
  13. public static void main(String[] args) . . . {  

創(chuàng)立自己的類加載器

在 Java應用開發(fā)進程中, 能夠會需要創(chuàng)立應用自己的類加載器。 典型的場景包括實現特定的Java字節(jié)代碼查找方式、對字節(jié)代碼停止加密/解密以及實現同名 Java類的隔離等。 創(chuàng)立自己的類加載器并不是一件復雜的事情, 只需要繼承自java. lang. ClassLoader類并覆寫對應的方法即可。 java. lang. ClassLoader中提供的方法有不少, 上面引見幾個創(chuàng)立類加載器時需要思索的:

  • defineClass():這個方法用來完成從Java字節(jié)代碼的字節(jié)數組到java. lang. Class的轉換。 這個方法是不能被覆寫的, 普通是用原生代碼來實現的。
  • findLoadedClass():這個方法用來依據名稱查找已經加載過的Java類。 一個類加載器不會重復加載同一名稱的類。
  • findClass():這個方法用來依據名稱查找并加載Java類。
  • loadClass():這個方法用來依據名稱加載Java類。
  • resolveClass():這個方法用來鏈接一個Java類。

這里比較 容易混淆的是findClass()方法和loadClass()方法的作用。 前面提到過, 在Java類的鏈接進程中, 會需要對Java類停止解析, 而解析能夠會導致以后Java類所援用的其它Java類被加載。 在這個時候, JVM就是通過調用以后類的定義類加載器的loadClass()方法來加載其它類的。 findClass()方規(guī)律是應用創(chuàng)立的類加載器的擴展點。 應用自己的類加載器應該覆寫findClass()方法來添加自定義的類加載邏輯。 loadClass()方法的默許實現會負責調用findClass()方法。 

前面提到, 類加載器的代理形式默許運用的是父類優(yōu)先的戰(zhàn)略。 這個戰(zhàn)略的實現是封裝在loadClass()方法中的。 假如希望修改此戰(zhàn)略, 就需要覆寫loadClass()方法。 

上面的代碼給出了自定義的類加載的常見實現形式:

  1. public class MyClassLoader extends ClassLoader . . . {  
  2. protected Class findClass(String name) throws ClassNotFoundException . . . {  
  3. byte[] b = null//查找或生成Java類的字節(jié)代碼  
  4. return defineClass(name, b, 0, b. length);  
  5. }  

希望通過以上關于java中類的加載、鏈接和初始化三方面的介紹,能夠給你帶來幫助。

責任編輯:于鐵 來源: 互聯網
相關推薦

2020-11-02 07:02:10

加載鏈接初始化

2024-03-08 08:26:25

類的加載Class文件Java

2024-03-12 07:44:53

JVM雙親委托機制類加載器

2012-02-28 10:04:09

Java

2012-05-23 12:46:53

JavaJava類

2019-11-04 13:50:36

Java數組編程語言

2012-04-09 13:43:12

Java

2013-03-04 11:10:03

JavaJVM

2011-06-17 15:29:44

C#對象初始化器集合初始化器

2022-03-21 09:50:50

JavaGroovy映射Map

2021-04-01 10:01:55

JavaStringJava基礎

2022-03-30 08:19:12

JavaGroovy

2011-06-09 14:13:06

C++JAVA缺省初始化

2012-03-13 13:38:42

Java

2023-12-18 09:26:12

C++switchif

2011-03-17 09:58:43

Java虛擬機JVM

2023-10-06 20:57:52

C++聚合成員

2022-01-04 19:33:03

Java構造器調用

2023-11-12 23:08:17

C++初始化

2016-11-11 00:33:25

雙重檢查鎖定延遲初始化線程
點贊
收藏

51CTO技術棧公眾號