Java語言規範
Java基礎技術細節總結 - 語言規範
Java開發莫忘基礎,寫業務寫多了很多基礎內容容易忘。這裏將尋根溯源,總結Java語言規範和基礎類中的一些細節問題。所有關於Java語言規範的細節問題,都可以參考 The Java? Language Specification, Java SE 8 Edition(JLS8) .
本文將不斷補充。。
小數化為整數
Math.floor(x)
返回小於等於x的最接近整數,返回類型為double;Math.round(x)
相當於四舍五入,返回值為long或int;Math.ceil(x)
返回大於等於x的最接近整數,返回類型為double。
靜態塊與構造塊
靜態塊:用static申明,JVM加載類時執行,僅執行一次且優先於主函數。
構造塊:類中直接用{}定義,每一次創建對象時執行,相當於往構造器最前面加上構造塊的內容(很像往每個構造器那裏插了內聯函數,構造塊就相當於內聯函數)。
執行順序優先級:靜態塊 > 構造塊 > 構造方法
有繼承關系時,執行順序通常是:父類靜態塊=>子類靜態塊=>父類構造塊=>父類構造方法=>子類構造塊=>子類構造方法
測試:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
public class test {
public static void main(String[] args) {
new Derived();
}
}
class Base {
static {
System.out.println("fucking => Base::static");
}
{
System.out.println("fucking => Base::before");
}
public Base() { |
輸出:
1 2 3 4 5 6 | fucking => Base::static fucking => Derived::static fucking => Base::before Base::Base<init> fucking => Derived::before Derived::Derived<init> |
運算符規則 - 加法規則
代碼片段:
1 2 3 4 5 | byte b1 = 1, b2 = 2, b3, b6; final byte b4 = 4, b5 = 6; b6 = b4 + b5; b3 = (b1 + b2); System.out.println(b3 + b6); |
結果:第四行編譯錯誤。
表達式的數據類型自動提升, 關於類型的自動提升,註意下面的規則。
- 所有的
byte
,short
,char
型的值將被提升為int
型 - 如果有一個操作數是
long
型,計算結果是long
型 - 如果有一個操作數是
float
型,計算結果是float
型 - 如果有一個操作數是
double
型,計算結果是double
型
而聲明為final
的變量會被JVM優化,因此第三句在編譯時就會優化為b6 = 10
,不會出現問題。
float x 與“零值”比較的if語句
1 | if (fabs(x) < 0.00001f) |
float類型的還有double類型的,這些小數類型在趨近於0的時候不會直接等於零,一般都是無限趨近於0。因此不能用==來判斷。應該用|x-0| < err
來判斷,這裏|x-0|表示絕對值,err表示限定誤差,用程序表示就是fabs(x) < 0.00001f
。
關於try和finally
1.首先執行到try
裏的return
,但是有finally
語句還要執行,於是先執行return
後面的語句,例如(x++
),把要返回的值保存到局部變量。
2.執行finally
語句的內容,其中有return
語句,這時就會忽略try中的return
,直接返回。
返回值問題。可以認為try
(或者catch
)中的return
語句的返回值放入線程棧的頂部:如果返回值是基本類型則頂部存放的就是值,如果返回值是引用類型,則頂部存放的是引用。finally中的return
語句可以修改引用所對應的對象,無法修改基本類型。但不管是基本類型還是引用類型,都可以被finally
返回的“具體值”具體值覆蓋。
三目運算符的類型轉換問題
三目運算符裏的類型必須一致,比如下面的代碼:
1 2 3 4 | int i = 40; String as_e1 = String.valueOf(i < 50 ? 233 : 666); String as_e2 = String.valueOf(i < 50 ? 233 : 666.0); assertEquals(true, as_e1.equals(as_e2)); |
結果是測試不通過,這裏就涉及到三元操作符的轉換規則:
- 如果兩個操作數無法轉換,則不進行轉換,返回
Object
對象 - 如果兩個操作數是正常的類型,那麽按照正常情況進行類型轉換,比如
int => long => float => double
- 如果兩個操作數都是字面量數字,那麽返回範圍較大的類型
Java中自增操作符的一些陷阱
觀察下面的一段代碼:
1 2 3 4 5 6 7 8 9 10 | public class AutoIncTraps { public static void main(String[] args) { int count = 0; for(int i = 0; i < 10; i++) { count = count++; } System.out.println(count); } } |
這段代碼的打印結果是0
,也就是說自增在這裏並沒有什麽卵用,這和C++是不一樣的。反編譯一下看一下字節碼(main函數部分):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | public static main([Ljava/lang/String;)V L0 LINENUMBER 6 L0 ICONST_0 ISTORE 1 L1 LINENUMBER 7 L1 ICONST_0 ISTORE 2 L2 FRAME APPEND [I I] ILOAD 2 BIPUSH 10 IF_ICMPGE L3 L4 LINENUMBER 8 L4 ILOAD 1 IINC 1 1 ISTORE 1 L5 LINENUMBER 7 L5 IINC 2 1 GOTO L2 L3 LINENUMBER 10 L3 FRAME CHOP 1 GETSTATIC java/lang/System.out : Ljava/io/PrintStream; ILOAD 1 INVOKEVIRTUAL java/io/PrintStream.println (I)V L6 LINENUMBER 11 L6 RETURN |
這裏相當於創建了一個局部變量存放count++
,但沒有返回,因此count
相當於沒變。看了字節碼後可能沒感覺,寫一下編譯器處理後的代碼吧:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public class AutoIncTraps { public AutoIncTraps() { } public static void main(String[] args) { byte count = 0; for(int i = 0; i < 10; ++i) { int var3 = count + 1; count = count; } System.out.println(count); } } |
總結一下這裏count
的處理流程:
- JVM把count值(其值是0)拷貝到臨時變量區。
- count值加1,這時候count的值是1。
- 返回臨時變量區的值,註意這個值是0,沒有修改過。
- 返回值賦值給count,此時count值被重置成0。
單純看這一個的字節碼比較抽象,來看一下這三句的字節碼,比較一下更容易理解:
1 2 3 | count = ++count; count = count++; count++; |
字節碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 | L4 LINENUMBER 9 L4 IINC 1 1 ILOAD 1 ISTORE 1 L5 LINENUMBER 10 L5 ILOAD 1 IINC 1 1 ISTORE 1 L6 LINENUMBER 11 L6 IINC 1 1 |
另外,自增操作不是原子操作,在後邊總結並發編程的時候會涉及到。
instanceof操作符的註意事項
instanceof
操作符左右兩邊的操作數必須有繼承或派生關系,否則不會編譯成功。因此,instanceof
操作符只能用於對象,不能用於基本類型(不會自動拆包)。
下面是一些典型的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | public class FuckingIOF { public void test() { List<Object> list = new ArrayList<>(); list.add("String" instanceof Object); list.add(new String() instanceof Object); list.add(new Object() instanceof String); //list.add(‘a‘ instanceof Character); //此句會編譯錯誤 list.add(null instanceof String); list.add((String)null instanceof String); list.add(null instanceof Object); list.add(new Generic<String>().isDataInstance("")); list.forEach(System.out::println); } } class Generic<T> { public boolean isDataInstance(T t) { return t instanceof Date; } } |
運行結果和分析:
1 2 3 4 5 6 7 | true => String是Object的子類 true => 同上 false => 同上 false => Java語言規範規定null instanceof ? 都是false false => 同上,無論怎麽轉換還是null false => 同上 false => 由於Java泛型在編譯時會進行類型擦除,因此這裏相當於Object instanceof Date了 |
詭異的NaN類型
根據 JLS8 4.2.3,對NaN
有以下規定:
- The numerical comparison operators < , <= , > , and >= return false if either or both operands are NaN (§15.20.1).
- The equality operator == returns false if either operand is NaN.
- In particular, (x<y) =="!(x">=y) will be false if x or y is NaN.
- The inequality operator != returns true if either operand is NaN (§15.21.1).
- In particular, x!=x is true if and only if x is NaN.
註意到Double.NaN == Double.NaN
返回false,這其實是遵循了IEEE 754 standard。NaN 代表一個非正常的數(比如除以0得到的數),其定義為:
1 2 3 4 5 6 | /** * A constant holding a Not-a-Number (NaN) value of type * {@code double}. It is equivalent to the value returned by * {@code Double.longBitsToDouble(0x7ff8000000000000L)}. */ public static final double NaN = 0.0d / 0.0; |
Integer類的靜態緩存 && valueOf和parseInt的對比
這個問題是在StackOverflow上看到的。以下三個表達式:
1 2 3 | System.out.println(Integer.valueOf("127") == Integer.valueOf("127")); System.out.println(Integer.valueOf("128") == Integer.valueOf("128")); System.out.println(Integer.parseInt("128") == Integer.valueOf("128")); |
結果分別是:
1 2 3 | true false true |
為什麽是這樣的結果呢?我們看一下valueOf方法的源碼:
1 2 3 4 5 6 7 8 9 | public static Integer valueOf(String s) throws NumberFormatException { return Integer.valueOf(parseInt(s, 10)); } public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); } |
可以看到valueOf
方法是在parseInt
方法的基礎上加了一個讀取緩存的過程。我們再看一下IntegerCache類的源碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | /** * Cache to support the object identity semantics of autoboxing for values between * -128 and 127 (inclusive) as required by JLS. * * The cache is initialized on first usage. The size of the cache * may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option. * During VM initialization, java.lang.Integer.IntegerCache.high property * may be set and saved in the private system properties in the * sun.misc.VM class. */ private static class IntegerCache { static final int low = -128; static final int high; static final Integer cache[]; static { // high value may be configured by property int h = 127; String integerCacheHighPropValue = sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); if (integerCacheHighPropValue != null) { try { int i = parseInt(integerCacheHighPropValue); i = Math.max(i, 127); // Maximum array size is Integer.MAX_VALUE h = Math.min(i, Integer.MAX_VALUE - (-low) -1); } catch( NumberFormatException nfe) { // If the property cannot be parsed into an int, ignore it. } } high = h; cache = new Integer[(high - low) + 1]; int j = low; for(int k = 0; k < cache.length; k++) cache[k] = new Integer(j++); // range [-128, 127] must be interned (JLS7 5.1.7) assert IntegerCache.high >= 127; } private IntegerCache() {} } |
原來JVM會緩存一部分的Integer對象(默認範圍為-128 - 127
),在通過valueOf
獲取Integer對象時,如果是緩存範圍內的就直接返回緩存的Integer對象,否則就會new一個Integer對象。返回的上限可通過JVM的參數-XX:AutoBoxCacheMax=<size>
設置,而且不能小於127(參照JLS 5.1.7)。這樣我們就可以解釋Integer.valueOf("127") == Integer.valueOf("127")
為什麽是true了,因為它們獲取的都是同一個緩存對象,而默認情況下Integer.valueOf("128") == Integer.valueOf("128")
等效於new Integer(128) == new Integer(128)
,結果自然是false。
我們再來看一下parseInt
方法的原型,它返回一個原生int值:
1 | public static int parseInt(String s) throws NumberFormatException |
由於一個原生值與一個包裝值比較時,包裝類型會自動拆包,因此Integer.parseInt("128") == Integer.valueOf("128")
就等效於128 == 128
,結果自然是true。
本文標題:Java基礎技術細節總結 - 語言規範
文章作者:sczyh30
發布時間:2015年09月18日
原始鏈接:http://www.sczyh30.com/posts/Java/java-basic-summary-01/
許可協議: "知識共享-保持署名-非商用-相同方式共享 4.0" 轉載請保留原文鏈接及作者。
Java語言規範