Java語言規範

Java基礎技術細節總結 - 語言規範

開發莫忘基礎,寫業務寫多了不少基礎內容容易忘。這裏將尋根溯源,總結Java語言規範和基礎類中的一些細節問題。全部關於Java語言規範的細節問題,均可以參考 The Java® Language Specification, Java SE 8 Edition(JLS8) .java

本文將不斷補充。。編程

小數化爲整數

  • Math.floor(x)返回小於等於x的最接近整數,返回類型爲double;
  • Math.round(x)至關於四捨五入,返回值爲long或int;
  • Math.ceil(x)返回大於等於x的最接近整數,返回類型爲double。

靜態塊與構造塊

靜態塊:用static申明,JVM加載類時執行,僅執行一次且優先於主函數。
構造塊:類中直接用{}定義,每一次建立對象時執行,至關於往構造器最前面加上構造塊的內容(很像往每一個構造器那裏插了內聯函數,構造塊就至關於內聯函數)。緩存

執行順序優先級:靜態塊 > 構造塊 > 構造方法併發

有繼承關係時,執行順序一般是:父類靜態塊=>子類靜態塊=>父類構造塊=>父類構造方法=>子類構造塊=>子類構造方法
測試:ide

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>

運算符規則 - 加法規則

代碼片斷:post

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,不會出現問題。ui

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。

相關文章
相關標籤/搜索