介紹
本文總結(jié)了要走Java開發(fā)這條路的成長軌跡。歡迎各位看客留下腳印。
正文開始之前,先申明幾點(diǎn):本文說的“Java開發(fā)”指的是以Java后端為主、少量的前端開發(fā),如果要了解更多前端開發(fā)的內(nèi)容請(qǐng)移步;另外,本文假設(shè)您在學(xué)習(xí)Java之前沒有其他語言的基礎(chǔ)。
這是一個(gè)系列的文章,而且會(huì)長期更新,歡迎各位看客關(guān)注!
前言
這時(shí)候,作為剛剛加入“碼農(nóng)”行列的您,還是有幾點(diǎn)是必須要搞定的:Java語法、行業(yè)代碼規(guī)范(類、方法、變量等的命名規(guī)范)、計(jì)算機(jī)基本的運(yùn)行原理、Java程序運(yùn)行的基本原理。我這里說的“搞定”的更加準(zhǔn)確的含義指的是:要很掌握的很熟練。因?yàn)檫@就像是樓梯的第一個(gè)臺(tái)階、高樓大廈的地基一樣,它直接影響你后續(xù)階段的發(fā)展。
您別小瞧這幾點(diǎn),以為在學(xué)校里就早早地對(duì)Java語法很了解了;不止您在成長,Java本身也在發(fā)展中,所以Java的不同版本之間可能也會(huì)有或大或小、或多或少的差異,忽略這一點(diǎn)的童鞋可能會(huì)被“絆腳石”磕碰的很慘。
程序是運(yùn)行在計(jì)算機(jī)上的,為了以后的發(fā)展,可以說對(duì)計(jì)算機(jī)的原理研究的不管多么深入都是很有必要的。但現(xiàn)在,你至少要對(duì)CPU、內(nèi)存、硬盤、網(wǎng)卡等大部件的性能參數(shù)有所了解。
而Java呢,是需要編譯為class文件(而不是機(jī)器碼),然后在虛擬機(jī)(JVM)上運(yùn)行的。這一點(diǎn),決定了Java是跨平臺(tái)(Linux、Windows等)的,也決定了它可能會(huì)比C語言要運(yùn)行的慢一點(diǎn)。它的內(nèi)存管理機(jī)制,又決定了Java是比較耗內(nèi)存的——雖然所占用的內(nèi)存并不是時(shí)時(shí)刻刻都需要的。
以上三點(diǎn)相對(duì)來說都是比較范范的東西,下面我來說一下對(duì)于初出茅廬的Java程序員們有哪些技能是必須掌握的,換句話說說,掌握了下面的內(nèi)容你就可以輕松應(yīng)對(duì)一些面試了:
封裝、繼承、多態(tài)
學(xué)Java或者其他面向?qū)ο蟮耐瑐?,這三個(gè)特性應(yīng)該都是記得很清楚。它很重要,會(huì)一直貫穿你的Java開發(fā)生涯;但這六個(gè)字過于濃縮、抽象,是很難理解的。其實(shí),我建議作為Java開發(fā)人員的第一年,反而可以忘記這些特性,先是多多動(dòng)手,積累一些項(xiàng)目經(jīng)驗(yàn),積累足夠的業(yè)務(wù)場景,積累足夠的常見問題后,再回過頭來細(xì)細(xì)研究這三個(gè)特性。
方法的重寫的重載
方法的重寫(override),指的是子類重寫(覆蓋)父類的方法。要求方法簽名要保持一致,不能拋出更多的異常(封裝),不能有更加嚴(yán)格的訪問權(quán)限(多態(tài))。
方法的重載(overload),指的是在同一個(gè)類或者子類中來重載一個(gè)方法,兩個(gè)方法之間要求參數(shù)列表不能相同,而其他的要相同。這是為了能夠有多個(gè)過程類似的方法實(shí)現(xiàn)的。
Java接口、類(抽象)的概念
接口可以理解為是一種規(guī)范、標(biāo)準(zhǔn)、協(xié)議,而并不關(guān)心具體實(shí)現(xiàn)過程。接口中的方法只能使用public、abstract來修飾,而屬性只能是用public、static、final來修飾。換句話說,在接口中即使不添加任何修飾符也可以,因?yàn)樗荒芴砑幽切┬揎椃椒ū厝皇莗ublic abstract的,而屬性一定是public static final的。根據(jù)接口中屬性的特點(diǎn),我們可以推斷,在申明屬性變量時(shí)必須給賦初始值,而且不能被修改。
如果有人要問,在接口的方法上添加abstract或者不添加有沒有區(qū)別的話,從上文就可以推斷出——是一樣的。對(duì)應(yīng)的屬性也是這個(gè)道理。
普通類中,所有的方法必須要有方法體——也就是說必須要有實(shí)現(xiàn),同樣的方法不可以使用abstract來修飾。類只允許繼承一個(gè)類,但是可以實(shí)現(xiàn)多個(gè)接口。類A實(shí)現(xiàn)類B,可以理解為類A也是一種B;而類A實(shí)現(xiàn)接口B、C,則可以理解為類A有B和C的特性(特點(diǎn))。
而抽象類,就像是接口和普通類的結(jié)合體,方法既可以有抽象的(abstract)的,也可以沒有。對(duì)屬性則沒有任何限制。另外,由于抽象類中有可能有抽象方法,所以是無法直接進(jìn)行實(shí)例化的。
Java的集合(Collection)體系
這是必須要弄明白的,在日常工作中跟它打交道的次數(shù)太多了。首先是java.util.Collection這個(gè)接口,它是集合的頂層接口,另外要留心它所在的包(package)是java.util。它的子接口包括List、Set,List的實(shí)現(xiàn)類包括:ArrayList、LinkedList、Vector。Set的實(shí)現(xiàn)類包括:HashSet、LinkedHashSet、TreeSet。
ArrayList的內(nèi)部是用動(dòng)態(tài)數(shù)組(普通數(shù)組的容量必須在初始化時(shí)給定)實(shí)現(xiàn)的,根據(jù)數(shù)組下標(biāo)來對(duì)元素進(jìn)行操作,具有訪問快速、修改較慢的特點(diǎn),這些是由于ArrayList的存儲(chǔ)結(jié)構(gòu)導(dǎo)致的。如果調(diào)用無參構(gòu)造函數(shù)的話,會(huì)把內(nèi)部數(shù)組elementData賦值一個(gè)空數(shù)組;如果調(diào)用的是帶有一個(gè)int類型參數(shù)(容量capacity)的構(gòu)造函數(shù)的話,會(huì)創(chuàng)建一個(gè)指定大小的數(shù)組。在調(diào)用add方法時(shí),會(huì)先檢查當(dāng)前數(shù)組是否可以在容納一個(gè)元素(這時(shí)候,保證至少會(huì)有10的容量),如果不夠的話就準(zhǔn)備擴(kuò)容(最大容量為Integer.MAX_VALUE)——先是創(chuàng)建一個(gè)新容量的數(shù)組,然后把之前的元素拷貝過去,最后把要添加的元素加入新的數(shù)組中。
根據(jù)以上ArrayList類的特點(diǎn),我們可以總結(jié)出一些ArrayList的使用技巧:如果您估計(jì)ArrayList中要添加的元素個(gè)數(shù)超過10個(gè)的話,最好調(diào)用有int參數(shù)的構(gòu)造函數(shù)給定一個(gè)初始容量,這樣能有效地避免頻繁擴(kuò)容導(dǎo)致的頻繁申請(qǐng)內(nèi)存(堆),甚至能避免一些不必要的gc事件;ArrayList適合讀取和修改操作較多的場合,而不適合于添加或者刪除較多的場合;ArrayList并沒有實(shí)現(xiàn)了多線程之間的同步,所以是線程不安全的。
可以通過工具類java.util.Collections的sort方法給List排序。
通過Collections的binarySearch來快速地查找List中的元素。
LinkedList<E>實(shí)現(xiàn)的是雙向鏈表(Doubly-linked),另外還實(shí)現(xiàn)了接口java.util.Deque<E>。它有add、addFirst、addLast、addAll等方法,add等同于addLast,set方法可以添加到指定位置。調(diào)用get方法時(shí),它會(huì)從所有的元素中遍歷查找。所以,LinkedList和ArrayList相反,插入、新增速度快,查找速度慢。另外,它也是線程不安全的。
Vector<E>除了實(shí)現(xiàn)了List接口外還實(shí)現(xiàn)了java.util.RandomAccess接口,默認(rèn)的容量為10,內(nèi)部也是用數(shù)組的形式實(shí)現(xiàn)的。它的add以及其他操作元素的方法使用了關(guān)鍵字synchronized,因此是線程安全的,也可以認(rèn)為Vector是線程安全版本的ArrayList。
TreeSet<E>內(nèi)部是利用TreeMap實(shí)現(xiàn)的,調(diào)用add方法時(shí)其實(shí)就是向map中添加一條key,value的值統(tǒng)一都是Object類。
HashSet<E>內(nèi)部是利用HashMap實(shí)現(xiàn)的。add方法實(shí)現(xiàn)過程與TreeSet類似。
Java集合體系中的java.util.Map比較特殊,和Collection不是一類,它本身就是頂層接口。它的實(shí)現(xiàn)類包括:HashMap、Hashtable。
HashMap<K,V>默認(rèn)的構(gòu)造函數(shù)會(huì)初始化一個(gè)容量(應(yīng)該是2的倍數(shù),最大容量為Integer.MAX_VALUE / 2 + 1,或者為1 << 30)為10、因子(load factor)為0.75的集合,之后調(diào)用init方法(默認(rèn)為空實(shí)現(xiàn),是讓子類來擴(kuò)展的)。
HashMap第一次調(diào)用put方法時(shí),內(nèi)部會(huì)調(diào)用方法inflateTable來初始化,所以說如果只是申明了一個(gè)Map對(duì)象后并不會(huì)占用任何空間。從這里可以看到HashMap內(nèi)部也是通過數(shù)組實(shí)現(xiàn)的,數(shù)組的元素類型為HashMap.Entry;而Entry類包含了key、value、下一個(gè)Entry的引用、hash值。而當(dāng)key為null時(shí),則內(nèi)部會(huì)調(diào)用方法putForNullKey,把key為null的Entry會(huì)放到數(shù)組的第一個(gè)。這時(shí)候會(huì)通過hash方法來計(jì)算key的hash值,hash方法會(huì)先調(diào)用key對(duì)象的hashCode,然后再進(jìn)行一些hash運(yùn)算。之后利用hash值和數(shù)組的長度計(jì)算出來一個(gè)數(shù)組下標(biāo)值,如果數(shù)組中的該下標(biāo)(index)中已經(jīng)有了Entry對(duì)象,則從Entry鏈中通過hash查找是否已經(jīng)存在了相同的key,有的話更新;沒有的話,則新增一個(gè)Entry放到數(shù)組的該下標(biāo)中,然后該Entry指向之前的Entry,形成一個(gè)Entry鏈。
從上面對(duì)HashMap的分析,可以得出一些結(jié)論。它內(nèi)部是由多個(gè)元素類型為HashMap.Entry的數(shù)組實(shí)現(xiàn),因此占用的內(nèi)存空間還是比較大的。key可以為null。它從Java1.6開始出現(xiàn),是非線程安全的。
Hashtable實(shí)現(xiàn)了抽象類Dictionary<K,V>。它從Java1.0開始出現(xiàn)。它的初始化過程和HashMap類似,但初始容量(capacity)為11,因子(load factor)為0.75f。
Hashtable的put方法使用關(guān)鍵字(synchronized)保證了線程間的同步,當(dāng)key或者value為空(null)時(shí)會(huì)拋出空指針異常。
TreeMap<K,V>實(shí)現(xiàn)了接口NavigableMap<K,V>。該集合和HashMap實(shí)現(xiàn)的過程類似,只是在調(diào)用put方法時(shí)就會(huì)先對(duì)key進(jìn)行排序。這個(gè)排序就要求key實(shí)現(xiàn)接口Comparator<? super K>,或者是在實(shí)例化TreeMap時(shí)給定一個(gè)Comparator。
NavigableMap<K,V>該接口繼承了接口SortedMap<K,V>,從Java1.6開始出現(xiàn)。
Java流(Stream)體系
Java中的“流”相關(guān)內(nèi)容都在包java.io中,想要系統(tǒng)的了解Java流就要知道有哪些分類。
按照大的類型分為:輸入流(InputStream)和輸出流(OutputStream)。這兩個(gè)頂層的流都是用抽象類來表示的,它們規(guī)定了輸入、輸出流的基本方法,它們是基于字節(jié)(byte)流。
java.lang.Readable和java.lang.Appendable是另一對(duì)Java“流”接口。注意這兩接口在java.io中對(duì)應(yīng)的頂層抽象類分別為:Reader、Writer,它們是針對(duì)字符類型的流。
DataInput和DataOutput是Java“流”中的面向數(shù)據(jù)類型的輸入、輸出流。
所有類型的輸入、輸出流都實(shí)現(xiàn)了接口Closeable,它只有一個(gè)close方法,那這個(gè)close方法應(yīng)該由誰來調(diào)用呢,我這里有個(gè)建議——誰打開的“流”誰來關(guān)閉。
Java異常(Exception)體系
Java的異常體系的根類是java.lang.Throwable,它有兩個(gè)子類java.lang.Error和java.lang.Exception。
Error是比較嚴(yán)重的錯(cuò)誤,基本會(huì)在程序無法繼續(xù)執(zhí)行的情況下拋出,我們無法捕獲更無法處理。
Exception是“編譯時(shí)”異常的根類,它的子類java.lang.RuntimeException是“運(yùn)行時(shí)”異常。所謂編譯時(shí)指的是在編譯Java源文件時(shí)拋出的異常,必須在處理(捕獲或者拋出)后源文件才可以正常編譯;所謂運(yùn)行時(shí)異常,指的是在Java程序運(yùn)行過程中有“可能”拋出的異常。
除了Error之外,編譯時(shí)異常也是比較嚴(yán)重的異常情況,所以必須顯示地處理。而運(yùn)行時(shí)異常通常不嚴(yán)重,即遇到了這種異常情況程序也還是可以繼續(xù)的。
Java多線程(Thread)
實(shí)現(xiàn)一個(gè)多線程類,可以實(shí)現(xiàn)接口java.lang.Runnable或者類java.lang.Thread。這里要注意的是類Thread也實(shí)現(xiàn)了接口Runnable。啟動(dòng)一個(gè)線程要調(diào)用start方法,如下例子所示:
SuRenPi Source code


