《Java深入解析-滲透java本質的36個話題》總結
浮點型別的種種懸疑
浮點型別只是近似的儲存,這很顯而易見,因為在計算機內部是使用二進位制來儲存數值的,而對於0.3333之類的奇數小數就沒辦法儲存,因此採用的是一種近似值的方式進行儲存的,所以就會造成一個誤差:下面程式碼將輸出false,因為對於浮點數運算會存在誤差:
double x = 0.1, y = 0.2, z = 0.3;
System.out.println(x+y==z);
//真正的0.1+0.2等於0.3000000004左右,後面會攜帶偏差值
System.out.println(x+y);
++i和i++解析(目前我看到的最好理解的講解方式)
首先,我們知道i++和i++的一個明顯的區別就是:前者先i先進行其他運算操作再自增,後者i先自增再進行其他運算操作。那麼如果對於下面這種情況呢?
int i = 1;
int y = 2;
i = i++;
y = ++y;
System.out.println(i);
System.out.println(j);
首先說一下他們的結果分別為:i=1,j=3; 我們可以將它改變成一個容易看懂的操作:
int i = l;
int y = i++;
那麼這個我們就能拆分成下面這個:
int i = 1;
int temp = i;
i++;
int y = temp;
最終y的值就是i,那麼i也同樣是這樣:
int i = 1;
int temp = i;
i++;
i = temp;
因此i最終還是為i,我們再來看y:
int y = 2;
y += 1;
y = y;
因此最終y自增了,可以看出,i++和++j的區別就在於前者建立了一個臨時的變數用於賦值操作,而其實兩者的自增操作都是發生再進行運算操作之前。
i+++j的運算結果(貪心規則)
按照編譯器的貪心規則來說,i最終匹配到自增運算,最終的結果為:
i++;
i+j;
貪心機制:編譯器再運算分析符號的時候,會盡可能多的結合有效的符號。我們知道,在進行轉義字元的時候,比如\431
它為什麼不匹配\43
,就是因為編譯器的貪心規則,不然就會永遠都無法正確的轉義\431
三元運算子的結果型別
三元運算子第一個為boolean型別,但是後面兩個表示式的型別可能不同,那麼當他們型別不同的時候,該運算子到底返回誰的型別呢?他們的具體判斷順序如下:
1.如果型別相同,則返回表示式1的型別
2.如果一個為基本型別,另一個為對應的包裝型別,則返回基本型別
3.如果一個為null,一個為引用型別,則返回引用型別
4.如果一個為null,一個為基本型別,則返回基本型別對應的包裝型別
+號連線字串的效能
在進行+來連線字串的時候,它會先建立一個臨時的StringBuilder物件,進行String物件連線之後,在呼叫toString方法轉化成String物件。
當使用+號連線的兩邊的字串為編譯時期就能確定的常量時,編譯器不會建立臨時StringBuilder物件,而會在編譯期就計算出該字串的值,不會造成效能開銷
String型別常量都是儲存在常量池裡面的,我們也可以呼叫字串的intern方法手動的將字串儲存到常量池中。這樣,當String編譯時期常量的值出現在常量池中 的時候,會直接返回常量池中的物件。intern方法返回值:如果常量池中有該物件,則返回常量池中 的該物件,否則返回自身
對於執行時建立的物件,會被分配到堆中,系統不會自動呼叫intern方法加入到常量池
特殊的main方法
我們通常都是通過run類來呼叫main方法,其實main方法也可以在其他方法中被呼叫:
public class Main_{
public static int i = 0;
public static void main(String[] args){
if(i==0){
main2();
System.out.println(0);
}
System.out.println(1);
}
public static void main2(){
i = 1;
main();
}
}
同樣,main方法也是可以繼承的,如果我們在Main2類中繼承Main類,那麼也可以直接啟動Main2;
我們知道,靜態方法是不能被重寫的,那麼是不是每次啟動Main2類都只能呼叫Main的main方法呢?答案肯定不是的,靜態方法雖然不能被重寫,但是可以被隱藏,也就是說,我們在Main2類中在定義main方法,那麼啟動Main2將是呼叫自身定義的main方法,而父類的main方法將被隱藏
過載時的方法選擇
方法過載時,類呼叫的方法在編譯時期就已經確定了,如下程式碼:
public class Main{
public static void a(String s){
System.out.println("a1");
}
public static void a(Object o){
System.out.println("a2");
}
public static void main(String[] args){
Object o = new String();
a(o);
}
}
最終呼叫的方法為a(Object o);呼叫過載方法時,過載方法的引數型別是根據引用的靜態型別來決定的,o的靜態引用型別為Object,那麼就呼叫a(Object o);
構造器
構造器其實並沒有建立物件,它只是負責物件的一系列屬性的初始化操作
當我們沒有定義構造器時,編譯器會自動生成一個空的預設無參構造器,這個構造器並不是一個空的方法,至少它呼叫了父類構造器進行父類初始化
this在哪裡
我們知道,當我們呼叫一個例項方法的時候,我們可以直接使用一個this物件,那麼該this物件是哪來的呢?其實,在構造器或者例項方法中,都含有一個隱藏的引數,該引數就是類的物件,當呼叫構造器或者例項方法時,就會將這個物件作為該引數傳進去,比如:
obj.test();
//實質上裡面包含一個隱含引數this,該this指向obj
obj.test(this);
Integer包裝型別的快取
Integer內部維護者一個快取陣列,預設的範圍是-128到127,當數值在這個範圍裡面時,會從快取裡面取物件,否則就會新建一個物件。其中-128快取下限是固定不能變得,而127上限可以通過修改系統屬性來改變:
java -Djava.lang.Integer.IntegerCache.high=200 Main
使用上述語句來啟動Main就會將Integer得內部快取上限更改至200
java的多維陣列
java的多維陣列其實是採用陣列的陣列的形式實現的,也就是說高維陣列的每一個元素都是一個低維陣列