Java程式設計思想(一)
本人剛工作四個月,初讀Java程式設計思想,由於我之前都是通過網路視訊的方式來學習Java知識,所以此書初讀起來有些晦澀難懂,但是我還是盡力去看,然後記下我初看時覺得對我有用的筆記和一些自己的理解。
第一章 物件導論
1.多型
在處理型別的層次結構時,把一個物件不當做它所屬的特定型別來對待,而是將其當做其基類(父類)的物件來對待。這使得人們可以編寫出不依賴於特定型別的程式碼。
2.單繼承
單繼承結構保證所有物件都具備某些功能,可以在每個物件上執行某些基本操作。
3.引數化型別
一個編譯器可以自動定製作用於特定型別上的類,用於向下轉型,如Circle是一種Shape型別,但是不知道某個Object是Circle還是Shape,此時就需要引數化型別,其實就是泛型。
4.物件的建立和生命週期
當處理完某個物件之後,系統某個其他部分可能還在處理它。java提供了被稱為“垃圾回收器”的機制,它可以自動發現物件何時不再被使用,並繼而銷燬它。可以避免暗藏的記憶體洩露問題。
5.併發程式設計
併發:指應用能夠交替執行不同的任務,例:吃完飯->喝水 喝完水->睡覺 一覺醒來->吃飯......
並行:指應用能夠同時執行不同的任務,例:吃飯的時候可以邊吃飯邊打電話,這兩件事情可以同時執行。
並行存在鎖的概念,如兩個相同執行緒並行訪問到同一資源,該資源只能被一個執行緒使用,該資源會被鎖定,被一個執行緒佔用時,該任務鎖定該項資源,完成任務,然後釋放資源鎖,使其它任務可以使用這項資源。
第二章 一切都是物件
1.物件儲存到什麼地方
(1)暫存器
(2)堆疊(RAM區):堆疊指標向下移動,分配新的記憶體,向上移動,則釋放記憶體。
(3)堆(RAM區):一種通用記憶體池。
堆不同於堆疊的好處是:編譯器不需要知道儲存的資料在堆裡存活多長時間。
壞處:用堆進行儲存分配和清理可能比用堆疊進行儲存分配需要更多的時間。
(4)常量儲存
直接存放在程式碼內部。
(5)非RAM儲存
資料存活在程式之外,如磁碟。
2.高精度數字
BigInteger:支援任意精度的整數。
BigDecimal:支援任何精度的定點數。
3.static
修飾程式碼塊:先於構造器載入,並且只會載入一次,方法內沒有this,全域性方法,僅通過類本身來呼叫static方法。
第三章 操作符
第四章 控制執行流程
第三、四兩章我覺得很基礎,所以沒有筆記。
第五章 初始化與清理
1.構造器:這是一個在建立物件時被自動呼叫的特殊方法,確保初始化,java會在使用者有能力操作物件之前自動呼叫相應的構造器,由編譯器呼叫,與類名相同,無返回值。
(1)不接受任何引數的構造器叫做預設構造器。
(2)如果你沒有定義構造器,編譯器自動幫你建立預設構造器,但是如果你定義了構造器,編譯器就不會再去建立預設構造器。
例子:
class Bird2{
Bird2(int f){}
Bird(double d){}
}
public class NoSynthesis{
public static void main(String[] args){
Bird2 b=new Bird2();//報錯,沒有預設構造器
Bird2 b2=new Bird2(1);
Bird2 b3=new Bird2(1.8);
}
}
(3)除構造器之外,編譯器禁止在其他任何方法中呼叫構造器。
2.this:指出當前物件的引用 如 A a=new A();指出的就是引用a。
3.垃圾回收器:垃圾回收器只會釋放那些經由new分配的記憶體。
4.finalize()方法:並非使用new獲得的記憶體區域時,使用該方法釋放記憶體,該區域主要指在java程式碼中呼叫了非java程式碼。也許會呼叫c的malloc()函式來分配空間,要釋放空間得呼叫free(),但該方法是c、c++中才有的,所以在java中要使用finalize()。當垃圾回收器準備好釋放物件佔用的儲存空間,將首先呼叫finalize()方法,並且在下一次垃圾回收動作發生時,才會真正回收物件佔用的記憶體。在做清理工作時,我們可以在finalize()里加入某種擦除功能,當垃圾回收發生時,finalize()得到呼叫,資料就會被擦除,要是垃圾回收沒有發生,資料就會保留。
(1)垃圾回收只與記憶體有關,也就是說使用垃圾回收器的唯一原因是為了回收程式不在使用的記憶體。無論是垃圾回收,還是終結,都不保證一定會發生。如果jvm並未面臨記憶體耗盡的情形,它是不會浪費時間去執行垃圾回收以恢復記憶體的。
5.垃圾回收器如何工作:
(1)停止-複製
先暫停程式的執行,然後將所有存活的物件從當前堆複製到另一堆,沒有被複制的全是垃圾,當物件被複制到新堆時,它們是一個挨著一個的,所以新堆保持緊湊排列,然後就可以按像傳送帶一樣的方法,每分配一個新物件,就往前移動一格,這樣簡單、直接地分配新空間了。當把物件從一處搬到另一處時,所有指向它的引用都必須修正。
缺點:效率會降低。原因是,首先,得有兩個堆,然後在這兩個分離的堆之間來回搗騰,從而得維護比實際需要多一倍的空間。第二個問題,是複製,如果程式只有少量垃圾,或者沒垃圾,垃圾回收器仍然會將所有記憶體自一處複製到另一處,這很浪費。
(2)標記-清掃
虛擬機器進行檢查,要是沒有新垃圾產生,就會轉向這一方式,該方式速度相當慢,但是當你知道只會產生少量垃圾甚至不會產生垃圾時,它的速度就很快了。
該方式所依據的思路同樣是從堆疊和靜態儲存區出發,遍歷所有的引用,進而找出所有存活的物件,每當它找到一個存活物件,就會給物件設一個標記,這個過程中不會回收任何物件。只有全部標記工作完成的時候,清理才會開始。在清理過程中,沒有標記的物件將會被釋放,不會發生任何複製動作,所以剩下的堆空間是不連續的,垃圾回收器要是希望得到連續空間的話,就得重新整理剩下的物件。
記憶體會分配較大的“塊”,停止-複製要把所有存活物件複製到新堆,會造成大量記憶體複製行為,有了塊之後垃圾回收器,就可以往廢棄的塊裡拷貝物件了,這種好處就是先將空間分配好,執行時,就不需要現去分配空間,減少時間,典型的空間換時間。
java實際垃圾清理是“自適應技術”:java虛擬機器會進行監視,如果物件都很穩定,垃圾回收器的效率降低的話,就切換到“標記-清掃”方式,同樣,java虛擬機器會跟蹤“標記-清掃”的效果,要是堆空間出現很多碎片,就會切換回“停止-複製”方式。
6.自動初始化:如果沒給基本型別的欄位賦值,編譯器會給其賦一個預設值,自動初始化會在構造器被呼叫之前執行。
第六章 訪問許可權控制
1.重構:即重寫程式碼,以使得它更可讀、更易理解、並因此而更具可維護性。
第七章 複用類
1.帶引數的構造器:
class Game{
Game(int i){
print("Game constructor");
}
}
class BoarGame extends Game{
BoarGame(int i){
super(i)
print("BoarGame constructor");
}
}
在繼承中構造器的執行順序:先父類構造器,然後子類構造器,上面程式碼要說明父類寫的是帶引數的構造器,所以沒有預設構造器,子類必須在自身構造器中呼叫父類的帶參構造器,因為子類會呼叫父類的預設構造器,但是此時父類沒有預設構造器,所以不呼叫父類的帶參構造器的話,會報錯。
2.名稱遮蔽:
如果java的基類擁有某個已被多次過載的方法名稱,那麼在子類中重新定義該方法名稱不會遮蔽其在基類中的任何版本。就是說父類中有一個方法的多個過載,子類也過載了該方法,它不會遮蔽掉父類的多個過載。@Override指重寫,如果你不想過載,加這個就不允許過載,只能重寫。
3.組合與繼承:
組合和繼承都允許在新的類中放置子物件,組合是顯示的這樣做,而繼承是隱式的做。
4.protected:
就類使用者而言,這是private的,但對於任何繼承與此類的子類或其他任何位於同一個包內的類來說,它是可以訪問的。
5.向上轉型:
舉個例子:樂器A 、鋼琴B 、B繼承A ,B擁有A的所有方法,那麼就可以說B是A型別的,再通俗就是父類引用指向子類物件。即 A a=new B() , a引用指向了一個B物件,表示B物件是A型別的。
延伸:向下轉型
A a=new A();
B b=(B)a ; 表示A物件是B型別
6.final:
(1)一個永不改變的編譯時常量。
(2)一個在執行時被初始化的值,而你不需要它被改變。
final修飾方法
把方法鎖定,防止任何繼承類修改它的含義,即確保在繼承中父類的方法不會被子類覆蓋。
final修飾類
你不允許被修飾的類有子類,即不能被繼承。
第八章 多型
1.後期繫結:即多型實現機制,編譯器一直不知道物件的型別,但是方法呼叫機制能找到正確的方法體,並加以呼叫。
2.子類繼承父類時,如果父類的一個方法私有,子類重寫這個方法是不成功的,即使方法名一樣也不能覆蓋父類的這個方法,只能被當做一個新的方法去看待。
3.只有普通的方法呼叫是多型的,如果直接訪問某個域,這個域就將在編譯期進行解析。如子類和父類中有兩個相同名字的基本型別 如int a,子類不會覆蓋父類的int a,而是分配了不同的儲存空間,向上轉型時,呼叫的就是父類的方法。
如 :
class Super{
public int field=0;
public int getField(){return field};
}
class Sub extends Supoer{
public int field=1;
public int getField(){return field};
public int getSuperField(){return super.field};
}
//Super sup=new Sub();
//sup.field=0,sup.getField()=1
//Sub sub=new Sub();
//sub.field=1,sub.getField()=1,sub.getSuperField=0;
為什麼會這種結果:第一個是向上轉型,呼叫的是Super這個型別的方法,所以field為0,sup.getField()=1不是因為呼叫的是子類的方法,還是呼叫的Super裡的,但是它返回的field卻是子類的,由於是有兩個不同的field,不同點是field、super.field,所以要返回1的話,就要用super.field。
4.構造器與多型:構造器相當於static方法,只不過該static宣告是隱式的,子類呼叫構造器前,要先呼叫父類的構造器。
5.繼承與清理:一般清理由垃圾回收器自己去執行,但在某些必要條件下,要自己寫清理方法,此時子類繼承父類,父類中寫了清理方法,但是子類存在特殊處理,所以重寫父類清理方法,覆蓋了父類清理,此時要注意,父類就不會被清理,由於是在子類中進行,父類的清理方法被覆蓋掉了,所以在子類中沒有執行父類的清理方法,所以一定要加上super的父類清理方法,否則父類的清理不會發生。
6.構造器內部的多型方法的行為:在繼承中,最好將父類直接放到子類中看,這樣直觀。
class Glyph{
void draw() {
print("G.draw()");
}
Glyph(){
print("Glyph() before draw()");
draw();
print("Glyph() after draw()");
}
}
class RoundGlyph extends Glyph{
private int radius=1;
RoundGlyph(int r){
radius=r;
print("RoundGlyph.RoundGlyph(),radius="+radius);
}
void draw(){
print("RoundGlyph.draw,radius="+radius);
}
}
public class PolyConstructors{
public static void main(String[] args){
new RoundGlyph(5);
}
}
//輸出 Glyph() before draw()
//RoundGlyph.RoundGlyph(),radius=0
//Glyph() after draw()
//RoundGlyph.RoundGlyph(),radius=5
這種輸出結果的理解,先是呼叫父類構造方法,走到draw()時,要把父類放到在子類中結合看,可以看到draw()被重寫了,所以呼叫的是子類的draw(),radius為0是由於在一切事物發生之前,將分配給物件的儲存空間初始化二進位制的0,即在呼叫父類構造前,先分配儲存空間,初始化的值為0,執行完父類構造後,再給radius賦值為5。
第九章 介面
1.抽象類和抽象方法:包含抽象方法的類叫做抽象類,如果一個類包含一個或多個抽象方法,該類必須被限定為抽象的,否則編譯器會報錯。抽象類主要用來被繼承,不能被例項化的類。
2.使用介面的核心原因:
(1)為了能夠向上轉型為多個基型別。
(2)防止客戶端程式設計師建立該類的物件,並確保這僅僅是建立一個介面。
介面與抽象類之間的選擇: 如果要建立不帶任何方法定義和成員變數的基類,那麼就應該選擇介面,而非抽象類。
3.通過繼承來擴充套件介面:一個類只能繼承一個基類,但能實現多個介面,介面可以繼承介面,並且可以多繼承。
4.介面中的域:放入介面中的任何域都是自動static和final的。
第十章 內部類
1.連結到外部類:內部類自動擁有對其外圍類所有成員的訪問權,privte修飾的也可訪問。
理解:就是內部類能直接訪問外部類的資訊,但外圍類不能直接訪問內部類裡的資訊。
2.使用.this與.new:建立一個內部類物件
DotNew dn=new DotNew();
DotNew.Inner dn1=dn.new Inner();
3.內部類與向上轉型:一個類裡面,含有一個被private修飾的內部類,外部類不能訪問這個內部類,除了內部類自己。
4.在方法和作用域內的內部類:在一個作用域內的內部類,在作用域外,他是不可用的,除此之外,他與普通的類沒有區別,作用域就是指{ }。
5.匿名內部類:
寫法:
return new A(){};
在匿名內部類,他不可能有命名構造器,因為它根本沒有名字。如果定義一個匿名內部類,並且希望它使用一個在其外部定義的物件,那麼編譯器會要求其引數引用是final的。
6.巢狀類:如果不需要內部類物件與其外圍類物件之間有聯絡,那麼可以將內部類宣告為static,這通常稱為巢狀類。普通的內部類物件隱式的儲存了一個引用,指向建立它的外圍物件。
巢狀類,意味著:
(1)要建立巢狀類的物件,並不需要其外圍類的物件
(2)不能從巢狀類的物件中訪問非靜態的外圍類物件
如2中建立一個內部類物件,只需要
Inner dn1=new Inner();
7.介面內部的類:接口裡可以放一個介面的實現類,該類實際就是巢狀類,因為介面自動public和static。
8.為什麼需要內部類:完成間接的多繼承實現,即一個類只能繼承一個父類,這時這個類只有兩個可用型別,即本身和父類型別,但是如果還想有另外幾個型別的話,就可以在子類中寫一個方法,方法裡寫一個類。
如
class Z extends D{
E makeE() {
return new E(){};
}
}
這時如果
Z z=new Z();
z.makeE();
它z返回的型別就是E了。
9.閉包與回撥:閉包是一個可呼叫的物件,它記錄了一些資訊,這些資訊來自於建立它的作用域。內部類是面向物件的閉包,它不僅包含外圍類物件(建立內部類的作用域)的資訊,還自動擁有一個指向外圍類物件的引用,在此作用域內,內部類有許可權操作所有的成員,包括private成員。
10.內部類的繼承:
class WithInner{
class Inner{
}
}
public class InheritInner extends WithInner.Inner{
InheritInner(WithInner wi){
wi.super;
}
}
這種繼承內部類的類,它的構造器,必須傳入一個內部類的外圍類的物件的引用,然後必須在構造器中使用*.super這個語法。估計是規的,沒有為啥。