ava是一門功能強(qiáng)大的多用途編程語言,也是全球最流行的開發(fā)語言之一。它是面向?qū)ο缶幊陶Z言的代表,集跨平臺、健壯性、高性能等諸多優(yōu)點,廣泛應(yīng)用于Web后端開發(fā)、移動端開發(fā)、大數(shù)據(jù)分析、人工智能等熱門領(lǐng)域,在互聯(lián)網(wǎng)行業(yè)占據(jù)十分重要的地位。目前,全球有超過500萬的專業(yè)開發(fā)者在使用Java語言,Java程序運(yùn)行在全球數(shù)十億臺設(shè)備上。作為全書開篇,本章將對Java概述、開發(fā)環(huán)境搭建、程序開發(fā)步驟、虛擬機(jī)與垃圾回收、開發(fā)工具等內(nèi)容進(jìn)行講解,帶領(lǐng)帶領(lǐng)大家進(jìn)入Java世界。
Java是一種高級計算機(jī)語言,它是由Sun公司(2009年4月20日被Oracle公司收購,2010年完成合并)于1995年5月推出的一種用來編寫跨平臺應(yīng)用軟件、完全面向?qū)ο蟮某绦蛟O(shè)計語言。Java語言簡單易用、安全可靠,自從問世以后,受到了市場的大力追捧。在PC、移動設(shè)備、家用電器等領(lǐng)域,Java技術(shù)無處不在。
Sun公司在1995年推出Java語言以后,吸引了編程世界的廣泛關(guān)注。那么,Java到底有什么魔力呢?1990年末,Sun公司預(yù)測嵌入式系統(tǒng)將會在家電領(lǐng)域大顯身手,于是在1991年6月啟動了“Green計劃”,由詹姆斯·高斯林(James Gosling)、邁克·謝里丹(Mike Sheridan)等人帶領(lǐng)的開發(fā)團(tuán)隊負(fù)責(zé),準(zhǔn)備開發(fā)一種能夠在各種消費(fèi)性電子產(chǎn)品(如機(jī)頂盒、冰箱、收音機(jī)等)上運(yùn)行的程序架構(gòu),以便于人們與家用電器進(jìn)行信息交流與控制。因為家用電器的計算處理能力和內(nèi)存都非常有限,所以要求語言必須非常小且能夠生成非常緊湊的代碼,這樣才能在這樣的環(huán)境中執(zhí)行。另外,不同的家用電器使用的CPU不同,因此要求該語言必須是跨平臺的。開發(fā)團(tuán)隊最初考慮使用C++語言,但是C++太過復(fù)雜,以致很多開發(fā)者經(jīng)常錯誤使用,而且項目面向的是嵌入式平臺,可用的系統(tǒng)資源十分有限,所以Sun公司創(chuàng)始人之一的比爾·喬伊(Bill Joy)決定開發(fā)一種新語言,他提議在C++的基礎(chǔ)上開發(fā)一種面向?qū)ο蟮沫h(huán)境。Java便由此而問世,詹姆斯?高斯林最初將其命名為Oak(橡樹)。遺憾的是,當(dāng)時由于這門語言只能為家用電器提供一個通用環(huán)境,且受到諸多因素的限制,Oak語言沒有得到迅速推廣。1994年夏天,隨著Innernet的迅猛發(fā)展,瀏覽器的出現(xiàn),枯燥乏味的信息文檔已經(jīng)不滿足人們的需求,這給Oak語言帶來了新的生機(jī)。詹姆斯·高斯林立刻意識到這是一個機(jī)會,于是對Oak進(jìn)行了小規(guī)模的改造。之后,開發(fā)團(tuán)隊的其他成員完成了第一個基于Oak語言的網(wǎng)頁瀏覽器WebRunner,從而讓瀏覽器具有了在網(wǎng)頁中執(zhí)行內(nèi)嵌代碼的能力,可以創(chuàng)造含有動態(tài)內(nèi)容的網(wǎng)頁。1995年,Sun公司將Oak更名為Java,并將其直接發(fā)布在互聯(lián)網(wǎng)上,免費(fèi)開源給大家使用,獲得了廣大開發(fā)人員的青睞。之后,Java開始走紅,成為一門廣為人知的編程語言,被用于開發(fā)Web應(yīng)用程序。
Java一開始具有吸引力,是因為Java程序可以在Web瀏覽器中運(yùn)行,隨著Internet普及和迅猛發(fā)展,以及Web技術(shù)的不斷更新,Java語言與時俱進(jìn)、推陳出新,使Java語言在現(xiàn)在社會經(jīng)濟(jì)發(fā)展和科學(xué)研究中,占據(jù)了越來越重要的地位。在最流行的語言流行指數(shù)Tiobe,RedMonk和PyPL中均長期排名前三,且多年是Tiobe排行榜中排名第一的語言。從手機(jī)軟件到企業(yè)級應(yīng)用、從無人駕駛汽車到線上支付、從Minecraft(我的世界)游戲娛樂到火星探測器太空探索,Java語言的使用場景非常廣泛。現(xiàn)在,Java廣泛應(yīng)用于開發(fā)服務(wù)器端的應(yīng)用程序,截止到2021年,Java開發(fā)占據(jù)了服務(wù)器端后臺開發(fā)80%以上的市場份額。
Java語言目前主要應(yīng)用于如下領(lǐng)域:
從Java編程語言本身角度來講,其嚴(yán)謹(jǐn)?shù)慕Y(jié)構(gòu),易懂的語法加上簡易的編寫為其之后的發(fā)展及革新提供了良好的保障。
注意:Java是印度尼西亞爪哇島的英文名稱,因盛產(chǎn)咖啡而聞名。
Java語言是一門跨平臺的適用于移動端、服務(wù)器領(lǐng)域、分布式環(huán)境的面向?qū)ο蟪绦蛟O(shè)計語言,它之所以能從眾多編程語言中脫穎而出,成為最流行的服務(wù)端開發(fā)語言之一,是因為具備如下顯著特點:
想一想:你了解哪些語言?Java語言在眾多編程中脫穎而出的原因有哪些?
Java開發(fā)工具包(JavaSE Development Kits,簡稱JDK)是一套獨(dú)立程序構(gòu)成的集合,用于開發(fā)和測試Java程序,是Java程序開發(fā)的首要工具。
JDK由Java API、Java工具和Java基礎(chǔ)的類庫等組成,其核心是Java API,API(Application Programming Interface,應(yīng)用程序編程接口)是Java提供的供編程人員使用的標(biāo)準(zhǔn)類庫,開發(fā)人員可以用這些類庫中的類來實現(xiàn)Java程序的各種功能,從而免去自行設(shè)計很多常用類的繁重工作,極大地提高開發(fā)效率。另外,Java API還包括一些重要的語言結(jié)構(gòu)以及基本圖形、網(wǎng)絡(luò)和文件I/O等。
本書中使用的是JDK15版本,與之前的版本相比,JDK 15 為用戶提供了14項主要的增強(qiáng)(JEP),同時新增了1個孵化器模塊、3個預(yù)覽功能、2個不推薦使用的功能,并刪除了2個淘汰的功能。
知識點撥:增強(qiáng)(JEP)、孵化器模塊(Incubator)和預(yù)覽特性(Preview)的具體含義:
增強(qiáng):英文全稱為JDK Enhancement Proposals ,簡稱 JEP,是JDK 增強(qiáng)建議,主要包括新增特性和改進(jìn)提案。
孵化器:實際上就是實驗版,主要從Java社區(qū)收集意見、反饋,穩(wěn)定性差,后期可能有比較大的變動,稱之為尚未定稿的API/工具。
預(yù)覽特性:規(guī)格已經(jīng)成型,實現(xiàn)基本確定,但是最終未定稿,這些特性,還可能被移除,但可能性比較小,一般都會定下來。
編寫Java程序,首先要下載JDK安裝程序,讀者可以直接從Oracle公司的官方網(wǎng)站下載。
通過瀏覽器打開Oracle官網(wǎng)(http://www.oracle.com/technetwork/java/javase/ downloads/index.html),根據(jù)提示進(jìn)入下載頁面,找到與自己的計算機(jī)操作系統(tǒng)對應(yīng)的JDK安裝文件下載鏈接,點擊下載即可。網(wǎng)頁內(nèi)容可能因版本或Oracle公司規(guī)劃而有所不同,用戶可以根據(jù)需要選擇所需要的JDK版本。
JDK安裝文件下載成功后,就可以安裝了。本書使用的是64位的Windows10環(huán)境,接下來詳細(xì)演示W(wǎng)indows 64位平臺下JDK15的安裝過程,具體步驟如下:
(1)雙擊從Oracle官網(wǎng)下載的JDK安裝文件,進(jìn)入JDK安裝界面,如圖1.1所示。
(2)單擊圖1.1中的“下一步”按鈕,進(jìn)入到JDK自定義安裝界面,如圖1.2所示。
圖1.1 JDK安裝界面 圖1.2 JDK默認(rèn)安裝路徑
(3)建議選擇直接安裝到默認(rèn)目錄,單擊“下一步”按鈕即可進(jìn)行安裝,如圖 1.3所示。也可以點擊“更改”按鈕,自行選擇安裝目錄。
(4)安裝完畢之后,彈出如圖1.4所示的界面,點擊“關(guān)閉”按鈕即可。
圖1.3 等待安裝界面 圖1.4 安裝完畢界面
在使用Java來編譯和運(yùn)行程序之前,必須先設(shè)置好環(huán)境變量。所謂環(huán)境變量,就是在操作系統(tǒng)中定義的變量,可供操作系統(tǒng)上的所有應(yīng)用程序使用。Path環(huán)境變量的作用是設(shè)置一個路徑,由操作系統(tǒng)去尋找該路徑下的文件(如.bat、.ext、.com等),對Java來說就是Java的安裝路徑。
下面以Windows 10操作系統(tǒng)為例說明。具體步驟如下:
(1)選擇“控制面板”→“系統(tǒng)和安全”→“系統(tǒng)”(也可以在桌面上右鍵單擊“此電腦”或“我的電腦”,選擇“屬性”選項),進(jìn)入到系統(tǒng)窗口,如圖1.5所示。
單擊“高級系統(tǒng)設(shè)置”
圖1.5 Windows 10系統(tǒng)窗口界面
(2)單擊“高級系統(tǒng)設(shè)置”選項,彈出“系統(tǒng)屬性”窗口,如圖1.6所示。
(3)單擊“環(huán)境變量(N)…”按鈕,彈出 “環(huán)境變量”窗口,如圖1.7所示。
圖1.6 “系統(tǒng)屬性”窗口界面 圖1.7 “環(huán)境變量”窗口界面
(4)在“環(huán)境變量”窗口的“系統(tǒng)變量(S)”區(qū)域中,單擊“新建(W)…”按鈕,打開“新建系統(tǒng)變量”窗口。并在“變量名(N)”文本框中輸入“JAVA_HOME”,在“變量值(V)”文本框中輸入JDK安裝目錄。筆者此時的安裝目錄為“C:\Program Files\Java\jdk-15”,如圖1.8所示。單擊“確定”按鈕,完成JAVA_HOME環(huán)境變量的配置。
圖1.8 “新建系統(tǒng)變量”窗口界面
(5)在“環(huán)境變量”窗口的“系統(tǒng)變量(S)”區(qū)域中,選中系統(tǒng)變量“Path”,如圖1.9所示。
圖1.9 “環(huán)境變量”窗口需按照Path變量界面
(6)在圖1.9所示的界面單擊“編輯(I)…”按鈕,打開“編輯環(huán)境變量”窗口,點擊“新建(N)”按鈕,在編輯頁面的文本框中添加“%JAVA_HOME%\bin”,如圖1.10所示。然后,單擊窗口的“確定”按鈕,保存環(huán)境變量,完成配置。
圖1.10 “編輯系統(tǒng)環(huán)境變量”窗口界面
注意:在配置Path環(huán)境變量時,JAVA_HOME環(huán)境變量并不是一定需要配置的,我們也可以直接將JDK的安裝路徑(C:\Program Files\Java\JDK-15\bin;)添加到Path環(huán)境變量中。這里配置JAVA_HOME的好處是,當(dāng)JDK的版本或安裝路徑發(fā)生變化時,只需要修改JAVA_HOME的值,而不用修改Path環(huán)境變量的值。
個別圖書中會提到Classpath環(huán)境變量,Classpath環(huán)境變量的作用與Path環(huán)境變量的作用類似,它是JVM執(zhí)行Java程序時搜索類的路徑的順序,以最先找到為準(zhǔn),JDK1.5之后,如果沒有設(shè)置Classpath環(huán)境變量,則Java解釋器會在當(dāng)前路徑下搜索Java類,故本書不再贅述。
JDK配置完成后,需要測試JDK是否能夠在計算機(jī)上運(yùn)行,具體步驟如下:
(1)同時按下鍵盤 Window鍵和R鍵,調(diào)出Dos命令行運(yùn)行窗口,在搜索框輸入cmd,如圖 1.11所示。
(2)單擊“確定”按鈕,進(jìn)入命令行窗口,如圖1.12所示。
圖1.11 運(yùn)行窗口 圖1.12 命令行窗口
(3)在命令行窗口中輸入“javac”命令,并按Enter鍵,系統(tǒng)會輸出javac的幫助信息,如圖1.13所示,說明JDK已經(jīng)成功配置,否則需要仔細(xì)檢查JDK環(huán)境變量的配置是否正確。
圖1.13 命令行信息
JDK安裝成功后,系統(tǒng)會自動在我們的安裝目錄下生成一個目錄,稱為JDK目錄,如圖 1.14所示,我們必須熟悉JDK目錄各個文件夾的作用才能更好的學(xué)習(xí)與編寫代碼。
圖1.14 JDK目錄
接下來,簡單介紹一下JDK目錄及其子目錄的含義和作用:
注意:自JDK9 以后,就取消了目錄中的jre目錄,將之前jre目錄里面的內(nèi)容分散到其他各個目錄了。
在我們?yōu)殡娔X配好Java開發(fā)環(huán)境以后,也就代表著我們可以開始實現(xiàn)我們的Java開發(fā)之旅了,現(xiàn)在我們自己來動手編寫一個Java程序,親自感受一下Java語言的編寫規(guī)范。
下面將編寫第一個Java程序,其功能是控制臺輸出“有夢想,一起實現(xiàn)!”,如例1-1所示。
例1-1 HelloDream.java
在開始編寫代碼之前,先在電腦D盤(本書使用D盤)中創(chuàng)建一個新的目錄及子目錄:“d:\javaCode\demo01”,然后在demo01下創(chuàng)建一個文本文件,并重命名為“HelloDream.java”,使用記事本打開,編寫如下程序代碼:
例1-1是程序的源代碼,下面針對逐條語句進(jìn)行詳細(xì)的講解,如圖1.15所示。
class是Java關(guān)鍵字,用來聲明該文件是一個類。類作為程序的基本單元存在,所有Java代碼必須寫在類里面
我們自己定義的類名,方便識別,類名必須與文件名一致
輸出語句,把我們要展示的內(nèi)容在控制臺輸出來
這句聲明了一個main方法,作為Java程序的執(zhí)行入口,需要執(zhí)行的代碼都要寫在main方法的大括號內(nèi)
圖1.15 記事本編寫的java代碼
注意:在編寫Java代碼時,所有的符號必須用英文半角格式,不允許出現(xiàn)中文字符。
編寫好的Java代碼文件需要編譯成Java字節(jié)碼文件(class文件)才能運(yùn)行,Java程序的編譯步驟如下:
接下來打開Dos命令行窗口,并按下面步驟來編譯和運(yùn)行HelloDream.java。
(1)打開Dos命令行窗口,先將路徑切換到需要編譯代碼的位置,即在窗口依次輸入“d:\javaCode\demo01”和“d:”命令,如圖1.16所示。
(2)切換好磁盤路徑之后,在命令行窗口輸入“javac HelloDream.java”命令,對源文件進(jìn)行編譯,如圖1.17所示。
圖1.16 切換磁盤目錄 圖1.17 編譯Java源文件
(3)在編譯成功后會發(fā)現(xiàn)同級目錄下多了一個名為“HelloDream.class”的文件,這個文件就是字節(jié)碼文件,如圖 1.18所示。
編譯生成的字節(jié)碼文件
圖1.18 命令行編譯后的文件目錄
編程技巧:在進(jìn)行編譯的時候,要寫文件全名加后綴名。javac編譯utf-8編碼的java文件時,容易出現(xiàn)“錯誤: 編碼GBK的不可映射字符”。解決方法是添加encoding 參數(shù):javac -encoding utf-8 WordCount.java,如果還不能解決,將文件保存成ANSI編碼格式。
編譯完成之后,就可以運(yùn)行程序。在Dos命令行窗口接著輸入“java HelloDream”命令,運(yùn)行剛才已經(jīng)編譯好的java文件,運(yùn)行結(jié)果如圖 1.19所示。
注意:在運(yùn)行的時候,輸入的是文件的全名,不加后綴名。
通過前面的學(xué)習(xí),我們知道了一個Java程序需要經(jīng)過編寫、編譯、運(yùn)行3階段,而且細(xì)心的同學(xué)會發(fā)現(xiàn),在編譯的時候我們用的是javac命令,而在運(yùn)行的時候我們用的是java命令,這在一定程度上給大家?guī)砹瞬簧俾闊:迷冢琂DK9之后,Java程序的編譯運(yùn)行進(jìn)行了改動,變得更加簡便,不需要再使用javac命令對java文件進(jìn)行編譯后運(yùn)行,而是直接使用java命令對java文件進(jìn)行編譯運(yùn)行。
接下來,我們將“d:\javaCode\demo01”目錄下編譯后的“HelloDrea.class”字節(jié)碼文件刪除掉,按照簡化后方法重新編譯運(yùn)行HelloDream.java程序,如圖1.20所示。
圖1.19 運(yùn)行Java程序 圖1.20 Java命令編譯運(yùn)行
通過圖1.20可以看到,我們只需要使用java命令就可以直接打印出java文件的輸出結(jié)果。
java文件是高級語言代碼,class文件是低級語言代碼。編譯過程實際上是通過Java編譯器將高級語言的源代碼編譯為低級語言代碼。那么反過來,是否可以通過低級語言代碼進(jìn)行反向工程,獲取其源代碼呢?答案是肯定得,這個過程就叫做反編譯。雖然,機(jī)器語言很難反編譯為源代碼,但是中間代碼是可以進(jìn)行反編譯的,比如用戶可以把javac編譯得到的class文件進(jìn)行反編譯,將其轉(zhuǎn)換為java文件。通過反編譯,我們可以了解別人的代碼內(nèi)容,學(xué)習(xí)別人的代碼的實現(xiàn)思路,還可以通過源代碼查找bug、制作外掛等。
Java中有很多反編譯工具,最常用的有如下幾種:
前面我們學(xué)習(xí)了Java程序的編寫、編譯與運(yùn)行過程,那么Java程序在計算機(jī)中運(yùn)行的底層原理是什么呢?它是如何實現(xiàn)跨平臺的呢?它在運(yùn)行過程中又是如何使用計算機(jī)內(nèi)存的呢?接下來,我們來學(xué)習(xí)Java虛擬機(jī)與垃圾回收機(jī)制。
Java虛擬機(jī)(Java Virtual Machine ,JVM)是運(yùn)行Java程序必不可少的機(jī)制。Oracle的Java虛擬機(jī)規(guī)范給出了JVM的定義:JVM是在一臺真實的機(jī)器上用軟件方式實現(xiàn)的一臺假象機(jī)。虛擬機(jī)的代碼存儲在.class文件中,并且每個.class文件最多包含一個public class類的代碼。
Java程序經(jīng)過編譯器(javac.exe)編譯之后,會產(chǎn)生與平臺無關(guān)的字節(jié)碼文件(即擴(kuò)展名.class的文件)。字節(jié)碼文件本質(zhì)上是一種標(biāo)準(zhǔn)化的可移植的二進(jìn)制格式,它最大的好處是可跨平臺運(yùn)行,也就是常說的“一次編譯,到處運(yùn)行”。字節(jié)碼文件必須交由解釋器來執(zhí)行,與計算機(jī)硬件、操作系統(tǒng)沒有關(guān)系,這個解釋程序就是JVM。換句話說,無論使用哪種操作平臺,只要其含有JVM,就可以運(yùn)行字節(jié)碼文件。事實上,正是有了Java虛擬機(jī)規(guī)范,才使得Java應(yīng)用程序達(dá)到與平臺無關(guān),從而實現(xiàn)可移植性,這也是Java語言風(fēng)靡全球、迅速普及的原因之一。
回顧之前之前學(xué)習(xí)的代碼編譯、運(yùn)行過程,我們可以很容易地理解,JVM實現(xiàn)跨平臺代碼執(zhí)行的過程如圖1.21所示。
圖1.21 JVM執(zhí)行流程圖
最后需要強(qiáng)調(diào)的是,JVM的實現(xiàn)包括字節(jié)碼驗證、解釋器、內(nèi)存垃圾回收等,JVM虛擬機(jī)規(guī)范對運(yùn)行時數(shù)據(jù)區(qū)域的劃分及字節(jié)碼的優(yōu)化并不做嚴(yán)格的限制,它們的實現(xiàn)依不同的平臺而有所不同。
在傳統(tǒng)的程序開發(fā)語言(C、C++及其他語言)中允許動態(tài)分配內(nèi)存,同時需要程序開發(fā)人員負(fù)責(zé)內(nèi)存資源的釋放,如果不釋放內(nèi)存,則隨著程序的不斷運(yùn)行,不斷有新的資源需要分配內(nèi)存,當(dāng)系統(tǒng)中沒有內(nèi)存可用時程序就會崩潰;或者,已動態(tài)分配的堆內(nèi)存由于某種原因未被程序釋放或無法釋放,也會造成系統(tǒng)內(nèi)存的浪費(fèi)。上述這些現(xiàn)象都被稱為“內(nèi)存漏洞”。
垃圾回收(Garbage Collection,GC)就是指釋放垃圾對象所占用的空間,防止內(nèi)存溢出。內(nèi)存處理是讓所有編程人員都很頭疼的地方,如果忘記或者錯誤地回收內(nèi)存會導(dǎo)致程序或系統(tǒng)的不穩(wěn)定甚至崩潰,Java提供的GC功能可以自動監(jiān)測對象并判斷是否超過作用域,從而確定是不是要回收對象。
在Java語言中,引入了垃圾回收機(jī)制,程序開發(fā)者在編寫程序的時候無需考慮內(nèi)存管理問題。Java提供了后臺系統(tǒng)級線程,自動記錄每次內(nèi)存分配的情況,并統(tǒng)計每個內(nèi)存地址的引用次數(shù),不定時地對內(nèi)存中沒有被引用或者長時間沒有使用的對象進(jìn)行回收,這樣回收的內(nèi)存資源可以再次分配其他的內(nèi)存申請。
垃圾回收能自動釋放內(nèi)存空間,使開發(fā)者可以將更多精力投入到軟件核心功能設(shè)計之上,不需要主動去考慮內(nèi)存漏洞的問題,極大地減輕了程序開發(fā)者編程的負(fù)擔(dān)。同時,垃圾回收是Java語言安全性策略的一個重要部份,它能夠有效保護(hù)程序的完整性。當(dāng)然,Java的垃圾回收也有一個潛在的缺點,就是它的開銷影響程序性能,Java虛擬機(jī)必須追蹤運(yùn)行程序中有用的對象,最終釋放沒用的對象,這個過程需要花費(fèi)CPU的時間。
在第1.3節(jié)編寫第一個Java程序時,我們使用的是記事本,這樣編寫程序比較辛苦且效率不高。那么,如何來提高編程效率呢?這就需要選擇一款優(yōu)秀的Java程序開發(fā)工具。
在Java的學(xué)習(xí)和開發(fā)過程中,離不開一款功能強(qiáng)大、使用簡單、高效率的開發(fā)工具。程序開發(fā)工具又叫集成開發(fā)環(huán)境(IDE),是用于提供程序開發(fā)環(huán)境的應(yīng)用程序,通常包括代碼編輯器、編譯器、調(diào)試器和圖形用戶界面等,這類軟件一般集代碼編寫、分析、編譯、調(diào)試為一體,可以極大程度地提高編程效率。目前,最流行的Java集成開發(fā)環(huán)境有Eclipse、InteliJ IDEA、NetBeans、jGRASP、BlueJ等。曾經(jīng),Eclipse是Java IDE中的王者,近些年其風(fēng)頭逐步被InteliJ IDEA所取代。
IntelliJ IDEA簡稱IDEA,是業(yè)界公認(rèn)最好的Java開發(fā)工具之一,特別在智能代碼助手、代碼自動提示、重構(gòu)、JavaEE支持、各類版本工具(git、svn等)、JUnit、CVS整合、代碼分析、 創(chuàng)新的GUI設(shè)計等方面,其功能可以說是超常的。
接下來,我們就來介紹IDEA的下載、安裝與啟動方法(筆者寫稿時使用了IDEA的2021版,讀者可以直接在官網(wǎng)進(jìn)行下載),具體步驟如下:
(1)通過網(wǎng)址( https://www.jetbrains.com/idea/)進(jìn)入官網(wǎng),如圖1.22所示。
圖1.22 IDEA官網(wǎng)下載界面
(2)點擊“Download”進(jìn)行下載,彈出下載界面,如圖 1.23所示。IntelliJ IDEA 提供了兩個版本,即 Ultimate(旗艦版)和 Community(社區(qū)版)。社區(qū)版是免費(fèi)的,但它的功能較少。旗艦版是商業(yè)版,提供了一組出色的工具和特性。
圖1.23 idea2021版下載界面
(3)點擊“Download”后會彈出如圖 1.24所示的注冊界面,讓我們進(jìn)行注冊,不用注冊,這時候已經(jīng)開始下載了。下載好安裝包后將其放在合適的位置,等待安裝即可。
已開始下載
圖1.24 等待下載與注冊界面
(4)雙擊下載好的安裝包,彈出安裝界面,如圖 1.25所示。
(5)點擊“Next>”按鈕選擇安裝目錄,一般選擇默認(rèn)即可,如果C盤空間不足可以選用其他盤符,如圖1.26 所示。
圖1.25 安裝界面 圖1.26 程序安裝目錄界面
(6)點擊“Next >”按鈕后,進(jìn)入安裝配置界面,勾選創(chuàng)建桌面快捷方式,本書使用的是64位操作系統(tǒng),所以勾選 “64bit Launder”(用戶請根據(jù)自己操作系統(tǒng)位數(shù),自行選擇),如圖 1.27所示。
(7)點擊“Next >”按鈕,跳轉(zhuǎn)至開始安裝界面,如圖1.28所示。點擊“Install”按鈕即跳轉(zhuǎn)至等待安裝界面,如圖1.29所示。程序安裝完畢界面如圖1.30所示,點擊“Finish”按鈕即可。
圖1.27 安裝配置界面 圖1.28 開始安裝界面
圖1.29 安裝等待界面 圖1.30 安裝完畢界面
安裝好IDEA之后,接下來就帶領(lǐng)大家體驗使用IDEA進(jìn)行程序開發(fā)的過程,步驟如下:
(1)在桌面上找到IDEA的快捷方式,雙擊圖標(biāo)打開IDEA,進(jìn)入“Welcome to IntelliJ IDEA”界面(IntelliJ IDEA旗艦版是商業(yè)收費(fèi)軟件,非付費(fèi)用戶首次登陸進(jìn)入不到收費(fèi)界面,但是該軟件為學(xué)生提供了人性化的福利,學(xué)生憑個人學(xué)號可以獲得免費(fèi)使用權(quán),具體根據(jù)官方提示操作即可),如圖1.31所示。IDEA界面的默認(rèn)顏色為黑色,默認(rèn)狀態(tài)下進(jìn)入下一步完全可以,如果不喜歡該風(fēng)格,可自行設(shè)置。在圖1.31所示界面選擇左側(cè)“Customize”,切換到如圖1.32所示的界面,再在右側(cè)“Color theme” 下拉選項卡選擇“IntelliJ Light”,背景色即可變?yōu)榱辽鐖D 1.33所示。在圖1.33所示界面選擇左側(cè)“Projects”,即可再次回到歡迎界面,如圖1.34所示。
圖1.31 IDEA歡迎界面 圖1.32 IDEA界面顏色設(shè)置
圖1.33 界面顏色變?yōu)榱辽?圖1.34 調(diào)整顏色后的歡迎界面
(2)在歡迎界面點擊“New Project”按鈕后進(jìn)入“New Project”界面,如圖1.35所示。選擇項目類型和版本號,當(dāng)前選擇Java項目,Project SDK是“15 version 15”。接下來單擊“Next”按鈕,進(jìn)入如圖1.36所示的界面,本界面用來設(shè)置是否使用模板開發(fā),這里不用勾選。
圖1.35 項目類型和版本 圖1.36 是否使用模板
(3)點擊“Next”按鈕進(jìn)入下一步,按照如圖 1.37所示,輸入項目名稱、選擇項目目錄。
點擊選擇項目存放目錄
項目存放的目錄
項目名稱
圖1.37 設(shè)置項目名稱和目錄
(4)單擊“Finish”按鈕,就可以看見我們用IDEA創(chuàng)建的第一個項目,如圖 1.38所示。
圖1.38 創(chuàng)建好的項目
(5)在src目錄下右鍵選擇“New”→“Java Class”,如圖1.39所示。點擊“Java Class”以后在彈窗內(nèi)填入類名,如圖1.40所示。
圖1.39 創(chuàng)建類
創(chuàng)建接口
默認(rèn)創(chuàng)建類
自定義類名,首字母大寫
圖1.40 創(chuàng)建類過程中的彈窗
(6)在類中編寫圖1.41所示的代碼,然后右鍵并點擊“Run 'HelloWorld.main()'”運(yùn)行代碼(或在菜單選擇“Run”→“Run 'HelloWorld'”,或者直接單擊工具欄的
圖標(biāo))。
圖1.41 編寫并運(yùn)行Java代碼
程序運(yùn)行結(jié)果如下:
HelloWorld!
好好學(xué)習(xí)Java!
程序運(yùn)行完成后,在IDEA打印運(yùn)行結(jié)果,通過在IDEA編輯器中編寫代碼,代碼編寫效率、執(zhí)行效率會更高。
注意:.idea目錄用來存放項目的配置信息,包括歷史記錄,版本控制信息等內(nèi)容。
1.1 Java的三大體系分別是______、______、______。
1.2 編譯Java程序需要使用______命令。
1.3 Java代碼編譯后的字節(jié)碼文件擴(kuò)展名為________。
2.1 Java程序未經(jīng)過編譯時的文件的后綴是( )
A..dll B..exe
C..class D..java
2.2 用Java虛擬機(jī)編譯類名為Hello的應(yīng)用程序的正確命令是( )
A.javac Hello.class B.Hello.class
C.java Hello.java D.java Hello
2.3 下列選項中,屬于Java語言特點的一項是( )
A.使用繁瑣 B.編譯執(zhí)行
C.節(jié)省內(nèi)存空間 D.面向?qū)ο?/span>
2.4 Java屬于哪一類語言( )
A.面向機(jī)器的語言 B.面向?qū)ο蟮恼Z言
C.面向過程的語言 D.面向操作系統(tǒng)的語言
3.1 簡述Java語言的特點。
3.2 簡述 JDK的含義和作用。
3.3 簡述什么是JVM。
4.1 編寫程序,在控制臺展示出“歡迎來AAA教育學(xué)習(xí)!”和“有夢想,一起實現(xiàn)!”兩句話。
認(rèn)打包生成的jar是不能夠直接運(yùn)行的,因為帶有main方法的類信息不會添加到manifest中(打開jar文件中的META-INF/MANIFEST.MF文件,將無法看到Main-Class一行)。
用maven打包java程序,當(dāng)執(zhí)行 java -jar 文件時提示 no main manifest attribute。
為了生成可執(zhí)行的jar文件,需要借助插件。
目錄:
# 進(jìn)入你想創(chuàng)建項目的父文件夾
cd /Volumes/RamDisk
# 查看當(dāng)前文件夾
pwd
# 生成項目
docker run -itd --rm --name maven_quick_tmp \
-v "$HOME/.m2/repository":/root/.m2/repository \
-v "$PWD":/usr/src/mymaven \
-w /usr/src/mymaven \
virhuiai/maven_quick:version-aliyun \
mvn archetype:generate \
-DarchetypeArtifactId=maven-archetype-quickstart \
-DinteractiveMode=false \
-DarchetypeVersion=1.4 \
-DgroupId=com.virhuiai.www \
-DartifactId=hello-world \
-DpackageName=com.virhuiai.www \
-DarchetypeVersion=RELEASE
查看下生成的項目結(jié)構(gòu):
tree -C hello-world
其中pom.xml的部分如下:
如果要指定版本號,即將jdk版本替換為1.8:
cd hello-world/
sed -ri -e 's!<maven.compiler.source>1.7</maven.compiler.source>!<maven.compiler.source>1.8</maven.compiler.source>!g' pom.xml
sed -ri -e 's!<maven.compiler.target>1.7</maven.compiler.target>!<maven.compiler.target>1.8</maven.compiler.target>!g' pom.xml
官網(wǎng)地址在:
http://maven.apache.org/plugins/maven-shade-plugin/examples/executable-jar.html
按說明,在pom.xml中添加以下內(nèi)容:
<build>
。。。
<pluginManagement>
。。。
</pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.virhuiai.www.App</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
注意這是直接位于build>plugins下的,不是pluginManagement里的,否則不會有效果()。
現(xiàn)在執(zhí)行mvn clean install:
mvn clean install
其中有句:
Replacing /usr/src/mymaven/hello-world/target/hello-world-1.0-SNAPSHOT.jar with /usr/src/mymaven/hello-world/target/hello-world-1.0-SNAPSHOT-shaded.jar
說明已經(jīng)被替換成帶有Main-Class信息的可運(yùn)行jar。
現(xiàn)在,在項目根目錄中執(zhí)行該jar文件:
root@9275e11b3f0f:/usr/src/mymaven/hello-world# java -jar /usr/src/mymaven/hello-world/target/hello-world-1.0-SNAPSHOT.jar
Hello World!
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>com.virhuiai.www.App</mainClass> <!-- //主程序入口類,可以按住control,單機(jī)定位到該類-->
</manifest>
</archive>
</configuration>
</plugin>
注意這也是直接位于build>plugins下的,不是pluginManagement里的,pluginManagement指定版本號。
、JVM中的類加載器類型
從Java虛擬機(jī)的角度講,只有兩種不同的類加載器:啟動類加載器和其他類加載器。
1.啟動類加載器(Boostrap ClassLoader):這個是由c++實現(xiàn)的,主要負(fù)責(zé)JAVA_HOME/lib目錄下的核心 api 或 -Xbootclasspath 選項指定的jar包裝入工作。
2.其他類加載器:由java實現(xiàn),可以在方法區(qū)找到其Class對象。這里又細(xì)分為幾個加載器
a).擴(kuò)展類加載器(Extension ClassLoader):負(fù)責(zé)用于加載JAVA_HOME/lib/ext目錄中的,或者被-Djava.ext.dirs系統(tǒng)變量指定所指定的路徑中所有類庫(jar),開發(fā)者可以直接使用擴(kuò)展類加載器。java.ext.dirs系統(tǒng)變量所指定的路徑的可以通過System.getProperty("java.ext.dirs")來查看。
b).應(yīng)用程序類加載器(Application ClassLoader):負(fù)責(zé)java -classpath或-Djava.class.path所指的目錄下的類與jar包裝入工作。開發(fā)者可以直接使用這個類加載器。在沒有指定自定義類加載器的情況下,這就是程序的默認(rèn)加載器。
c).自定義類加載器(User ClassLoader):在程序運(yùn)行期間, 通過java.lang.ClassLoader的子類動態(tài)加載class文件, 體現(xiàn)java動態(tài)實時類裝入特性。
這四個類加載器的層級關(guān)系,如下圖所示。
二、為什么要自定義類加載器
三、自定義類加載器
3.1 ClassLoader實現(xiàn)自定義類加載器相關(guān)方法說明
要實現(xiàn)自定義類加載器需要先繼承ClassLoader,ClassLoader類是一個抽象類,負(fù)責(zé)加載classes的對象。自定義ClassLoader中至少需要了解其中的三個的方法: loadClass,findClass,defineClass。
public Class<?> loadClass(String name) throws ClassNotFoundException { return loadClass(name, false); protected Class<?> findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException(name); } protected final Class<?> defineClass(String name, byte[] b, int off, int len) throws ClassFormatError { return defineClass(name, b, off, len, null); }
loadClass:JVM在加載類的時候,都是通過ClassLoader的loadClass()方法來加載class的,loadClass使用雙親委派模式。如果要改變雙親委派模式,可以修改loadClass來改變class的加載方式。雙親委派模式這里就不贅述了。
findClass:ClassLoader通過findClass()方法來加載類。自定義類加載器實現(xiàn)這個方法來加載需要的類,比如指定路徑下的文件,字節(jié)流等。
definedClass:definedClass在findClass中使用,通過調(diào)用傳進(jìn)去一個Class文件的字節(jié)數(shù)組,就可以方法區(qū)生成一個Class對象,也就是findClass實現(xiàn)了類加載的功能了。
貼上一段ClassLoader中l(wèi)oadClass源碼,見見真面目...
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } }
源碼說明...
/** * Loads the class with the specified <a href="#name">binary name</a>. The * default implementation of this method searches for classes in the * following order: * * <ol> * * <li><p> Invoke {@link #findLoadedClass(String)} to check if the class * has already been loaded. </p></li> * * <li><p> Invoke the {@link #loadClass(String) <tt>loadClass</tt>} method * on the parent class loader. If the parent is <tt>null</tt> the class * loader built-in to the virtual machine is used, instead. </p></li> * * <li><p> Invoke the {@link #findClass(String)} method to find the * class. </p></li> * * </ol> * * <p> If the class was found using the above steps, and the * <tt>resolve</tt> flag is true, this method will then invoke the {@link * #resolveClass(Class)} method on the resulting <tt>Class</tt> object. * * <p> Subclasses of <tt>ClassLoader</tt> are encouraged to override {@link * #findClass(String)}, rather than this method. </p> * * <p> Unless overridden, this method synchronizes on the result of * {@link #getClassLoadingLock <tt>getClassLoadingLock</tt>} method * during the entire class loading process. * * @param name * The <a href="#name">binary name</a> of the class * * @param resolve * If <tt>true</tt> then resolve the class * * @return The resulting <tt>Class</tt> object * * @throws ClassNotFoundException * If the class could not be found */
翻譯過來大概是:使用指定的二進(jìn)制名稱來加載類,這個方法的默認(rèn)實現(xiàn)按照以下順序查找類: 調(diào)用findLoadedClass(String)方法檢查這個類是否被加載過 使用父加載器調(diào)用loadClass(String)方法,如果父加載器為Null,類加載器裝載虛擬機(jī)內(nèi)置的加載器調(diào)用findClass(String)方法裝載類, 如果,按照以上的步驟成功的找到對應(yīng)的類,并且該方法接收的resolve參數(shù)的值為true,那么就調(diào)用resolveClass(Class)方法來處理類。 ClassLoader的子類最好覆蓋findClass(String)而不是這個方法。 除非被重寫,這個方法默認(rèn)在整個裝載過程中都是同步的(線程安全的)。
resolveClass:Class載入必須鏈接(link),鏈接指的是把單一的Class加入到有繼承關(guān)系的類樹中。這個方法給Classloader用來鏈接一個類,如果這個類已經(jīng)被鏈接過了,那么這個方法只做一個簡單的返回。否則,這個類將被按照 Java?規(guī)范中的Execution描述進(jìn)行鏈接。
3.2 自定義類加載器實現(xiàn)
按照3.1的說明,繼承ClassLoader后重寫了findClass方法加載指定路徑上的class。先貼上自定義類加載器。
package com.chenerzhu.learning.classloader; import java.nio.file.Files; import java.nio.file.Paths; /** * @author chenerzhu * @create 2018-10-04 10:47 **/ public class MyClassLoader extends ClassLoader { private String path; public MyClassLoader(String path) { this.path = path; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { try { byte[] result = getClass(name); if (result == null) { throw new ClassNotFoundException(); } else { return defineClass(name, result, 0, result.length); } } catch (Exception e) { e.printStackTrace(); } return null; } private byte[] getClass(String name) { try { return Files.readAllBytes(Paths.get(path)); } catch (Exception e) { e.printStackTrace(); } return null; } }
以上就是自定義的類加載器了,實現(xiàn)的功能是加載指定路徑的class。再看看如何使用。
package com.chenerzhu.learning.classloader; import org.junit.Test; /** * Created by chenerzhu on 2018/10/4. */ public class MyClassLoaderTest { @Test public void testClassLoader() throws Exception { MyClassLoader myClassLoader = new MyClassLoader("src/test/resources/bean/Hello.class"); Class clazz = myClassLoader.loadClass("com.chenerzhu.learning.classloader.bean.Hello"); Object obj = clazz.newInstance(); System.out.println(obj); System.out.println(obj.getClass().getClassLoader()); } }
首先通過構(gòu)造方法創(chuàng)建MyClassLoader對象myClassLoader,指定加載src/test/resources/bean/Hello.class路徑的Hello.class(當(dāng)然這里只是個例子,直接指定一個class的路徑了)。然后通過myClassLoader方法loadClass加載Hello的Class對象,最后實例化對象。以下是輸出結(jié)果,看得出來實例化成功了,并且類加載器使用的是MyClassLoader。
在此我向大家推薦一個Java學(xué)習(xí)交流群。交流學(xué)習(xí)群號:874811168 里面會分享一些資深架構(gòu)師錄制的視頻錄像:有Spring,MyBatis,Netty源碼分析,高并發(fā)、高性能、分布式、微服務(wù)架構(gòu)的原理,JVM性能優(yōu)化、分布式架構(gòu)等這些成為架構(gòu)師必備的知識體系。還能領(lǐng)取免費(fèi)的學(xué)習(xí)資源,一起學(xué)習(xí),一起進(jìn)步,目前受益良多。
com.chenerzhu.learning.classloader.bean.Hello@2b2948e2 com.chenerzhu.learning.classloader.MyClassLoader@335eadca
四、類Class卸載
JVM中class和Meta信息存放在PermGen space區(qū)域(JDK1.8之后存放在MateSpace中)。如果加載的class文件很多,那么可能導(dǎo)致元數(shù)據(jù)空間溢出。引起java.lang.OutOfMemory異常。對于有些Class我們可能只需要使用一次,就不再需要了,也可能我們修改了class文件,我們需要重新加載 newclass,那么oldclass就不再需要了。所以需要在JVM中卸載(unload)類Class。
JVM中的Class只有滿足以下三個條件,才能被GC回收,也就是該Class被卸載(unload):
很容易理解,就是要被卸載的類的ClassLoader實例已經(jīng)被GC并且本身不存在任何相關(guān)的引用就可以被卸載了,也就是JVM清除了類在方法區(qū)內(nèi)的二進(jìn)制數(shù)據(jù)。
JVM自帶的類加載器所加載的類,在虛擬機(jī)的生命周期中,會始終引用這些類加載器,而這些類加載器則會始終引用它們所加載的類的Class對象。因此這些Class對象始終是可觸及的,不會被卸載。而用戶自定義的類加載器加載的類是可以被卸載的。雖然滿足以上三個條件Class可以被卸載,但是GC的時機(jī)我們是不可控的,那么同樣的我們對于Class的卸載也是不可控的。
五、JVM自定義類加載器加載指定classPath下的所有class及jar
經(jīng)過以上幾個點的說明,現(xiàn)在可以實現(xiàn)JVM自定義類加載器加載指定classPath下的所有class及jar了。這里沒有限制class和jar的位置,只要是classPath路徑下的都會被加載進(jìn)JVM,而一些web應(yīng)用服務(wù)器加載是有限定的,比如tomcat加載的是每個應(yīng)用classPath+“/classes”加載class,classPath+“/lib”加載jar。以下就是代碼啦...
package com.chenerzhu.learning.classloader; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Enumeration; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.jar.JarEntry; import java.util.jar.JarFile; /** * @author chenerzhu * @create 2018-10-04 12:24 **/ public class ClassPathClassLoader extends ClassLoader{ private static Map<String, byte[]> classMap = new ConcurrentHashMap<>(); private String classPath; public ClassPathClassLoader() { } public ClassPathClassLoader(String classPath) { if (classPath.endsWith(File.separator)) { this.classPath = classPath; } else { this.classPath = classPath + File.separator; } preReadClassFile(); preReadJarFile(); } public static boolean addClass(String className, byte[] byteCode) { if (!classMap.containsKey(className)) { classMap.put(className, byteCode); return true; } return false; } /** * 這里僅僅卸載了myclassLoader的classMap中的class,虛擬機(jī)中的 * Class的卸載是不可控的 * 自定義類的卸載需要MyClassLoader不存在引用等條件 * @param className * @return */ public static boolean unloadClass(String className) { if (classMap.containsKey(className)) { classMap.remove(className); return true; } return false; } /** * 遵守雙親委托規(guī)則 */ @Override protected Class<?> findClass(String name) { try { byte[] result = getClass(name); if (result == null) { throw new ClassNotFoundException(); } else { return defineClass(name, result, 0, result.length); } } catch (Exception e) { e.printStackTrace(); } return null; } private byte[] getClass(String className) { if (classMap.containsKey(className)) { return classMap.get(className); } else { return null; } } private void preReadClassFile() { File[] files = new File(classPath).listFiles(); if (files != null) { for (File file : files) { scanClassFile(file); } } } private void scanClassFile(File file) { if (file.exists()) { if (file.isFile() && file.getName().endsWith(".class")) { try { byte[] byteCode = Files.readAllBytes(Paths.get(file.getAbsolutePath())); String className = file.getAbsolutePath().replace(classPath, "") .replace(File.separator, ".") .replace(".class", ""); addClass(className, byteCode); } catch (IOException e) { e.printStackTrace(); } } else if (file.isDirectory()) { for (File f : file.listFiles()) { scanClassFile(f); } } } } private void preReadJarFile() { File[] files = new File(classPath).listFiles(); if (files != null) { for (File file : files) { scanJarFile(file); } } } private void readJAR(JarFile jar) throws IOException { Enumeration<JarEntry> en = jar.entries(); while (en.hasMoreElements()) { JarEntry je = en.nextElement(); je.getName(); String name = je.getName(); if (name.endsWith(".class")) { //String className = name.replace(File.separator, ".").replace(".class", ""); String className = name.replace("\\", ".") .replace("/", ".") .replace(".class", ""); InputStream input = null; ByteArrayOutputStream baos = null; try { input = jar.getInputStream(je); baos = new ByteArrayOutputStream(); int bufferSize = 1024; byte[] buffer = new byte[bufferSize]; int bytesNumRead = 0; while ((bytesNumRead = input.read(buffer)) != -1) { baos.write(buffer, 0, bytesNumRead); } addClass(className, baos.toByteArray()); } catch (Exception e) { e.printStackTrace(); } finally { if (baos != null) { baos.close(); } if (input != null) { input.close(); } } } } } private void scanJarFile(File file) { if (file.exists()) { if (file.isFile() && file.getName().endsWith(".jar")) { try { readJAR(new JarFile(file)); } catch (IOException e) { e.printStackTrace(); 在此我向大家推薦一個Java學(xué)習(xí)交流群。交流學(xué)習(xí)群號:874811168 可免費(fèi)領(lǐng)取java資料。 } } else if (file.isDirectory()) { for (File f : file.listFiles()) { scanJarFile(f); } } } } public void addJar(String jarPath) throws IOException { File file = new File(jarPath); if (file.exists()) { JarFile jar = new JarFile(file); readJAR(jar); } } }
如何使用的代碼就不貼了,和3.2節(jié)自定義類加載器的使用方式一樣。只是構(gòu)造方法的參數(shù)變成classPath了,篇末有代碼。當(dāng)創(chuàng)建MyClassLoader對象時,會自動添加指定classPath下面的所有class和jar里面的class到classMap中,classMap維護(hù)className和classCode字節(jié)碼的關(guān)系,只是個緩沖作用,避免每次都從文件中讀取。自定義類加載器每次loadClass都會首先在JVM中找是否已經(jīng)加載className的類,如果不存在就會到classMap中取,如果取不到就是加載錯誤了。
六、最后
至此,JVM自定義類加載器加載指定classPath下的所有class及jar已經(jīng)完成了。這篇博文花了兩天才寫完,在寫的過程中有意識地去了解了許多代碼的細(xì)節(jié),收獲也很多。本來最近僅僅是想實現(xiàn)Quartz控制臺頁面任務(wù)添加支持動態(tài)class,結(jié)果不知不覺跑到類加載器的坑了,在此也趁這個機(jī)會總結(jié)一遍。當(dāng)然以上內(nèi)容并不能保證正確,所以希望大家看到錯誤能夠指出,幫助我更正已有的認(rèn)知,共同進(jìn)步。
出處:https://www.cnblogs.com/chenerzhu/p/9741883.html
*請認(rèn)真填寫需求信息,我們會在24小時內(nèi)與您取得聯(lián)系。