Think in Java第四版 讀書筆記1

第一章對象導論(Java的幾個重要部分)

訪問控制的目的:

1.權限控制 2.類創建者修改某些實現而不會影響類使用者

代碼複用的方式:

1.繼承 2.組合(composition UML中實心菱形+實線表示)

繼承

(UML中空心三角+實心線表示)

基類與子類行爲不同的產生方式

  1. 爲子類新增方法
  2. 在子類覆蓋(overriding)基類的原來的方法

兩種關係

  1. 是一個 is-a 純粹替代(子類和父類接口方法完全相同,子類的接口 方法可能在父類存在,反之亦然)
  2. 像是一個(子類和父類不完全相同,子類的接口 方法可能在父類不存在,下圖爲is-like-a的例子)

多態:

父類引用指向子類對象 ClassParent classA = new ClassChild(); 也就是將子類泛化成父類類型 在新增一個繼承父類的子類類型時,可以輕鬆替換掉原來的子類類型,程序更新維護便利,在實際classA調用方法的時候編譯器並不知道要調用的代碼在什麼地方,是在運行時才知道調用子類的對應方法的,這也稱爲後期綁定。

向上轉型:

將子類看作是基類的過程稱爲向上轉型(upcasting)

方向相反則是向下轉型,向下轉型可能導致ClassCastException,因爲就像是一個形狀不一定是圓,它也可能是矩形或者三角形.而參數化類型:泛型正是爲此而生

Java 單根繼承的好處:

多重繼承很靈活但是複雜,而單根繼承相對簡單,效率更高。單根繼承類型是確定的,這樣優化了垃圾回收;垃圾回收往往因爲不確定的類型而陷入僵局。

Java的容器列舉

List(存儲序列)Map(關聯數組,建立Key value關係)Set(內容不可重複,實現基於map)隊列 樹 堆棧等

Java的內存分配與垃圾回收

C++對象的生命週期和存儲位置可以在編寫程序時決定,但是這樣必須知道對象的確切數量生命週期和類型,在解決一般化或者特別複雜的問題時不夠靈活。
Java則在堆(heap)內存池中動態創建對象。知道運行時才知道需要多少對象,生命週期如何,具體是什麼類型。只有程序運行到相關代碼行才能確定這一切。如果需要一個對象,可以隨時在堆中創建,這是因爲存儲空間在運行時時動態管理的,也因此一般比在堆棧中創建和銷燬對象需要更多的時間。
動態方式在創建對象時具有靈活性,所以適用於複雜系統。但是效率可能就沒有C++高了。
因爲Java採用了動態堆上分配內存的機制,因此Java垃圾回收可以自動回收,而像C++手動堆棧分配內存就沒法做到自動回收,程序員手動回收時則容易出現遺漏後者錯誤

異常處理

在從錯誤狀態進行可靠恢復的途徑

併發編程

優點:多處理器同時執行任務,效率高 缺點:共享資源時存在隱患
網絡編程
客戶端編程與服務器編程

本章主要講述Java的一些重要特點

第二章 一切都是對象

Java程序的存儲位置

(書中總結的很好)

寄存器:

運算速度最快,但是數量有限,在Java中是不可直接控制的。從程序中無法感覺寄存器的存在

堆棧:

位於RAM(隨機訪問存儲)中,多用於存儲對象的引用,依靠指針的上下移動來工作。指針上移,便是釋放一個對象,指針下移,表示分配一個新內存。因爲需要上下移動指針,所以Java系統需要知道存儲在堆棧中所有對象的確切生命週期。對戰的速度僅次於寄存器。注意堆棧中存儲了對象的引用但沒有將實際對象存儲在這裏

堆:

也位於RAM中,用於存放Java對象。堆與堆棧的區別主要在於1.他們存儲的內容不同2.堆不需要也不知道堆中存儲的數據需要存活多久。當使用new新建一個對象時,堆中自會給其分配內存。因此相比於堆棧,堆中的內存分配更加靈活,但是因爲這種靈活,也需要付出代價:堆中的內存分配和釋放需要更多的時間

常量存儲:

根據不同系統而不同,有的系統將常量存放在程序代碼內部;而有的系統則會將常量與其他部分分離,在此情況下,可以將常量存放於ROM(只讀存儲器)–典型的例子是字符串常量池

非RAM存儲:

數據可以脫離程序而存在,此類的兩個典型是流對象和持久化對象。流對象通常發生在一個機器與另一個機器的數據傳輸,對象被轉化成字節流或者字符流傳輸給另外一個對象。持久化對象則被存儲與磁盤,需要時則可以將他們解析和轉換成基於RAM,可恢復的常規的程序對象。
特例,基本類型的存儲
因爲使用new創建對象需要在堆棧生成一個引用,在堆中動態分配內存,對象如果比較小,往往效率不高。8大基本類型在堆棧中創建的變量存放的不是引用而直接存放值,這樣就不必在堆中分配內存,因此基本類型的創建更爲靈活。

當然基本數據類型都有與之對應的包裝類,而包裝類的創建對象的形式則和普通對象的創建方式一樣。比如
Integer I = new Integer(10);
User user = new User();
在堆棧和堆中創建變量,分配內存的形式是一樣的

