Java編程思想
這是一個通過對《Java編程思想》(Think in java)第四版進行閱讀同時對java內容查漏補缺的系列。一些基礎的知識不會被羅列出來,這里只會列出一些程序員經常會忽略或者混淆的知識點。
所列知識點全部都是針對自己個人而言,同時也歡迎大家進行補充。
第一章(對象導論)
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello every one,I'm cpacm");
}
}
這一章是java的整體介紹,讓我們先熟悉了java是什么。其具體的內容都會在后面介紹。
面向基本語言的五大特性
- 萬物皆為對象。
- 程序是對象的集合,它們通過發送消息來告知彼此所要做的。
- 每個對象都擁有其類型。
- 某一特定類型的所有對象都可以接收同樣的消息。
第二章(一切都是對象)
一、存儲
p22
棧(堆棧):存放基本類型變量和對象引用變量。位於RAM區
堆:存放new得到的對象和數組。也位於RAM區
常量存儲:存放常量,包括靜態變量。
二、基本類型
p26
基本數據類型在沒有初始化的時候會獲得一個默認值。
基本數據類型 | 默認值 | 大小 |
---|---|---|
boolean | false | 未確定 |
char | null | 16bits |
byte | 0 | 8bits |
short | 0 | 16bits |
int | 0 | 32bits |
float | 0f | 32bits |
long | 0L | 64bits |
double | 0d | 64bits |
tip1:String不是基本數據類型
tip2:上面的初始默認值並不適用於方法內部變量。其默認初始化值未知。當使用未初始化的變量編譯器會返回錯誤
public class BaseType {
static boolean b;
static char c;
static byte bt;
static short s;
static int i;
static float f;
static long l;
static double d;
public static void main(String[] args) {
System.out.println("類變量——"+"boolean:"+b+" char:"+c+" byte:"+bt+" short:"+s+" int:"+i+" float:"+f+" long:"+l+" double:"+d);
}
}
//類變量——boolean:false char: byte:0 short:0 int:0 float:0.0 long:0 double:0.0
第三章(操作符)
一、別名現象
p40
當兩個變量包含的是同一個引用時,修改其中一個變量的值另一個變量的值也同時改變。記住new出來的對象的=賦值都是只傳遞引用。
Tip:減少為對象賦值。
class T{
int i;
}
public class Assigment {
public static void main(String[] args) {
T t1 = new T();
T t2 = new T();
t1.i = 5;
t2.i = 8;
t1 = t2;
t1.i = 10000;
System.out.println(t2.i);
}
}
//10000
二、equals方法
p45
在自定義的對象中使用equals方法時需要覆蓋此方法,否則默認是比較引用。
三、短路
p47
其現象本質為:當已經確定一個邏輯表達式的結果時不會再計算剩余的部分。
public class ShortCircuit {
public static void main(String[] args) {
// TODO Auto-generated method stub
boolean flag1 = test1()&&test2()||test3()&&test4();
System.out.println("\n");
boolean flag2 = test1()&&test3()||test2()&&test4();
}
static boolean test1(){
System.out.println("test1");
return true;
}
static boolean test2(){
System.out.println("test2");
return false;
}
static boolean test3(){
System.out.println("test3");
return true;
}
static boolean test4(){
System.out.println("test4");
return false;
}
}
/*
test1
test2
test3
test4
test1
test3
*/
boolean flag2 = test1()&&test3()||test2()&&test4();
若test1為true,test3為true時,因為前面這部分已經確定為true,所以后面部分不會被調用。
四、e
p49
科學與工程領域中,"e"代表自然對數的基數,為2.718。
而在C,C++和java(或者更多的語言)中,"e"代表“10的冪次”
$1.39e-43f = 1.39*10^{-43}$
五、位操作
p49
與,或,異或,非 &,|,^,~
與:所有的位都為1則輸出1,否則輸出0;
或:只要有一個位是1就輸出1;
異或:兩個位值相等時輸出1;
非:1輸出0,0輸出1.
位運算不會出現短路現象。
p50
移位操作符:
$<<$:操作數向左移動,低位補0;
$>>$:操作數向右移動,(1)符號為正時,高位補0,(2)符號為負時,高位補1;
$>>>$:java獨有操作符,操作數向右移動,高位統一補0。
char,byte,short進行移位操作時先會轉成int類型,即32位
public class URShift {
public static void main(String[] args) {
int i = 1024;
System.out.println(Integer.toBinaryString(i));
i >>= 10;
System.out.println(Integer.toBinaryString(i));
i = -1;
System.out.println(Integer.toBinaryString(i));
i >>>= 10;
System.out.println(Integer.toBinaryString(i));
i <<= 1;
System.out.println(Integer.toBinaryString(i));
short s = -1;
s >>>= 10;//s移位后得到的結果在賦值時會強行轉為int,所以移位后的s已經是int型
System.out.println(Integer.toBinaryString(s));
s = -1;
System.out.println(Integer.toBinaryString(s>>>10));
}
}
/*
10000000000
1
11111111111111111111111111111111
1111111111111111111111
11111111111111111111110
11111111111111111111111111111111
1111111111111111111111
*/
六、截尾
p55
將float或double轉型為整數值時,總是對數字進行截尾,不會進行四舍五入。如果想要得到舍入的結果可以使用Math.round()
public class CastingNumbers {
public static void main(String[] args) {
double d = 1.7d;
int i = (int)d;
System.out.println(i);
i = (int) Math.round(d);
System.out.println(i);
}
}
//1
//2
第四章(控制執行流程)
一、goto 標簽
goto關鍵詞,在java中則是使用標簽代替臭名昭著的goto。其實在java中也是最好不要用標簽來跳轉語句,太傷智商。。
寫法
outer:
並放在迭代語句前。
(1)continue 標簽
跳出所有循環,並到標簽位置的語句,但會重新進入緊接后面的循環里。
(2)break 標簽
跳出所有循環,且不再進入緊接后面的循環里。
public class LabeledFor {
public static void main(String[] args) {
System.out.println("測試continue————————————");
label:
for(int i=0;i<3;i++){
System.out.println("外部for循環"+i);
for(int j=0;j<3;j++){
if(j==1){
continue label;
}
System.out.println("內部循環"+j);
}
}
System.out.println("測試break————————————————");
label2:
for(int m=0;m<3;m++){
System.out.println("外部for循環"+m);
for(int n=0;n<3;n++){
if(n==1){
break label2;
}
System.out.println("內部循環"+n);
}
}
}
}
/*
測試continue————————————
外部for循環0
內部循環0
外部for循環1
內部循環0
外部for循環2
內部循環0
測試break————————————————
外部for循環0
內部循環0*/
第五章(初始化與清理)
一、重載
p81
重載主要以傳入參數及順序來區別。不能通過返回值來區別
以基本數據類型傳入時:自動包裝機制
(1)若傳入的數據類型小於方法中聲明的形式參數類型,實際數據類型就會提升。
byte->short->int->long->float->double
(2)如果傳入的實際參數較大,就得通過類型轉換執行窄化轉換。
二、垃圾回收機制
p91
標記-清掃(Android中使用這個技術)
從堆棧和存儲區出發,遍歷所有的引用,進而找出所有存活的對象,並給與標記。遍歷完成后,清理所有未被標記的對象。
停止-復制
先暫停程序的運行,然后將所有存活的對象復制到另一個堆,而沒有復制的是可回收的內存。
三、初始化順序
p94
所有的變量都會在任何方法(包括構造器)被調用之前得到初始化。
p95
無論創建多少對象,靜態數據都只占用一份存儲區域,static不能作用於局部變量。且靜態初始化動作只執行一次。
四、可變參數列表。
p102
void method(Object... args){}
調用 method();或method(new Object[]{1,2,3,4});
第七章(復用類)
一、toString()的自動調用
p126
有時候編譯器會自動幫你調用toString()方法。
"source"+ source;這時候會自動調用source對象的toString方法。
二、構造函數調用順序
p130
構造函數總是從基類開始。
class Insert{
Insert(){
System.out.println("Insert");
}
}
public class Beetle extends Insert{
Beetle(){
System.out.println("Beetle");
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Beetle b = new Beetle();
}
}
//Insert
//Beetle
三、final
p140
對於基本類型,final使數值恆定不變;而用於對象引用,final使引用恆定不變,但對象本身是可以改變的。
private 屬於 final 方法
static final是屬於類屬性,即能被類調用,不用實例化
final則需要實例化。
四、組合模式
組合模式可以看做是一顆樹,每個枝干都可以長出新的枝干,它們的結構都是相同的。
將枝干抽象為一個類,里面包含鏈接下一個節點的方法,若需要不斷的鏈接下一個節點只需要繼承這個方法並實現。
示例:導航菜單
組合模式,將對象組合成樹形結構以表示“部分-整體”的層次結構,組合模式使得用戶對單個對象和組合對象的使用具有一致性。
public abstract class Tab {
private String title;
public Tab(String title) {
this.title = title;
}
protected abstract void add(Tab tab);
protected abstract void romove(Tab tab);
}
public class CardTab extends Tab{
public CardTab(String title) {
super(title);
// TODO Auto-generated constructor stub
}
private List<Tab> tabs;
@Override
protected void add(Tab tab) {
// TODO Auto-generated method stub
tabs.add(tab);
}
@Override
protected void romove(Tab tab) {
// TODO Auto-generated method stub
tabs.remove(tab);
}
}
public class TabView {
public static void main(String[] args) {
// TODO Auto-generated method stub
CardTab rootTab = new CardTab("roottab");
CardTab tab1 = new CardTab("tab1");
CardTab tab2 = new CardTab("tab2");
CardTab tab3 = new CardTab("tab3");
rootTab.add(tab1);
rootTab.add(tab2);
rootTab.add(tab3);
CardTab tab4 = new CardTab("tab1-1");
CardTab tab5 = new CardTab("tab1-2");
tab1.add(tab4);
tab1.add(tab5);
}
}
/**
* 這樣子Tab組成了一個導航列表,這就是一個簡單的組合模式.
*/
第八章(多態)
多態是一項讓程序員“將改變的事物與未變的事物分離開來”的重要技術。
一、多態缺陷
p156
缺陷1:只有非private方法才可以被覆蓋
class Super{
public int field = 0;
public int getField(){return field;};
}
class Sub extends Super {
public int field = 1;
public int getField() { return field; }
public int getSuperField() { return super.field; }
}
public class FiledAccess {
public static void main(String[] args) {
Super sup = new Sub(); // Upcast
System.out.println("sup.field = " + sup.field +
", sup.getField() = " + sup.getField());
Sub sub = new Sub();
System.out.println("sub.field = " +
sub.field + ", sub.getField() = " +
sub.getField() +
", sub.getSuperField() = " +
sub.getSuperField());
}
}
輸出:
sup.field = 0, sup.getField() = 1
sub.field = 1, sub.getField() = 1, sub.getSuperField() = 0
缺陷2:域和靜態方法直接在編譯時候進行解析,所以多態不會對其產生作用。
class StaticSuper {
public static String staticGet() {
return "Base staticGet()";
}
public String dynamicGet() {
return "Base dynamicGet()";
}
}
class StaticSub extends StaticSuper {
public static String staticGet() {
return "Derived staticGet()";
}
public String dynamicGet() {
return "Derived dynamicGet()";
}
}
public class StaticPolymorphism {
public static void main(String[] args) {
StaticSuper sup = new StaticSub(); // Upcast
System.out.println(sup.staticGet());
System.out.println(sup.dynamicGet());
}
} /* Output:
Base staticGet()
Derived dynamicGet() */
二、斷言
p153
@Override作用:
斷言,如果我們使用了這種annotation在一個沒有覆蓋父類方法的方法時,java編譯器將以一個編譯錯誤來警示
三、構造器構造順序
p158
構造器在多態時的構造順序:
class Meal {
Meal() {
P.print("Meal()");
}
}
class Bread {
Bread() {
P.print("Bread()");
}
}
class Cheese {
Cheese() {
P.print("Cheese()");
}
}
class Lettuce {
Lettuce() {
P.print("Lettuce()");
}
}
class Lunch extends Meal {
Lunch() {
P.print("Lunch()");
}
}
class PortableLunch extends Lunch {
PortableLunch() {
P.print("PortableLunch()");
}
}
public class Sandwich extends PortableLunch {
private Bread b = new Bread();
private Cheese c = new Cheese();
private Lettuce l = new Lettuce();
public Sandwich() {
P.print("Sandwich()");
}
public static void main(String[] args) {
new Sandwich();
}
}
class P {
public static void print(String s){
System.out.println(s);
}
}
輸出:
Meal()
Lunch()
PortableLunch()
Bread()
Cheese()
Lettuce()
Sandwich()
解釋說明:
一個繼承類實例化的時候必須要確保所使用的成員已經構建完畢,所以必須先調用基類的構造器,所以當實例化Sandwich對象時先調用其基類的構造方法:
Meal()
Lunch()
PortableLunch()
其次對成員變量進行初始化
Bread()
Cheese()
Lettuce()
最后調用構造器
Sandwich()
三、構造器初始化
p163
初始化的過程:
(1)在所有事物發生之前,將分配給對象的存儲空間初始化為二進制的零。
(2)調用基類構造器。
(3)按照聲明順序調用成員的初始化方法。
(4)調用導出類(本體)的構造器主體。