chapter 6 繼承與多态
6.1何謂繼承
1.繼承的定義:繼承就是避免多個類間重複定義共同行為。
2.總結:教材中通過設計一款RPG遊戲的部分代碼向我們展示了“重複”程式代碼的弊端,為了改進,我們引進了繼承的概念。在我們自我學習的過程中,通過一次次的改進,也曾經得到這樣的啟示:在不同的資訊類中,有多少的屬性就設定多少個變量,給每個屬性分别給予設定和擷取的方法,以來可以将屬性設為private時仍然可以存取引用資料,二來當多個類擁有同樣屬性和同樣的設定、擷取方法時,我們可以将其利用繼承于父類的方式精簡代碼,提高可讀性和簡潔性,并未之後對代碼的修改提供友善。
簡而言之,就是需要我們通過了解和提煉,将相同的程式代碼提升為父類。
3.繼承的格式:我們通過關鍵字“extends”來連接配接子類和父類,這表示子類會擴充、繼承父類的行為。
4.引入類圖:類圖是用于模組化幫助我們了解類中内容、父類子類間關系的工具,在教材161頁有提示,另外在畢向東老師Java講解視訊中也有提及,并且更為詳細。
5.is-a:繼承中,子類與父類間會有is-a的短息,即“是一種”的關系。簡單說就是:子類是父類的一種。教材中通過劍士(子類)是一種角色(父類)的例子來說明。
此處引入了編譯與運作的通過判斷的講解,在這當中提及了一個操作:“如何讓編譯程式閉嘴”。這其實是一種投機的操作,編譯雖然能夠無錯誤地通過,但運作結果要自負。
6.重載:在并行的類中定義同名方法,這就是重載方法的運用。
7.多态:引入多态,其實是對重載的改進。當并行類很多并且他們擁有共同的特點時,我們可以将其運用繼承修改代碼,并在此基礎上引入多态。多态其實就是使用單一接口操作多種類型的對象。這種寫法的好處就是能夠具有更高的可維護性。
8.重新定義:在繼承父類之後,定義于父類中相同的方法不熟,但執行内容不同,這就是重新定義(Override)。
技巧:在某個子類中某個方法前标注@Override,表示要求編譯程式檢查,該方法是不是真的重新定義了父類中國某個方法,若不是,則編譯錯誤。
9.抽象方法:如果某方法區塊中沒有任何程式代碼操作,可以使用abstract表示該方法為抽象方法,該方法不用撰寫{}區塊,至二級用“;”結束即可。
10.抽象類:若一個類中有方法沒有操作并且表示為abstract,則這個類就不能用來生成執行個體,即是抽象類,要在class前面表示abstract。
11.抽象類的繼承:若子類繼承抽象類,對于抽象方法有兩種做法:一是繼續标示該方法為abstract,該子類是以也是一個抽象類,必須在class前面表示abstract;另一種做法就是操作抽象方法。若兩種做法都沒有實施,就會引發編譯錯誤。
6.2繼承文法細節
1.protected:
(1)private定義的屬性不能直接在子類中存取,隻能通過擷取方法來取得。若隻想讓子類可以直接存取屬性變量又不想完全開放通路權限時,可将他們定義為protected。
(2)被聲明為protected的成員,相同包中的類可以直接存取,不同包中的類需要在繼承後的子類中存取。
(3)在寫程式時,若一開始對權限未知,一般将成員設定為private。
2.重新定義的細節
(1)super:若子類想取得父類中的方法定義,可在調用方法前,加上super關鍵字。(例見Game6 Role.java;Game6 SwordsMan.java;Game6 Magician.java)
(2)注意:可以使用super關鍵字調用的父類方法,不能定義為private。并且對于父類中的方法權限,隻能擴大但不能縮小。
(3)JDK5的改進:在JDK5之前,重新定義方法時除了可以定義權限較大的關鍵字外,其他部分必須與父類中方法簽署完全一緻,例如傳回值類型。在JDK5及之後,隻要傳回類型是父類中方法傳回類型的子類,也是可以通過編譯的。
(4)提示:static方法屬于類擁有,如果子類中定義了相同簽署的static成員,該成員屬于子類所有,而非重新定義,static方法也沒有多态,因為對象不會個别擁有static成員。
3.構造函數:
(1)繼承構造函數定義的先後順序:建立子類石磊後,會先進行父類定義的初始流程再進行子類中定義的初始流程。即在建立子類執行個體後,會先執行父類構造函數定義的流程,再執行子類構造函數定義的流程。
(2)構造函數的重載:父類中可重載多個構造函數,如果子類構造函數中沒有指定執行父類中哪個構造函數,會預設調用父類中無參數的構造函數。
(3)this()和super()隻能擇一調用,而且一定要在構造函數第一行執行。
4.final關鍵字:
(1)final:如果對象資料成員被聲明為final,但沒有明确使用=指定值,表示延遲對象成員值的指定,在構造函數執行流程中,一定要有對該資料成員指定值得操作,否則編譯錯誤。
(2)繼承中的final:若class前使用了final,則這個類是末類,不能被繼承。若方法被限定為final,子類也不能重新定義final方法了。
5.java.lang.Object:如果定義類時沒有使用extends關鍵字定義繼承任何類,則代表它繼承java.lang.Object。即它是所有類的最上層父類。
6.垃圾收集:對于不再有用的對象,JVM有垃圾收集機制,收集到的垃圾對象所占據的記憶體空間會被垃圾收集器釋放。在執行流程中,無法通過變量參考的對象,就是GC認定的垃圾對象。
7.抽象類:重點通過例子展現繼承思想。(代碼包中有對應代碼)
附加:總結補充
通過我對Java視訊的複習,我将繼承與多态相關知識點記錄如下:
1.繼承的優點:
(1)提高了代碼的複用性。
(2)讓類與類之間産生了關系,提供了另一個特征多态的前提。
2.父類的由來:其實是由多個類不斷向上抽取共性内容而來的。java中對于繼承,java隻支援單繼承。java雖然不直接支援多繼承,但是保留了這種多繼承機制,進行改良。
單繼承:一個類隻能有一個父類。
多繼承:一個類可以有多個父類。
3.不支援多繼承的原因:當一個類同時繼承兩個父類時,兩個父類中有相同的功能,那麼子類對象調用該功能時,運作哪一個呢?因為父類中的方法中存在方法體。但是java支援多重繼承。A繼承B,B繼承C,C繼承D。多重繼承的出現,就有了繼承體系。體系中的頂層父類是通過不斷向上抽取而來的。它裡面定義的該體系最基本最共性内容的功能。是以,一個體系要想被使用,直接查閱該系統中的父類的功能即可知道該體系的基本用法。那麼想要使用一個體系時,需要建立對象。建議建立最子類對象,因為最子類不僅可以使用父類中的功能。還可以使用子類特有的一些功能。
簡單說:對于一個繼承體系的使用,查閱頂層父類中的内容,建立最底層子類的對象。
4.子父類出現後,類中的成員的特點:
(1)成員變量:子父類中出現一樣的屬性時,子類類型的對象,調用該屬性,值是子類的屬性值。如果想要調用父類中的屬性值,需要使用一個關鍵字:super 。
This:代表是本類類型的對象引用。
Super:代表是子類所屬的父類中的記憶體空間引用。
注意:子父類中通常是不會出現同名成員變量的,因為父類中隻要定義了,子類就不用在定義了,直接繼承過來用就可以了。
(2)成員函數:當子父類中出現了一模一樣的方法時,建立子類對象會運作子類中的方法。好像父類中的方法被覆寫掉一樣。是以這種情況,是函數的另一個特性:覆寫(複寫,重寫)。當一個類的功能内容需要修改時,可以通過覆寫來實作。
(3)構造函數:發現子類構造函數運作時,先運作了父類的構造函數。原因是子類的所有構造函數中的第一行,其實都有一條隐身的語句super();
注意:子類中所有的構造函數都會預設通路父類中的空參數的構造函數,因為每一個子類構造内第一行都有預設的語句super(); 如果父類中沒有空參數的構造函數,那麼子類的構造函數内,必須通過super語句指定要通路的父類中的構造函數。如果子類構造函數中用this來指定調用子類自己的構造函數,那麼被調用的構造函數也一樣會通路父類中的構造函數。
5.繼承的細節:
在方法覆寫時,注意兩點:
子類覆寫父類時,必須要保證,子類方法的權限必須大于等于父類方法權限可以實作繼承。否則,編譯失敗。
覆寫時,要麼都靜态,要麼都不靜态。 (靜态隻能覆寫靜态,或者被靜态覆寫)
6.繼承的弊端:打破了封裝性。對于一些類,或者類中功能,是需要被繼承,或者複寫的。此時則需要引進final來解決。
7.final特點:
這個關鍵字是一個修飾符,可以修飾類,方法,變量。
被final修飾的類是一個最終類,不可以被繼承。
被final修飾的方法是一個最終方法,不可以被覆寫。
被final修飾的變量是一個常量,隻能指派一次。
其實這樣的原因的就是給一些固定的資料起個閱讀性較強的名稱。
不加final修飾不是也可以使用嗎?那麼這個值是一個變量,是可以更改的。加了final,程式更為嚴謹。常量名稱定義時,有規範,所有字母都大寫,如果由多個單詞組成,中間用 _ 連接配接。
8.抽象類 abstract:
抽象:不具體,看不明白。抽象類表象展現。
在不斷抽取過程中,将共性内容中的方法聲明抽取,但是方法不一樣,沒有抽取,這時抽取到的方法,并不具體,需要被指定關鍵字abstract所标示,聲明為抽象方法。
抽象方法所在類一定要标示為抽象類,也就是說該類需要被abstract關鍵字所修飾。
9.抽象類的特點:
抽象方法隻能定義在抽象類中,抽象類和抽象方法必須由abstract關鍵字修飾(可以描述類和方法,不可以描述變量)。
抽象方法隻定義方法聲明,并不定義方法實作。
抽象類不可以被建立對象(執行個體化)。
隻有通過子類繼承抽象類并覆寫了抽象類中的所有抽象方法後,該子類才可以執行個體化。否則,該子類還是一個抽象類。
10.抽象類的細節:
(1)抽象類中是否有構造函數?有,用于給子類對象進行初始化。
(2)抽象類中是否可以定義非抽象方法?
可以。其實,抽象類和一般類沒有太大的差別,都是在描述事物,隻不過抽象類在描述事物時,有些功能不具體。是以抽象類和一般類在定義上,都是需要定義屬性和行為的。隻不過,比一般類多了一個抽象函數。而且比一般類少了一個建立對象的部分。
(3)抽象關鍵字abstract和哪些不可以共存?final , private , static
(4)抽象類中可不可以不定義抽象方法?可以。抽象方法目的僅僅為了不讓該類建立對象。
chapter7 接口與多态
1.定義:關鍵字interface。接口中包含的成員,最常見的有全局常量、抽象方法。
注意:接口中的成員都有固定的修飾符。
成員變量:public static final
成員方法:public abstract
interface Inter{
public static final int x = 3;
public abstract void show();
}
3:接口中有抽象方法,說明接口不可以執行個體化。接口的子類必須實作了接口中所有的抽象方法後,該子類才可以執行個體化。否則,該子類還是一個抽象類。
4:類與類之間存在着繼承關系,類與接口中間存在的是實作關系。繼承用extends ;實作用implements ;
5:接口和類不一樣的地方,就是,接口可以被多實作,這就是多繼承改良後的結果。java将多繼承機制通過多現實來展現。
6:一個類在繼承另一個類的同時,還可以實作多個接口。是以接口的出現避免了單繼承的局限性。還可以将類進行功能的擴充。
7:其實java中是有多繼承的。接口與接口之間存在着繼承關系,接口可以多繼承接口。
接口都用于設計上,設計上的特點:(可以了解主機闆上提供的接口)
1:接口是對外提供的規則。
2:接口是功能的擴充。
3:接口的出現降低了耦合性。
抽象類與接口:
抽象類:一般用于描述一個體系單元,将一組共性内容進行抽取,特點:可以在類中定義抽象内容讓子類實作,可以定義非抽象内容讓子類直接使用。它裡面定義的都是一些體系中的基本内容。
接口:一般用于定義對象的擴充功能,是在繼承之外還需這個對象具備的一些功能。
抽象類和接口的共性:都是不斷向上抽取的結果。
抽象類和接口的差別:
1:抽象類隻能被繼承,而且隻能單繼承。
接口需要被實作,而且可以多實作。
2:抽象類中可以定義非抽象方法,子類可以直接繼承使用。
接口中都有抽象方法,需要子類去實作。
3:抽象類使用的是 is a 關系。
接口使用的 like a 關系。
4:抽象類的成員修飾符可以自定義。
接口中的成員修飾符是固定的。全都是public的。
在開發之前,先定義規則,A和B分别開發,A負責實作這個規則,B負責使用這個規則。至于A是如何對規則具體實作的,B是不需要知道的。這樣這個接口的出現就降低了A和B直接耦合性。
chapter8 異常
1.異常:就是不正常。程式在運作時出現的不正常情況。其實就是程式中出現的問題。這個問題按照面向對象思想進行描述,并封裝成了對象。因為問題的産生有産生的原因、有問題的名稱、有問題的描述等多個屬性資訊存在。當出現多屬性資訊最友善的方式就是将這些資訊進行封裝。異常就是java按照面向對象的思想将問題進行對象封裝。這樣就友善于操作問題以及處理問題。
2.出現的問題有很多種,比如角标越界,空指針等都是。就對這些問題進行分類。而且這些問題都有共性内容比如:每一個問題都有名稱,同時還有問題描述的資訊,問題出現的位置,是以可以不斷的向上抽取。形成了異常體系。
--------java.lang.Throwable:
Throwable:可抛出的。
|--Error:錯誤,一般情況下,不編寫針對性的代碼進行處理,通常是jvm發生的,需要對程式進行修正。
|--Exception:異常,可以有針對性的處理方式
無論是錯誤還是異常,它們都有具體的子類展現每一個問題,它們的子類都有一個共性,就是都以父類名才作為子類的字尾名。
這個體系中的所有類和對象都具備一個獨有的特點;就是可抛性。
可抛性的展現:就是這個體系中的類和對象都可以被throws和throw兩個關鍵字所操作。
class ExceptionDemo{
public static void main(String[] args) {
// byte[] buf = new byte[1024*1024*700];//java.lang.OutOfMemoryError記憶體溢出錯誤
}
}
在開發時,如果定義功能時,發現該功能會出現一些問題,應該将問題在定義功能時标示出來,這樣調用者就可以在使用這個功能的時候,預先給出處理方式。
3.标示方法:通過throws關鍵字完成,格式:throws 異常類名,異常類名...标示後,調用者在使用該功能時,就必須要處理,否則編譯失敗。
4.處理方式:1、捕捉;2、抛出。
對于捕捉:java有針對性的語句塊進行處理。
try {
需要被檢測的代碼;
}
catch(異常類 變量名){
異常處理代碼;
}
fianlly{
一定會執行的代碼;
}
--------------------------------------------------------
catch (Exception e) { //e用于接收try檢測到的異常對象。
System.out.println("message:"+e.getMessage());//擷取的是異常的資訊。
System.out.println("toString:"+e.toString());//擷取的是異常的名字+異常的資訊。
e.printStackTrace();//列印異常在堆棧中資訊;異常名稱+異常資訊+異常的位置。
}
異常處理原則:功能抛出幾個異常,功能調用如果進行try處理,需要與之對應的catch處理代碼塊,這樣的處理有針對性,抛幾個就處理幾個。
特殊情況:try對應多個catch時,如果有父類的catch語句塊,一定要放在下面。
throw 和throws關鍵字的差別:
throw用于抛出異常對象,後面跟的是異常對象;throw用在函數内。
throws用于抛出異常類,後面跟的異常類名,可以跟多個,用逗号隔開。throws用在函數上。
通常情況:函數内容如果有throw,抛出異常對象,并沒有進行處理,那麼函數上一定要聲明,否則編譯失敗。但是也有特殊情況。
異常分兩種:
1:編譯時被檢查的異常,隻要是Exception及其子類都是編譯時被檢測的異常。
2:運作時異常,其中Exception有一個特殊的子類RuntimeException,以及RuntimeException的子類是運作異常,也就說這個異常是編譯時不被檢查的異常。
編譯時被檢查的異常和運作時異常的差別:
編譯被檢查的異常在函數内被抛出,函數必須要聲明,否編譯失敗。
聲明的原因:是需要調用者對該異常進行處理。
運作時異常如果在函數内被抛出,在函數上不需要聲明。
不聲明的原因:不需要調用者處理,運作時異常發生,已經無法再讓程式繼續運作,是以,不讓調用處理的,直接讓程式停止,由調用者對代碼進行修正。
定義異常處理時,功能内部如果出現異常,如果内部可以處理,就用try;如果功能内部處理不了,就必須聲明出來,讓調用者處理。
自定義異常:當開發時,項目中出現了java中沒有定義過的問題時,這時就需要我們按照java異常建立思想,将項目的中的特有問題也進行對象的封裝。這個異常,稱為自定義異常。
對于除法運算,0作為除數是不可以的。java中對這種問題用ArithmeticException類進行描述。對于這個功能,在我們項目中,除數除了不可以為0外,還不可以為負數。可是負數的部分java并沒有針對描述。是以我們就需要自定義這個異常。
自定義異常的步驟:
1:定義一個子類繼承Exception或RuntimeException,讓該類具備可抛性。
2:通過throw 或者throws進行操作。
異常的轉換思想:當出現的異常是調用者處理不了的,就需要将此異常轉換為一個調用者可以處理的異常抛出。
當異常出現後,在子父類進行覆寫時,有了一些新的特點:
1:當子類覆寫父類的方法時,如果父類的方法抛出了異常,那麼子類的方法要麼不抛出異常要麼抛出父類異常或者該異常的子類,不能抛出其他異常。
2:如果父類抛出了多個異常,那麼子類在覆寫時隻能抛出父類的異常的子集。
注意:
如果父類或者接口中的方法沒有抛出過異常,那麼子類是不可以抛出異常的,如果子類的覆寫的方法中出現了異常,隻能try不能throws。
如果這個異常子類無法處理,已經影響了子類方法的具體運算,這時可以在子類方法中,通過throw抛出RuntimeException異常或者其子類,這樣,子類的方法上是不需要throws聲明的。
常見異常:
1、腳标越界異常(IndexOutOfBoundsException)包括數組、字元串;
空指針異常(NullPointerException)
2、類型轉換異常:ClassCastException
3、沒有這個元素異常:NullPointerException
4、不支援操作異常;
異常要盡量避免,如果避免不了,需要預先給出處理方式。比如家庭備藥,比如滅火器。
chapter9 collection和maps
1.Map集合:
|--Hashtable:底層是哈希表資料結構,是線程同步的。不可以存儲null鍵,null值。
|--HashMap:底層是哈希表資料結構,是線程不同步的。可以存儲null鍵,null值。替代了Hashtable.
|--TreeMap:底層是二叉樹結構,可以對map集合中的鍵進行指定順序的排序。
Map集合存儲和Collection有着很大不同:
Collection一次存一個元素;Map一次存一對元素。
Collection是單列集合;Map是雙列集合。
Map中的存儲的一對元素:一個是鍵,一個是值,鍵與值之間有對應(映射)關系。
特點:要保證map集合中鍵的唯一性。
1,添加。
put(key,value):當存儲的鍵相同時,新的值會替換老的值,并将老值傳回。如果鍵沒有重複,傳回null。
void putAll(Map);
2,删除。
void clear():清空
value remove(key) :删除指定鍵。
3,判斷。
boolean isEmpty():
boolean containsKey(key):是否包含key
boolean containsValue(value) :是否包含value
4,取出。
int size():傳回長度
value get(key) :通過指定鍵擷取對應的值。如果傳回null,可以判斷該鍵不存在。當然有特殊情況,就是在hashmap集合中,是可以存儲null鍵null值的。
Collection values():擷取map集合中的所有的值。
5,想要擷取map中的所有元素:
原理:map中是沒有疊代器的,collection具備疊代器,隻要将map集合轉成Set集合,可以使用疊代器了。之是以轉成set,是因為map集合具備着鍵的唯一性,其實set集合就來自于map,set集合底層其實用的就是map的方法。
★ 把map集合轉成set的方法:
Set keySet();
Set entrySet();//取的是鍵和值的映射關系。
Entry就是Map接口中的内部接口;
為什麼要定義在map内部呢?entry是通路鍵值關系的入口,是map的入口,通路的是map中的鍵值對。
1.取出map集合中所有元素的方式一:keySet()方法。
可以将map集合中的鍵都取出存放到set集合中。對set集合進行疊代。疊代完成,再通過get方法對擷取到的鍵進行值的擷取。
Set keySet = map.keySet();
Iterator it = keySet.iterator();
while(it.hasNext()) {
Object key = it.next();
Object value = map.get(key);
System.out.println(key+":"+value);
}
2.取出map集合中所有元素的方式二:entrySet()方法。
Set entrySet = map.entrySet();
Iterator it = entrySet.iterator();
while(it.hasNext()) {
Map.Entry me = (Map.Entry)it.next();
System.out.println(me.getKey()+"::::"+me.getValue());
}
使用集合的技巧:
看到Array就是數組結構,有角标,查詢速度很快。
看到link就是連結清單結構:增删速度快,而且有特有方法。addFirst; addLast; removeFirst(); removeLast(); getFirst();getLast();
看到hash就是哈希表,就要想要哈希值,就要想到唯一性,就要想到存入到該結構的中的元素必須覆寫hashCode,equals方法。
看到tree就是二叉樹,就要想到排序,就想要用到比較。
比較的兩種方式:
一個是Comparable:覆寫compareTo方法;
一個是Comparator:覆寫compare方法。
LinkedHashSet,LinkedHashMap:這兩個集合可以保證哈希表有存入順序和取出順序一緻,保證哈希表有序。
集合:當存儲的是一個元素時,就用Collection。當存儲對象之間存在着映射關系時,就使用Map集合。保證唯一,就用Set。不保證唯一,就用List。
------------------------------------------------------------------------------------------------
Collections:它的出現給集合操作提供了更多的功能。這個類不需要建立對象,内部提供的都是靜态方法。
靜态方法:
Collections.sort(list);//list集合進行元素的自然順序排序。
Collections.sort(list,new ComparatorByLen());//按指定的比較器方法排序。
class ComparatorByLen implements Comparator<String>{
public int compare(String s1,String s2){
int temp = s1.length()-s2.length();
return temp==0?s1.compareTo(s2):temp;
}
}
Collections.max(list); //傳回list中字典順序最大的元素。
int index = Collections.binarySearch(list,"zz");//二分查找,傳回角标。
Collections.reverseOrder();//逆向反轉排序。
Collections.shuffle(list);//随機對list中的元素進行位置的置換。
将非同步集合轉成同步集合的方法:Collections中的 XXX synchronizedXXX(XXX);
List synchronizedList(list);
Map synchronizedMap(map);
原理:定義一個類,将集合所有的方法加同一把鎖後傳回。
Collection 和 Collections的差別:
Collections是個java.util下的類,是針對集合類的一個工具類,提供一系列靜态方法,實作對集合的查找、排序、替換、線程安全化(将非同步的集合轉換成同步的)等操作。
Collection是個java.util下的接口,它是各種集合結構的父接口,繼承于它的接口主要有Set和List,提供了關于集合的一些操作,如插入、删除、判斷一個元素是否其成員、周遊等。
chapter10 輸入輸出
IO流:用于處理裝置上資料。
流:可以了解資料的流動,就是一個資料流。IO流最終要以對象來展現,對象都存在IO包中。
流也進行分類:
1:輸入流(讀)和輸出流(寫)。
2:因為處理的資料不同,分為位元組流和字元流。
①位元組流:處理位元組資料的流對象。裝置上的資料無論是圖檔或者dvd,文字,它們都以二進制存儲的。二進制的最終都是以一個8位為資料單元進行展現,是以計算機中的最小資料單元就是位元組。意味着,位元組流可以處理裝置上的所有資料,是以位元組流一樣可以處理字元資料。
②字元流:因為字元每個國家都不一樣,是以涉及到了字元編碼問題,那麼GBK編碼的中文用unicode編碼解析是有問題的,是以需要擷取中文位元組資料的同時+ 指定的編碼表才可以解析正确資料。為了友善于文字的解析,是以将位元組流和編碼表封裝成對象,這個對象就是字元流。隻要操作字元資料,優先考慮使用字元流體系。
注意:流的操作隻有兩種:讀和寫。
流的體系因為功能不同,但是有共性内容,不斷抽取,形成繼承體系。該體系一共有四個基類,而且都是抽象類。
位元組流:InputStream OutputStream
字元流:Reader Writer
在這四個系統中,它們的子類,都有一個共性特點:子類名字尾都是父類名,字首名都是這個子類的功能名稱。
public static void main(String[] args) throws IOException { //讀、寫都會發生IO異常
/*
1:建立一個字元輸出流對象,用于操作檔案。該對象一建立,就必須明确資料存儲位置,是一個檔案。
2:對象産生後,會在堆記憶體中有一個實體,同時也調用了系統底層資源,在指定的位置建立了一個存儲資料的檔案。
3:如果指定位置,出現了同名檔案,檔案會被覆寫。
*/
FileWriter fw = new FileWriter("demo.txt"); // FileNotFoundException
/*
調用Writer類中的write方法寫入字元串。字元串并未直接寫入到目的地中,而是寫入到了流中,(其實是寫入到記憶體緩沖區中)。怎麼把資料弄到檔案中?
*/
fw.write("abcde");
fw.flush(); // 重新整理緩沖區,将緩沖區中的資料刷到目的地檔案中。
fw.close(); // 關閉流,其實關閉的就是java調用的系統底層資源。在關閉前,會先重新整理該流。
}
close()和flush()的差別:
flush():将緩沖區的資料刷到目的地中後,流可以使用。
close():将緩沖區的資料刷到目的地中後,流就關閉了,該方法主要用于結束調用的底層資源。這個動作一定做。
--------------------------------------------------------------------------------------------------------------------
io異常的處理方式:io一定要寫finally;
FileWriter寫入資料的細節:
1:window中的換行符:\r\n兩個符号組成。 linux:\n。
2:續寫資料,隻要在構造函數中傳入新的參數true。
3:目錄分割符:window \\ /
public static void main(String[] args) {
FileWriter fw = null;
try {
fw = new FileWriter("demo.txt",true);
fw.write("abcde");
}
catch (IOException e ){
System.out.println(e.toString()+"....");
}
finally{
if(fw!=null)
try{
fw.close();
}
catch (IOException e){
System.out.println("close:"+e.toString());
}
}
}
FileReader:使用Reader體系,讀取一個文本檔案中的資料。傳回 -1 ,标志讀到結尾。
import java.io.*;
class FileReaderDemo {
public static void main(String[] args) throws IOException {
/*
建立可以讀取文本檔案的流對象,FileReader讓建立好的流對象和指定的檔案相關聯。
*/
FileReader fr = new FileReader("demo.txt");
int ch = 0;
while((ch = fr.read())!= -1) { //條件是沒有讀到結尾
System.out.println((char)ch); //調用讀取流的read方法,讀取一個字元。
}
fr.close();
}
}
讀取資料的第二種方式:第二種方式較為高效,自定義緩沖區。
import java.io.*;
class FileReaderDemo2 {
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("demo.txt"); //建立讀取流對象和指定檔案關聯。
//因為要使用read(char[])方法,将讀取到字元存入數組。是以要建立一個字元數組,一般數組的長度都是1024的整數倍。
char[] buf = new char[1024];
int len = 0;
while(( len=fr.read(buf)) != -1) {
System.out.println(new String(buf,0,len));
}
fr.close();
}
}
IO中的使用到了一個設計模式:裝飾設計模式。
裝飾設計模式解決:對一組類進行功能的增強。
包裝:寫一個類(包裝類)對被包裝對象進行包裝;
包裝類和被包裝對象要實作同樣的接口;
包裝類要持有一個被包裝對象;
包裝類在實作接口時,大部分方法是靠調用被包裝對象來實作的,對于需要修改的方法我們自己實作;
字元流:
Reader:用于讀取字元流的抽象類。子類必須實作的方法隻有 read(char[], int, int) 和 close()。
|---BufferedReader:從字元輸入流中讀取文本,緩沖各個字元,進而實作字元、數組和行的高效讀取。 可以指定緩沖區的大小,或者可使用預設的大小。大多數情況下,預設值就足夠大了。
|---LineNumberReader:跟蹤行号的緩沖字元輸入流。此類定義了方法 setLineNumber(int) 和 getLineNumber(),它們可分别用于設定和擷取目前行号。
|---InputStreamReader:是位元組流通向字元流的橋梁:它使用指定的 charset 讀取位元組并将其解碼為字元。它使用的字元集可以由名稱指定或顯式給定,或者可以接受平台預設的字元集。
|---FileReader:用來讀取字元檔案的便捷類。此類的構造方法假定預設字元編碼和預設位元組緩沖區大小都是适當的。要自己指定這些值,可以先在 FileInputStream 上構造一個 InputStreamReader。
|---CharArrayReader:
|---StringReader:
Writer:寫入字元流的抽象類。子類必須實作的方法僅有 write(char[], int, int)、flush() 和 close()。
|---BufferedWriter:将文本寫入字元輸出流,緩沖各個字元,進而提供單個字元、數組和字元串的高效寫入。
|---OutputStreamWriter:是字元流通向位元組流的橋梁:可使用指定的 charset 将要寫入流中的字元編碼成位元組。它使用的字元集可以由名稱指定或顯式給定,否則将接受平台預設的字元集。
|---FileWriter:用來寫入字元檔案的便捷類。此類的構造方法假定預設字元編碼和預設位元組緩沖區大小都是可接受的。要自己指定這些值,可以先在 FileOutputStream 上構造一個 OutputStreamWriter。
|---PrintWriter:
|---CharArrayWriter:
|---StringWriter:
位元組流:
InputStream:是表示位元組輸入流的所有類的超類。
|--- FileInputStream:從檔案系統中的某個檔案中獲得輸入位元組。哪些檔案可用取決于主機環境。FileInputStream 用于讀取諸如圖像資料之類的原始位元組流。要讀取字元流,請考慮使用 FileReader。
|--- FilterInputStream:包含其他一些輸入流,它将這些流用作其基本資料源,它可以直接傳輸資料或提供一些額外的功能。
|--- BufferedInputStream:該類實作緩沖的輸入流。
|--- Stream:
|--- ObjectInputStream:
|--- PipedInputStream:
OutputStream:此抽象類是表示輸出位元組流的所有類的超類。
|--- FileOutputStream:檔案輸出流是用于将資料寫入
File
或
FileDescriptor
的輸出流。
|--- FilterOutputStream:此類是過濾輸出流的所有類的超類。
|--- BufferedOutputStream:該類實作緩沖的輸出流。
|--- PrintStream:
|--- DataOutputStream:
|--- ObjectOutputStream:
|--- PipedOutputStream:
緩沖區是提高效率用的:
BufferedWriter:是給字元輸出流提高效率用的,那就意味着,緩沖區對象建立時,必須要先有流對象。明确要提高具體的流對象的效率。
FileWriter fw = new FileWriter("bufdemo.txt");
BufferedWriter bufw = new BufferedWriter(fw);//讓緩沖區和指定流相關聯。
for(int x=0; x<4; x++){
bufw.write(x+"abc");
bufw.newLine(); //寫入一個換行符,這個換行符可以依據平台的不同寫入不同的換行符。
bufw.flush();//對緩沖區進行重新整理,可以讓資料到目的地中。
}
bufw.close();//關閉緩沖區,其實就是在關閉具體的流。
BufferedReader:
FileReader fr = new FileReader("bufdemo.txt");
BufferedReader bufr = new BufferedReader(fr);
String line = null;
while((line=bufr.readLine())!=null){ //readLine方法傳回的時候是不帶換行符的。
System.out.println(line);
}
bufr.close();
流對象:其實很簡單,就是讀取和寫入。但是因為功能的不同,流的體系中提供N多的對象。那麼開始時,到底該用哪個對象更為合适呢?這就需要明确流的操作規律。
流的操作規律:
1,明确源和目的。
資料源:就是需要讀取,可以使用兩個體系:InputStream、Reader;
資料彙:就是需要寫入,可以使用兩個體系:OutputStream、Writer;
2,操作的資料是否是純文字資料?
如果是:資料源:Reader
資料彙:Writer
如果不是:資料源:InputStream
資料彙:OutputStream
3,雖然确定了一個體系,但是該體系中有太多的對象,到底用哪個呢?
明确操作的資料裝置。
資料源對應的裝置:硬碟(File),記憶體(數組),鍵盤(System.in)
資料彙對應的裝置:硬碟(File),記憶體(數組),控制台(System.out)。
4,需要在基本操作上附加其他功能嗎?比如緩沖。如果需要就進行裝飾。
轉換流特有功能:轉換流可以将位元組轉成字元,原因在于,将擷取到的位元組通過查編碼表擷取到指定對應字元。
轉換流的最強功能就是基于 位元組流 + 編碼表 。沒有轉換,沒有字元流。
發現轉換流有一個子類就是操作檔案的字元流對象:
InputStreamReader
|--FileReader
OutputStreamWriter
|--FileWrier
想要操作文本檔案,必須要進行編碼轉換,而編碼轉換動作轉換流都完成了。是以操作檔案的流對象隻要繼承自轉換流就可以讀取一個字元了。
但是子類有一個局限性,就是子類中使用的編碼是固定的,是本機預設的編碼表,對于簡體中文版的系統預設碼表是GBK。
FileReader fr = new FileReader("a.txt");
InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"),"gbk");
以上兩句代碼功能一緻,
如果僅僅使用平台預設碼表,就使用FileReader fr = new FileReader("a.txt"); //因為簡化。
如果需要制定碼表,必須用轉換流。
轉換流 = 位元組流+編碼表。
轉換流的子類File = 位元組流 + 預設編碼表。
凡是操作裝置上的文本資料,涉及編碼轉換,必須使用轉換流。
chapter 12 Lambda
這部分内容書上講的很詳細很多代碼可以用來參照,知識點就不列舉了。另外由于這個是JDK 8的新特性,我在網上找了其他一些探究講解,可以用來參考。
連結如下:
http://developer.51cto.com/art/201404/435591.htm
chapter 14 NIO和NIO2
這部分内容我至今看的不是很懂,隻是看了書上内容反複看了代碼,但是實際上并不能了解多少。網上也能看到一些資料,但是對自己啟發也不是很大= =。還得繼續研讀。
總結
這六個章節是我負責來敲代碼和做記錄的部分。由于自身的基礎和對一些子產品的學習欠缺導緻我看起來也很吃力。在寒假扣除掉很多無法避免的時間後,我隻能算是勉勉強強看了下來。但是後面部分對我來說是真正陌生的,看起來了解慢,内容新,難度又大。實在是無法對自己寒假的學習感到滿意。我覺得這真的需要認真檢討。有的部分看起來反反複複看,也沒有什麼真正學到的,真正記在腦子裡的。我想這對于沒有Java基礎的同學們來說學起來可能要更加困難。是以還需要花更多的時間來精讀這本教材。通過對以前學過部分的Java資料對比,我發現這本教材講的很詳細,也很簡明易懂。有很多代碼可以借助了解。這正是我對讀這本書的動力所在。我總感覺這本書的講解風格很适合我來學習新的知識。是以,我會在和新同學們一起學習的過程中再去鞏固自己的知識。