數組的存儲

數組在C與C++中是一個連續的內存塊,而在Java中,創建數組則意味着創建了一個引用數組,並且每一個引用初始化爲null
變量的作用域由花括號決定。

對象的作用域:

舉個例子
{
String s = new String(“aaa”);
}
其中變量s的作用域在後花括號之後就消失了,但是s所指向的String對象仍然存在於堆棧中。在C++中,這樣的對象積累起來,會導致內存佔用,這會引起內存泄漏但是Java由自己的垃圾回收機制,不需要程序員手動處理類似問題,這樣就避免嘲笑我忘記釋放內存而產生問題。

變量的初始化

類的字段可以不進行初始化,Java會有一些默認初始化

類的字段如果是一個引用,則沒有初始化,其值默認初始化爲null.
而方法中的變量必須初始化,若非如此,Java會提示錯誤 變量沒有初始化。

變量的區分

使用包名區分

使用其他構建

運用import導包

static關鍵字

類的static變量對於該類的所有對象來說只存在一個實例(static字段堆每個類來說只有一份存儲空間),該變量可以直接使用類名獲取其值,也就是說即使沒有創建對象,也可以調用該變量。被static修飾的方法和變量在一些文獻中也叫類方法和類變量。

編譯運行的條件

1.安裝JDK並配置好環境變量(目的是能找到java和javac兩個命令)
2.javac Xxx.java
3.java Xxx

註釋和嵌入式文檔(關於如何寫註釋)

常見的單行 多行註釋不需多說,本小節主要說的是Javadoc這個註釋提取工具,在java文件中註釋以一定格式編寫,則使用javadoc可以提取註釋並生成另外的文檔,一般可以使用瀏覽器打開類似的文檔,撰寫註釋是務必遵守一些語法,還會使用到一些html標籤和@開頭的javadoc標籤(@See @link之類),在實際中其實用的比較少,但是我們可以看到我們使用的JDK的註釋中確實出現類似描述的東西。由於用的比較少,就不詳述了。

編碼風格

駝峯式爲主

本章主要講述要創建第一個Java程序需要的要素,比如項目構建需要創建包名,需要導包,需要創建方法,變量,使用靜態方法,如何註釋並通過javadoc生成html文檔等等

第三章 操作符(有些簡單的操作符就略過了)

別名機制問題引入

其實就是引用傳遞的問題,問題出現在賦值和方法調用上
比如

public class Book {
	
	public Book(String name) {
		super();
		this.name = name;
	}

	String name;
	
	private void changeString(String s) {
		s = "zzz";
	}
	
	private void changeString(Book book) {
		book.name = "zzz";
	}
	
	public static void main(String [] args){
		Book book1 = new Book("Java");
		Book book2 = new Book("Android");
		System.out.println(book1.name);
		System.out.println(book2.name);
		
		book1 = book2;//賦值產生的引用傳遞  這句是問題關鍵
		book1.name = "Java Script";
		System.out.println(book1.name);
		System.out.println(book2.name);
		
		book2 = book1;
		book1.name = "Python";
		System.out.println(book1.name);
		System.out.println(book2.name);
		System.out.println();
		
		//參數傳遞時的引用傳遞
		String s = "abc";
		book1.changeString(s);//調用changeString方法
		System.out.println(s);
		
		String bookName = "C++";
		Book book = new Book(bookName);
		book.changeString(book);//調用changeString(Book)方法
		System.out.println(bookName);
		System.out.println(book.name);
		//核心: 基本類型賦值和方法調用都不會發生引用傳遞,都是拷貝值,只有引用類型的變量纔有這兩個問題。
		//這裏應該時問題的引入,不理解也沒關係,第六章應該會有詳述
	}
}

輸出

Java
Android
Java Script
Java Script
Python
Python

abc
C++
zzz

關於運算符

其優先級不需要死記硬背,有時候不記得可以使用括弧,而且這樣程序的可讀性更高。
對於++i --i i++ i–之類的操作符也很簡單,不贅述

等價性

基本類型的等價性

直接用== !=判斷即可,比如

	public static void main(String[] args) {
		String string1 = "sd";
		String string2 = "sd";
		System.out.println(string1 == string2);
	}

輸出true

引用類型的等價性

對於Java已經定義好的引用類型(如Integer String Boolean)大多數可以使用equals來判斷內容是否相等

	public static void main(String[] args) {
		String string1 = new String("sd");
		String string2 = new String("sd");
		System.out.println(string1 == string2);
		System.out.println(string1.equals(string2));
		
		Boolean boolean1 = new Boolean(false);
		Boolean boolean2 = new Boolean(false);
		System.out.println(boolean1 == boolean2);
		System.out.println(boolean1.equals(boolean2));
		
		Integer integer1 = new Integer(15);
		Integer integer2 = new Integer(15);
		System.out.println(integer1 == integer2);
		System.out.println(integer1.equals(integer2));
	}

輸出

false
true
false
true
false
true