package test;/** * @author suren * @date 2016年6月29日 下午1:58:37 */public class Test{ public static void main(String[] args) throws Exception { new ThreadTestA().start(); new Thread(new ThreadTestB()).start(); } static class ThreadTestA extends Thread { @Override public void run() { System.out.println("from thread test a."); } } static class ThreadTestB implements Runnable { @Override public void run() { System.out.println("from thread test b."); } }}
線程間同步,可以在類或者方法上使用關(guān)鍵字synchronized。
另外,還有個(gè)線程相關(guān)的知識(shí)點(diǎn)。使用Thread.sleep和Object.wait相比較,前者會(huì)一直占用CPU,而后者會(huì)放棄CPU的控制權(quán)。
有關(guān)Java多線程的更多內(nèi)容請(qǐng)搜索“Java多線程”。
設(shè)計(jì)模式“單例”
好多人都在談?wù)摗霸O(shè)計(jì)模式”,還有一些人可以說出很多種設(shè)計(jì)模式,例如:單例模式、工廠模式、觀察者模式等等。但對(duì)于剛剛進(jìn)入這個(gè)行業(yè)的人來說,我不認(rèn)為記住這么多的名稱有任何用處,而且在實(shí)際項(xiàng)目中也不會(huì)有實(shí)踐的機(jī)會(huì)。學(xué)習(xí)設(shè)計(jì)模式這是一件長期的事情,而且在有一定項(xiàng)目編程經(jīng)驗(yàn)以后再來學(xué)習(xí)會(huì)更有效果。
我在這里先介紹一個(gè)很容易理解的設(shè)計(jì)模式——單例模式。所謂單例,指的是在虛擬機(jī)(JVM)中某個(gè)類只有一份實(shí)例。通常情況下,單例的實(shí)現(xiàn)有幾個(gè)需要注意的地方:構(gòu)造函數(shù)私有化、提供一個(gè)靜態(tài)方法用于獲取實(shí)例對(duì)象、考慮多線程情況。
反射
在Java中所有的都是類,而所有java.lang.Object類則是所有類的父類——即使沒有“顯示地”繼承。而JDK中有些類是用來描述“類本身”信息的,它們大多數(shù)在包java.lang.reflect中,例如:Method、Field、Constructor、Member、Type、Modifier等。
而我這里提到的反射就是JDK提供的用于獲取“類本身”信息的API,換句話說:通過反射技術(shù)可以動(dòng)態(tài)第獲取甚至修改“類本身”的信息。如果你對(duì)C語言有點(diǎn)了解的話,這一點(diǎn)可以和C語言的指針來做對(duì)比理解。
那么反射技術(shù)通常都用在什么場合呢?利用反射可以做到很多常規(guī)程序無法做到的事情,例如代理,而在框架設(shè)計(jì)中經(jīng)常會(huì)用到反射技術(shù)。
Java用戶界面編程Swing
JDK中提供的用于編程GUI(用戶界面)程序的是Swing框架。這里的API都是在java.awt和javax.swing中,我們可以認(rèn)為javax.swing包中的API是對(duì)java.awt中API的升級(jí)或者改進(jìn)。例如,按鈕的實(shí)現(xiàn)類Button就有兩份,分別是java.awt.Button和javax.swing.JButton。從包的分布和類名上,我們可以看到區(qū)別,在javax.swing包中的組件類基本都是大寫字母J開頭的。這里,我推薦使用javax.swing包中的API。
Java是跨平臺(tái)的,由此提供的Swing框架也是在所有的平臺(tái)上運(yùn)行時(shí)可以保持一致的風(fēng)格。但由此引發(fā)的一個(gè)問題是:利用Swing編寫的GUI程序(C/S架構(gòu))相對(duì)比較丑陋一點(diǎn),導(dǎo)致在企業(yè)開發(fā)中用的相對(duì)比較少。但是,利用Swing來開發(fā)一些小的工具還是完全可以的。
這里我介紹一些GUI程序原理性的內(nèi)容,這樣你就算沒寫過也可以多少有點(diǎn)了解。所有的GUI程序的底層都是在調(diào)動(dòng)API在屏幕上“繪制圖形”,因此呢,可以理解GUI程序中會(huì)有一個(gè)單獨(dú)的線程來做“繪制”工作,而且這個(gè)線程就是主線程。然后,我們繼續(xù)思考,如果一個(gè)界面中的組件(元素)過多的話,就會(huì)導(dǎo)致“繪制”線程占用的時(shí)間比較多,使用起來顯得比較“卡”。
既然上面我已經(jīng)提到了線程,那么在程序中讀取、處理數(shù)據(jù)時(shí)占用的時(shí)間比較長的話,就會(huì)導(dǎo)致界面更加“卡”。為了解決這個(gè)問題,我們通常都會(huì)選擇新開一個(gè)線程來處理占用時(shí)間比較長的任務(wù)。到此為止呢,我們至少已經(jīng)有了兩個(gè)線程,那么在讀取、處理數(shù)據(jù)的線程中要修改界面元素的話,就必然涉及到了線程間同步的問題——我們不可以在新的線程中修改界面元素,否則會(huì)發(fā)生數(shù)據(jù)不同步的問題。
Java網(wǎng)絡(luò)編程
Java網(wǎng)絡(luò)編程是很大的一塊內(nèi)容,現(xiàn)在呢,您只需要了解Java網(wǎng)絡(luò)編程中都有哪些接口(功能),以及簡單了解網(wǎng)絡(luò)協(xié)議即可。
TCP/IP協(xié)議棧分為應(yīng)用層、傳輸層、鏈路層等。Java提供了傳輸層協(xié)議的網(wǎng)絡(luò)編程接口,也就是可以對(duì)TCP、UDP協(xié)議數(shù)據(jù)包進(jìn)行操作。而應(yīng)用層的網(wǎng)絡(luò)協(xié)議,例如:FTP、HTTP、SFTP、SNAP等都需要利用第三方的庫了。HttpClient即是Apache的一個(gè)子項(xiàng)目,用于操作HTTP協(xié)議。
TCP協(xié)議是可靠的協(xié)議,它能保證數(shù)據(jù)包完整地在網(wǎng)絡(luò)中進(jìn)行傳輸,而UDP協(xié)議則不能保證這一點(diǎn),但UDP傳輸?shù)乃俣认鄬?duì)比較快。據(jù)此特點(diǎn),UDP經(jīng)常用在需要傳輸大量數(shù)據(jù),但對(duì)數(shù)據(jù)完整性要求不是很高的場合,例如視頻傳輸?shù)取A硗?,TCP在發(fā)送數(shù)據(jù)包時(shí),必須要求對(duì)方正在簡體你指定的端口,否則無法發(fā)送;而UDP協(xié)議則不需要對(duì)方一定是正在監(jiān)聽中,它只管發(fā)送,不關(guān)心對(duì)方是否已經(jīng)接收了。
JSP原理
JSP(Java Server Pages)作為常用的一種動(dòng)態(tài)頁面技術(shù),有幾個(gè)很關(guān)鍵的地方是必須理解清楚、正確的,這也是作為一個(gè)合格、稱職、專業(yè)的程序員所應(yīng)該了解的。
首先,從JSP的字面上來說,它一定是服務(wù)器端(后端)的技術(shù),而不是前端技術(shù),不可以和HTML混為一談。和其他的動(dòng)態(tài)頁面技術(shù)不同,它是需要編譯的——當(dāng)它被訪問時(shí),將會(huì)由JSP引擎編譯(class字節(jié)碼文件);如果是在Tomcat中運(yùn)行的話,編譯后的字節(jié)碼文件會(huì)保存在目錄work/Catalina/localhost/${webroot}/org/apache/jsp中。
那JSP編譯完的class文件是什么呢?從它所繼承(實(shí)現(xiàn))的類上看,就能一目了然了。它繼承了類org.apache.jasper.runtime.HttpJspBase,實(shí)現(xiàn)了接口org.apache.jasper.runtime.JspSourceDependent。
而HttpJspBase是個(gè)抽象類,它繼承了抽象類javax.servlet.http.HttpServlet,實(shí)現(xiàn)了接口javax.servlet.jsp.HttpJspPage。
接口JspSourceDependent是為了能夠跟蹤JSP源碼文件的依賴關(guān)系,從而能夠編譯過期的JSP文件。
jQuery常用方法
這里至少應(yīng)該知道如何使用jQuery定位元素、設(shè)置(獲?。┲?、ajax請(qǐng)求。
jQuery有類(class)選擇器、名稱(tagname)選擇器、ID選擇器、屬性(attribute)選擇器、派生選擇器等。
JavaScript基本概念
各種瀏覽器的調(diào)試技巧
本站有一篇《JavaScript調(diào)試介紹》您可以搜索一下,介紹了各種主流瀏覽器的JavaScript調(diào)試方法和技巧。
集成開發(fā)工具Eclipse的熟練使用
本站有一篇《Eclipse使用技巧》您可以搜索一下,介紹了Eclipse的一些使用技巧。
應(yīng)用服務(wù)器配置
本章有一篇《玩轉(zhuǎn)Tomcat》您可以搜索一下,介紹了Tomcat所有常用的配置方法。
數(shù)據(jù)庫
除了要會(huì)使用數(shù)據(jù)庫的帶有界面的客戶端(例如:Navicat等)連接,還需要知道如何通過命令行來連接,例如:mysql -uroot -proot。
創(chuàng)建數(shù)據(jù)庫(database)、表(table)的SQL應(yīng)該會(huì)寫,簡單的查詢語句更是必須的。
Struts2框架
Hibernate框架
本站有幾篇有關(guān)Hibernate的專題介紹,大家可以搜索一下《Hibernate注解類介紹》、《Hibernate常見異常》。
Spring框架
本系列文章
Java開發(fā)成長之路第一年
Java開發(fā)成長之路第二年
Java開發(fā)成長之路第三年
Java開發(fā)成長之路第四年
Java開發(fā)成長之路第五年
Java開發(fā)成長之路第六年
Java開發(fā)成長之路第七年
