1. 程式人生 > >Java語言規範

Java語言規範

構造方法 tro spa row con value 類型轉換 ann 所有

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() {
System.out.println("Base::Base<init>"); } } class Derived extends Base { static { System.out.println("fucking => Derived::static"); } { System.out.println("fucking => Derived::before"); } public Derived() { super(); System.out.println("Derived::Derived<init>"); } }

輸出:

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);

結果:第四行編譯錯誤。

表達式的數據類型自動提升, 關於類型的自動提升,註意下面的規則。

  1. 所有的byte,short,char型的值將被提升為int
  2. 如果有一個操作數是long型,計算結果是long
  3. 如果有一個操作數是float型,計算結果是float
  4. 如果有一個操作數是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));

結果是測試不通過,這裏就涉及到三元操作符的轉換規則:

  1. 如果兩個操作數無法轉換,則不進行轉換,返回Object對象
  2. 如果兩個操作數是正常的類型,那麽按照正常情況進行類型轉換,比如int => long => float => double
  3. 如果兩個操作數都是字面量數字,那麽返回範圍較大的類型

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的處理流程:

  1. JVM把count值(其值是0)拷貝到臨時變量區。
  2. count值加1,這時候count的值是1。
  3. 返回臨時變量區的值,註意這個值是0,沒有修改過。
  4. 返回值賦值給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 { @Test 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語言規範