可以看到 “== ”用在引用類型上,輸出結果可能與想象的不同,這是因爲對於引用類型,“”判讀的是 對象的引用是否相等,當我們使用new創建對象時,java往往分配不同的堆棧和堆空間給不同的對象,因此對於引用類型,使用“”判斷往往返回false。
但並不是只要是引用類型,只要使用equals就可以判斷其內容是否等價。當比較對象類型是自定義類型時,需要我們覆蓋Object的equals方法纔可以正確比較,否則調用equals方法會默認調用Object的equals方法,而它的實現就是使用“==”來比較,這一點在第17章將會詳述。

邏輯操作符

與或非(&& || !)比較簡單,就略過了,但是書中也講述了&&和& 以及|| 和|的關係,實際應用時,似乎|和&很少使用,關鍵詞是短路,簡單,略過。

直接常量

此處沒搞明白,說是直接變量或許更好?這裏說的最多的一個是定義數字類型時加前綴後綴的問題,這個用的其實很少。另外一個是“窄化轉型”,在本章後面有講述。

指數計數法

這用的也特別少,這裏Think in Java還是很嚴謹的,詳述了Java中e與科學自然e的區別。

按位操作符

這個用的也比較少,一共四種 按位與& 按位或| 按位非~ 按位異或^,他們和邏輯操作符極爲類似。
一個例子

	public static void main(String[] args) {
		int i = 1 + 4 + 16 + 64;
		int j = 2 + 8 + 32 + 128;
		System.out.println("i = " + Integer.toBinaryString(i));
		System.out.println("j = " + Integer.toBinaryString(j));
		System.out.println("i & j = " + Integer.toBinaryString(i & j));
		System.out.println("i | j = " + Integer.toBinaryString(i | j));
		System.out.println("i ^ j = " + Integer.toBinaryString(i ^ j));
		System.out.println("~i = " + Integer.toBinaryString(~i));
		System.out.println("~j = " + Integer.toBinaryString(~j));
	}

結果

i =   1010101
j = 10101010
i & j = 0
i | j = 11111111
i ^ j = 11111111
~i = 11111111111111111111111110101010
~j = 11111111111111111111111101010101

移位操作符

分左移位操作符<< 右移位操作符>> 無符號右移>>> 這個我也不經常使用,其他書籍資料中講述位運算對於機器來說,其速度比普通的加減乘除快,所以很多地方爲了提高運算速度使用移位,比如左移一位一般是2 兩位4,三位*8……
這裏的移位和按位操作符結合起來,還是很複雜的,但其實用的不多,至於掌握到什麼程度,就看個人需要了。

三元操作符 ? :

就是if else的簡化版,只能由一個else,看起來代碼簡潔些。在Android studio中,如果可以使用但沒有使用三元操作符的地方會有警告,但書中以及個人也覺得if else的邏輯可讀性更高,何時使用三元操作符就看個人了

字符串拼接 +

這個用的就多了,不過也很簡單,就是如果一個表達式以字符串起頭,後面所有操作數都會自動轉成字符串類型
例子

	public static void main(String[] args) {
		int x = 1;
		int y = 3;
		String string = "string";
		System.out.println(x + y + string);
		System.out.println(x + string + y);
		System.out.println(string + x + y);
	}

輸出

4string
1string3
string13

關於Java數字類型的轉換

byte8位 short16位 int32位 long64位 float32位 double64位
char16位
日常賦值和計算時會有些類型的轉換,這種情況發生在
1.不同類型的變量之間的賦值
2.不同類型的變量之間的運算
第一點例如:

		int x =1;
		float y = 2.02f;
		System.out.println(x +" "+ y );
		
		y = x;
		System.out.println(x +" "+ y );
		y = (float)x;//強制轉換其實是不必要的,會自動轉換
		System.out.println(x +" "+ y );

第二點比如int類型與float類型運算,結果會是float類型
float類型與double類型運算,結果會是double類型

類型提升和強制轉換

將int值賦值給float變量,值會自動轉型成float類型,就像執行了強制轉換,這種自動的轉換在本書中稱爲
類型提升,即將一個小容器裏的東西放到大容器中。但反過來則不行
將float值賦值給int變量,會報錯,除非加上強制轉換,可以理解爲Java認爲這樣做會丟失精度,是不安全的,需要使用者自己知道。這就相當於將大容器的東西放到小容器,小容器有可能放不下,那麼只能把物體裁小(精度丟失),才能放入小容器,就出現了截尾。
比如將29.7賦值給int值,則最後的值是29
想要使用四捨五入要使用Math類的round方法

關於操作符的建議

1.如果不確定操作符的運算順序,就使用括弧,這樣可以提高可讀性和確保代碼正確性
2.(個人想法,不是書中建議)如果沒有必要,儘量避免不同類型數值之間的運算,雖然類型轉換有一定的規律可循,但是我們應當避免無意的錯誤或者精度丟失。這樣的錯誤往往是難以發現的。

操作符小結

char byte short運算時有時會被操作符進行類型提升(轉化成int值)之後需要自行窄化(強制轉換成原來的類型,可能信息丟失)
相同類型的數據進行運算時要注意可能的超出範圍
bool類型的數據在java中的功能很有限,很多在c c++中可以進行的操作在Java中是行不通的

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章