第一章對象導論(Java的幾個重要部分)
訪問控制的目的:
1.權限控制 2.類創建者修改某些實現而不會影響類使用者
代碼複用的方式:
1.繼承 2.組合(composition UML中實心菱形+實線表示)
繼承
(UML中空心三角+實心線表示)
基類與子類行爲不同的產生方式
- 爲子類新增方法
- 在子類覆蓋(overriding)基類的原來的方法
兩種關係
- 是一個 is-a 純粹替代(子類和父類接口方法完全相同,子類的接口 方法可能在父類存在,反之亦然)
- 像是一個(子類和父類不完全相同,子類的接口 方法可能在父類不存在,下圖爲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中是行不通的