開發莫忘基礎,寫業務寫多了不少基礎內容容易忘。這裏將尋根溯源,總結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);
|
結果:第四行編譯錯誤。測試
表達式的數據類型自動提高, 關於類型的自動提高,注意下面的規則。優化
byte
,short
,char
型的值將被提高爲int
型long
型,計算結果是long
型float
型,計算結果是float
型double
型,計算結果是double
型而聲明爲final
的變量會被JVM優化,所以第三句在編譯時就會優化爲b6 = 10
,不會出現問題。ui
1
|
if (fabs(x) < 0.00001f)
|
float類型的還有double類型的,這些小數類型在趨近於0的時候不會直接等於零,通常都是無限趨近於0。所以不能用==來判斷。應該用|x-0| < err
來判斷,這裏|x-0|表示絕對值,err表示限定偏差,用程序表示就是fabs(x) < 0.00001f
。
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
觀察下面的一段代碼:
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
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
操做符只能用於對象,不能用於基本類型(不會自動拆包)。
下面是一些典型的例子:
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了
|
根據 JLS8 4.2.3,對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;
|
這個問題是在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" 轉載請保留原文連接及做者。