head first java讀書筆記
1. 基本資訊
- 頁數:689
- 閱讀起止日期:20170104-20170215
2. 標籤
- Java入門
3. 價值
- 8分
4. 主題
使用物件導向的思路介紹Java的基礎知識,從物件的基本概念、變數、方法,到函式庫,整合與多型,靜態,再到GUI,序列化,網路,資料結構,最後介紹釋出和遠端呼叫。
5. 綱要
- Java的基本介紹-第1章
- 物件的基本介紹(變數與方法)-第2-5章
- 函式庫-第6章
- 物件的深入-繼承、多型、建構函式-第7-9章
- 靜態的變數或方法-第10章
- 異常處理-第11章
- GUI-第12-13章
- 序列化與IO-第14章
- 網路與執行緒-第15章
- 常用資料結構-第16章
- 包與釋出程式-第17章
- 遠端過程呼叫、servlets等-第18章
- 附錄
6. 點評
本書最大的啟發是建立物件導向的基本思想,萬物皆在物件中,到底是如何組成和實現的。
7. 摘錄
7.1 Java的基本介紹
- Java程式包括:原始碼、編譯器、輸出(class檔案)、JVM;
最常用的java
public class ClassName { public static void main(String[] args){ System.out.println("Hello World!"); } }
- Java不能像C一樣使用整型做測試條件
(int)(Math.random()*length)
來出現隨機數;
7.2 物件的基本介紹
- 物件本身已知的事物被稱為例項變數,物件可以執行的動作被稱為方法;
- 類是物件的藍圖;
- main的兩種用途:測試真正的類,啟動你的java應用程式;
- Java的程式在執行期是一組會互相交談的物件;
- 變數有兩種型別primitive主資料型別和引用;
- primitive主資料型別:boolean, char(0~65535), byte(-128~127), short(-32768~32767), int, long, float, double。
- 沒有物件變數,只有引用到物件的變數;引用變數更像是物件的遙控器;
- 沒有引用到任何物件的引用變數的值為null;
- 如果堆上的物件沒有任何引用變數,便會被回收;
- 陣列就像杯架,陣列變數就是陣列物件的遙控器;
- 將例項變數標記為private,將getter和setter標記為public;
- 例項變數永遠都會有預設值,引用變數的預設值是null;區域性變數沒有預設值。
- 程式設計的方法:找出類應該做的事情,列出例項變數和方法,編寫偽碼,編寫測試用的程式,實現類,測試方法,排錯或重新設計。
- 偽碼描述要做什麼事情,而不是如何做。
- Interger.parseInt()只會在所給的String為數字時有用。
7.3 API
- ArrayList
listName = new ArrayList (); - Java的API中,類是包含在包中的。使用時,必須import 類全名或直接打出去全名。除非是來自於java.lang這個包中,比如System, String, Math等。
7.4 物件深入
- extends表示繼承,繼承搜尋方法時,會從底層向祖先搜尋;
- 繼承是是一個,例項變數是有一個;
- public會被繼承,private不會被繼承。
- IS-A是單方向的;
- 繼承的意義有二:避免了重複的程式碼;定義出共同的協議;
- 多型為宣告父類的物件引用指向子類的物件;
- 通過多型,可以寫出即使引進新型子類時,也不必修改的程式;
- 繼承雖然沒有層次的限制,但一般不會超過2層;
- 標識出final的類可以確保方法都是自己需要的版本,final也可以防止特定的方法被覆蓋,只要加在方法前;
- 覆蓋父類方法需要滿足兩個條件:引數必須要一樣,且返回型別要相容;不能降低方法的存取許可權;
- abstract表示抽象,抽象類不會被初始化;
- abstract可以放在方法前,直接以分號結尾。抽象方法只能放在抽象類中,所有的抽象方法在子類中必須被實現;
- Java中,所有的類都繼承自object。object有equals,getClass,hashCode,toString等方法。
- 介面是為了解決多重繼承的問題出現的,它沒有例項變數。像是一個100%的純抽象類,使用interface定義,所有的方法都是抽象的。其他類用implements來實現介面的方法。類是可以實現多個藉口的。
- 如果新的類無法對其他類通過IS-A測試,就設計不繼承的類;
- 只有在需要某類的特殊化版本時,已覆蓋或增加新的方法來繼承現有的類;
- 當你需要定義一群子類的模板,又不想初始化此模板時,使用抽象類;
- 如果定義出類可以扮演的角色,使用介面;
- 使用super關鍵字執行父類的方法;
- 抽象類可以帶有抽象和非抽象的方法;
- 方法呼叫和區域性變數在棧空間,所有的物件在堆空間;
- 建構函式沒有型別,也不會被繼承。通常一定要有沒有引數的建構函式。如果有了一個有引數的建構函式,則沒引數的建構函式必須自己寫才會有,否則建立會出錯。
- 通常狀況下,類的建構函式是public。
- 建構函式的呼叫鏈是自頂向下的,即先執行父類,再執行子類。
- 子類的建構函式如果想呼叫父類,使用super();通常狀況下,super會被編譯器預設呼叫沒有引數的版本,顯示呼叫的話,必須要把super();放到第一行。
- 可以使用this(args)呼叫過載版的其他的建構函式,例如無引數的建構函式,加個預設值呼叫有引數的建構函式。this(Color.Red)
7.5 靜態的變數或方法
- 靜態方法不需要建立例項變數就可以呼叫,像Math.abs(-1);
- 如果類只有靜態方法,即要限制非抽象類初始化,可以將建構函式設定為private;
- 靜態方法不能呼叫非靜態的變數或方法,例如在main中呼叫;但可以呼叫靜態變數或方法。
- 類中靜態變數的值,對所有例項都相同,均是共享的;即例項變數每個例項一個,靜態變數每個類一個。
public static final double PI=3.1415
public表示可供各方讀取,static表示不用建立例項即可使用,final表示不變;靜態final變數預設取名大寫,且必須初始化。- final的變數代表不能改變,final的方法不能被覆蓋,final的類不能被繼承;
- 使用類似ArrayList
時,要用到對主型別資料的封裝。因為ArrayList設計成只接受類和物件。Java5.0之前,int不能直接加入ArrayList,5.0之後可以了,因為加入了autoboxing。Integer iWarp = new Integer(i); int unWrapped = iWrap.intValue();利用上述解包裝; - 主資料型別的靜態方法:Integer.parseInt("2"), new Boolean("true").booleanValue();Double.toString(d);
- 格式化輸出String.format("%,d",1000);
- 日期的格式化輸出:%tc 完整的日期和時間,%tr是時間,%tA %tB %td 分別是星期,月份和日期。如果不想重複輸入引數,可以使用String.format("%tA, %<tB %<td", today);
java.util.Calendar物件來操作日期
Calendar cal = Calendar.getInstance(); cal.set(2017,1,6,15,40); cal.getTimeInMillis(); cal.HOUR_OF_DAY cal.add(cal.DATE, 30);//月份滾動 cal.roll(cal.DATE, 30);//月份不動 cal.set(cal.DATE, 1); //重要的方法 add(int field, int amount) get(int field) getInstance() getTimeInMillis() roll(int field, int amount) set(int field, int amount) set(year,month,day,hour,minute) setTimeInMillis(long millis) //關鍵欄位 DATE / DAY_OF_MONTH HOUR / HOUR_OF_DAY MILLISECOND MINUTE MONTH YEAR ZONE_OFFSET
import static java.lang.System.out;靜態的import可以到方法,然後直接呼叫out即可,不建議使用;
7.6 異常處理
- Java通過throws語句來告訴你所有的異常行為;把有風險的程式放在try塊中,用catch塊擺放異常的處理程式;異常時Exception型別的物件;
會丟擲異常的方法必須要宣告它有可能會這麼做。方法可以抓住其他異常,異常總會丟回給呼叫方;
public void takeRisk() throws BadException{ if (abandonAllHope){ throw new BadException(); } } public void crossFingers(){ try{ anObject.takeRisk(); }catch (BadException ex){ System.out.println("Aaargh"); ex.printStackTrace(); } }
- try/catch塊用來處理真正的異常,而不是程式的邏輯錯誤。
- 開發與測試期間發生RuntimeException是不會受編譯器關於是否宣告它會丟擲RuntimeException的檢查的,也不會管呼叫方是否認識到該異常;
- 無論成功或者失敗都要執行的放在finally中;即使try和catch都有return也一樣。
- throws可以丟擲多個異常。
throws Exception1, Exception2
;catch也可以用類似switch語句一樣來分別處理,但是要從小到大; - 異常也可以是多型的,可以用所丟擲的異常父型來catch異常。即可以用Exception ex來catch異常,但是不推薦這麼做;
duck異常表示,當呼叫有異常的方法,也宣告會丟擲異常時,可以不把此方法的呼叫放在try/catch塊中,但是不推薦這麼做。
7.7 GUI相關
- GUI的流程是建立frame,建立widget,加在widget,顯示出來。
監聽和事件源之間的溝通通過程式程式碼呼叫
button.addActionListener(this)
來向按鈕註冊。按鈕會在事件發生時,呼叫註冊該介面的方法actionPerformed(theEvent)
;import javax.swing.*; import java.awt.event.*; public class SimpleGui1B implements ActionListener{ JButton button; public static void main(String[] args){ SimpleGui1B gui = new SimpleGui1B(); gui.go(); } public void go(){ JFrame frame = new JFrame(); button = new JButton("click me"); button.addAcitonListener(this); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.getContentPane().add(button); frame.setSize(300,300); frame.setVisible(true); } public void actionPerformed(ActionEvent event){ button.setText("I've been clicked!"); } }
- frame預設有東西南北中五個位置來放置widget;
當有多個widget需要監聽事件時,用內部類來解決;
public void go(){ //... labelButton.addActionListener(new LaberlListener()); colorButton.addActionListener(new ColorListener()); label = new JLabel("I'm a label") //... } class LaberListener implements ActionListener { public void actionPerformed(ActionEvent event){ labnel.setText("Ouch!") } }
- 不同的事件有不同的對應回撥函式,ActionListener對應的回撥函式是actionPerformed,通過addActionListener新增。
每個背景元件都可以有自定義規則的佈局管理器。BorderLayout表示五個區域,是frame預設的,FlowLayout表示從左至右,有必要時換行,是panel預設的;BoxLayout以垂直排列;
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); panel.add(button); frame.getContentPane().add(BorderLayout.North, panel);
JTextField文字框元件,JTextArea可滾動的文字框元件,JCheckBox元件,JList元件均為常用元件。
7.8 序列化與IO
- 儲存物件的狀態可以有兩種辦法,序列化(自己的Java程式讀),純文字檔案(公共格式,其他程式可讀)。
序列化寫入的步驟:建立FileOutputStream對應一個檔案xxx.ser,有FileStream建立ObjectOutputStream,由ObjectOutputStream寫入object,關閉ObjectOutputStream。其中需要注意,能夠寫入ObjectOutputStream的object必須implements Serializable,但是不需要實現任何方法。
FileOutputStream fileStream = new FileOutputStream("MyGame.ser"); ObjectOutputStream os = new ObjectOutputStream(fileStream); os.writeObject(characterOne); os.close();
- 當一個類可以序列化時,必須所有的例項變數都能被序列化,如果某個變數不需要序列化,需要標記transient,例如
transient String currentID;
解序列化步驟,建立FileInputStream讀取一個檔案,建立ObjectInputStream,讀取物件,轉換物件型別,關閉。
FileInputStream fileStream = new FileInputStream("MyGame.ser"); ObjectInputStream os = new ObjectInputStream(fileStream); Object one = os.readObject(); GameCharacter elf = (GameCharacter) one; os.close()
讀寫文字檔案。注意可以使用緩衝區的方法來減少磁碟IO。使用writer.flush()強制緩衝區內容寫入磁碟。
import java.io.*; class WriteAFile{ public static void main(String[] args){ try { FileWriter writer = new FileWriter("Foo.txt"); writer.write("hello, foo!"); //BufferedWriter bWriter = new BufferedWriter(writer); //bWriter.flush(); writer.close(); File myFile = new File("Foo.txt"); FileReader fileReader = new FileReader(myFile); BufferedReader reader = new BufferedReader(fileReader); String line = null; while((line = reader.readLine()) != null){ System.out.println(line); } reader.close(); } catch(IOException ex){ ex.printStackTrace(); } } }
java.io.File類的操作
//建立出File物件 File f = new File("MyCode.txt"); //建目錄 File dir = new File("Chapter_8"); dir.mkdir() //列出目錄內容 if (dir.isDirectory()){ String[] dirContents = dit.list(); } //取得絕對路徑 dit.getAbsolutePath(); //刪除檔案 boolean isDeleted = f.delete()
每個物件被序列化的同時,都會帶上一個類的版本的識別ID,即serialVersionUID。因此要注意版本。
7.9 網路與執行緒
網路接收訊息的步驟:建立socket連線,輸入到底層的InputStreamReader上,轉換到緩衝區字元BufferedReader,讀取資料;
Socket chatSocket = new Socket("127.0.0.1", 5000); InputStreamReader stream = new InputStreamReader(chatSocket.getInputStream()); BufferedReader reader = new BufferedReader(stream); String message = reader.readline(); reader.close();
寫訊息的流程是:建立socket,getOutputSream並建立PrintWriter,寫入資料;
Socket chatSocket = new Socket("127.0.0.1", 5000); PrintWriter writer = new PringWriter(chatSocket.getOutputStream()); writer.println("haha"); writer.close();
伺服器端建立服務的流程為:建立ServerSocket,等待客戶端連線,客戶連線後,呼叫accept方法建立新的socket。
ServerSocket serverSock = new ServerSocket(5000); while(true){ Socket sock = serverSock.accept(); PrintWriter writer = new PrintWriter(sock.getOutputStream()); writer.println("haha"); writer.close(); }
- Thread類用於建立執行緒,它有void join();void start();static void sleep();等方法。
- 啟動新執行緒的流程是:建立Runnable物件,建立Thread物件,並賦值Runnable任務;啟動Thread。
Runnable是一個介面,它只有一個方法public void run()。它就是執行緒要執行的工作。
public class MyRunnable implements Runnable { public void run(){ go(); } public void go(){ System.out.println("top o' the stack"); } } class ThreadTestDrive{ public static void main(String[] args){ Runnable threadJob = new MyRunnable(); Thread myThread = new Thread(threadJob); myThread.start(); System.out.println("back in main"); } }
- 新建執行緒執行緒有新建(new),可執行(start),執行中,三個狀態,可執行和執行中兩個狀態是來回切換的,由JVM排程決定,是隨機不可控的。最多隻能靠sleep來影響最小保證時間。
- 執行緒不可重複啟動。
- 執行緒的名字可以通過myThread.setName("Solon's Thread")來設定,並可通過呼叫Thread.currentThread().getName()來獲取。
執行緒會產生併發性問題(concurrency),併發性問題會引發競爭狀態(race condition),競爭狀態會引發資料損毀。舉個例子,兩個執行緒共用一個餘額,每次用錢時,先檢查餘額,再扣錢。這樣會由於競爭原因出現負數。所以需要一把鎖,來保證一個方法一次只能被一個執行緒呼叫,即用synchronized關鍵字。
private synchronized void makeWithDrawa(int amount){ //... }
- 要注意鎖是鎖在物件上的,只有物件包含同步化方法時才起作用。物件就算有多個同步化方法,也還是隻有一個鎖。
同步化可以只修飾幾行,這樣可以減小原子操作的範圍,提高效率。
private void makeWithDrawa(int amount){ //... synchronized(this){ //... } }
- 兩個執行緒和兩個物件就可以引起死鎖,各自佔有一個資源,又需要呼叫彼此的資源。
靜態的方法是執行在類上的,當要對靜態的方法做同步化是,會使用類本身的鎖。
7.10 常用資料結構與泛型
- TreeSet一有序狀態保持並可防止重複;
- HashMap-KV來儲存;
- LinkedList-針對經常插入和刪除的集合;沒有ArrayList常用;
- HashSet-防重複的集合,快速查詢;
- LinkedHashMap-類似HashMap,但可記住元素的插入順序,可按照存取順序來排序;
- 可用java.util下的Collections.sort(List list)來給ArrayList排序,此函式會直接改變list的順序。
- 不用泛型時,任何object物件都可以加入ArrayList,造成混亂。使用泛型,只有任一單一型別可加入;
泛型有三件事是重要的:
new ArrayList<Song>(); //建立例項 ArrayList<Song> songList = new ArrayList<song>(); //宣告指定泛型型別的變數 void foo(ArrayList<Song> list); //宣告或呼叫指定泛型的方法
在說明檔案中,一般用E表示指定型別;
public class ArrayList<E> extends AbstractList<E> implements List<E> ...{ public boolean add(E o) }
運用泛型的方法可以使用未定義在類宣告的型別引數。
public <T extends Animal> void takeThing(ArrayList<T> list) //與後續的萬用字元相同 public void takeThing(ArrayList<? extends Animal>) //與下面不一樣,下面的只能使用Animal,不能使用其子類 public void takeThing(ArrayList<Animal> list)
- 以泛型的觀點來說extends可以代表extend或implement,表示是一個。
- 如果要對自定義類實現排序,有兩種方法,一是實現該類的Comparable介面,這個介面有compareTo方法。可以呼叫已有的型別來輔助實現該方法;二是增加Comparator類的引數來比較。
- 可以呼叫HashSet的addAll方法,來生成ArrayList對應的佇列;
同樣如果要使用HashSet去除重複,針對自定義類,要覆蓋equals方法和hashCode方法。它的比較過程是先比較hashCode(),如果相同,再比較equals。
class Song implements Comparable<Song>{ ... public boolean equals(Object aSong){ Song s = (Song)aSong; return getTitle().equals(s.getTitle()); } public int hashCode(){ return title.hashCode(); } }
HashMap適合用KV場景儲存資料
HashMap<String, Integer> scores = new HashMap<String, Integer>(); scores.put("Bert", 43); System.out.println(scores.get("Bert"));
泛型引數和陣列引數的區別
public void takeAnimals1(ArrayList<Animal> animals) public void takeAnimals2(Animal[] animals) //takeAnimals1(new ArrayList<Dog> dogs)會出錯,編譯錯誤 //takeAnimals2(new Dog[] dogs)不會出錯 //前者可以防止在函式中進行Dog的特有相關操作,後者在執行期如果執行相關操作,會丟擲異常
為了解決上述前者的問題,可以使用萬用字元
public void takeAnimals3(ArrayList<? extends Animal> animals) //為了防止此時,將ArrayList<Dog>中加入Cat,當用萬用字元時,不能對佇列做加入操作
7.11 包與釋出程式
可以使用
-d class_path
來將原始碼與類檔案相分離,實現對原始碼的保護;javac -d ../classes *.java
jar類似tar命令,它有自己的規則。首先要確定所有的類檔案都在classes目錄下,其次要有manifest.txt檔案來描述哪個類帶有main()方法,最後執行命令打jar包
cat manifest.txt Main-Class:MyApp cd MiniProject/classes jar -cvmf manifest.txt app1.jar *.class
- 如果jar包指定了manifest,則可以執行
java -jar app1.jar
。 - 除非類是包的一部分,否則Java虛擬機器不會深入其他目錄去找。
- 用包防止類名衝突,可以把類放到各個包裡。就像java.util.ArrayList。
而為了防止包命名衝突,一般反向使用domain作為包名稱。
com.headfirstbooks.Book com.headfirstjava.projects.Chart
- 選定包名稱後,需要再類的原始碼的第一行將類加入包中
package com.headfirstjava;
。然後要設定對應的目錄結構。 - 一般Java程式的路徑為專案根目錄下有source和classes兩個檔案,source目錄下為包路徑和原始碼,這樣用-d ../classes編譯後,-d會在classes目錄下建立對應的目錄和class檔案。
- 按照上述約定時,manifest檔案也放在class目錄下,一般為
Main-Class:com.headfirstjava.PackageExcise
。 在classes目錄下執行jar語句打包,這裡只要指定com路徑就行。
jar -cvmf manifest.txt packEx.jar com
- 使用
jar -xf packEx.jar
解壓後會發現有META-INF
目錄,其下有MANIFEST.MF
檔案,即為寫入的對應檔案。 - JWS即Java Web Start,使用者能通過網頁上的某個連線來啟動Java程式,一旦程式下載後,下次就能獨立於瀏覽器來執行,它是通過網路來發布程式的一種手段。jnlp檔案是個描述應用程式可執行JAR檔案的XML檔案。
製作可執行jar程式;編寫jnlp檔案;兩者均放入Web伺服器;Web伺服器設定新型別
application/x-java-jnlp-file
;設定網頁連結到jnlp檔案<a href="MyApp.jnlp">Launch My App</a>
7.12 遠端過程呼叫、servlets等
- 遠端過程呼叫最早是為了利用Server端的強大處理能力。RMI全稱Remote Method Invocation;
- RMI需要伺服器與伺服器helper和客戶端與客戶端helper。helper假裝成服務,但其實只是真實服務的代理;
- 使用RMI時要確定協議,兩端都是Java可使用JRMP協議,IIOP可以呼叫Java物件或其他型別的遠端方法。
- 在RMI中客戶端的helper稱為stub,客戶端的helper成為skeleton
- 建立遠端服務的流程是建立介面(extends Remote),實現類(extends UnicastRemoteObject impements MyRemote),rmic產生stub與skeleton(
rmic MyRemoteImpl
),啟動RMI registry(rmiregistry
),啟動遠端服務(Naming.rebind("Remote Hello", serviceMyRemote)
)。 - 客戶端查詢RMIregistry(
(MyRemote)Naming.lookup("rmi://127.0.0.1/Remote Hello")
),RMIregistry返回stub物件,客戶端呼叫stub上的方法; - 注意點:啟動遠端服務前啟動registry;引數和返回型別可序列化;
- servlet相當於Java的CGI,可以處理使用者提交的請求,並將結果返回給瀏覽器的網頁;
- 建立並執行serverlet的步驟:找到網頁伺服器可以存放servlet的地方;取得servlet.jar並新增到classpath上(其不是標準函式庫的一部分);extends HttpServlet來編寫servlet的類;編寫html來呼叫servlet;給伺服器配置html和servlet;
- servlet一般通過覆蓋HttpServlet的doGet和doPost來建立;它輸出帶有完整標識的HTML網頁;
- EJB是Enterprise JavaBeans,它具有一組光靠RMI不會有的服務,比如交易管理、安全性、併發性、資料庫和網路功能等;EJB伺服器作用於RMI呼叫和服務層之間。service helper呼叫EJB object,object呼叫Enterprise bean,後者再呼叫DB等操作;
Jini也使用RMI,但具有自適應探索(接收服務註冊和客戶端呼叫查詢)和自恢復網路(用心跳檢查服務)的功能;
7.13 附錄
- 按位非~,按位與&,按位或|,按位異或^;
- 右移>>,無符號右移>>>;
- String具有不變性,放在String pool中,不受Garbage Collector管理。
- 包裝類沒有setter,其具有不變性。例如new Integer(42),永遠都是42。
斷言可以用來測試,它比println的好處是沒有特別設定的話,它會自動被JVM忽略,也可專門開啟斷言除錯程式。
assert (height > 0) : "height = " + height + " weight = " + weight; //冒號後面的語句可以是任何返回非null值得語句,但一定要注意不要在這裡改變物件的狀態 //斷言的編譯和普通編譯沒差別,執行時有差異。 javac TestDriveGame.java java -ea TestDriveGame
new Foo().go()
是一個呼叫go方法,但又不需要維持對Foo引用的方式;- 靜態巢狀類,和普通類很像,可通過new Outer.Inner()來建立。可存取外層靜態私有變數;
- 非靜態的巢狀類,通常被稱為內部類。
匿名內部類。
//button.addActionListener(quitListener); //通常是傳遞一個內部類的例項 //雖然ActionListener是個介面,而且我們不能宣告一個介面的例項,但匿名內部類是個例外 button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ev){ System.exit(0); } });
- public任何程式程式碼都可公開存取;default只在統一包中的預設事物能夠存取;protected可允許不在同一包的子類,繼承該部分;
- 對於常用的可變String,一般使用StringBuilder,Thread安全環境中,採用StringBuffer;
- 二維陣列是指陣列的陣列,
new int [4][2]
其實是建立一個包含四個元素的陣列,每個元素是一個長度為2的陣列。 - 列舉相對於普通常量的優勢是限定了範圍,其實際是繼承了java.lang.Enum。宣告時使用
public enum Members {JERRY, BOBBY, PHIL};
- 每個列舉都內建values()可以用於遍歷操作;
可以在enum中加入建構函式,方法、變數和特定常量的內容;
8. 相關
暫無