1. 程式人生 > >Java五道輸出易錯題解析(避免小錯誤)

Java五道輸出易錯題解析(避免小錯誤)

收集了幾個易錯的或好玩的Java輸出題,分享給大家,以後在程式設計學習中稍微注意下就OK了。

1. 看不見的空格?

下面的輸出會正常嗎?

package basic;

public class IntegerTest {

    public static void main(String[] args) {
        System.out.println(Integer.parseInt("1"));
        System.out.println(Integer.parseInt("2"));
 }
}


解析:將上面程式碼複製下(不要自己手敲)在自己的環境裡執行看看,是不是輸出下面錯誤來了:

1
Exception in thread “main” java.lang.NumberFormatException: For input string: “2”
at java.lang.NumberFormatException.forInputString(Unknown Source)
at java.lang.Integer.parseInt(Unknown Source)
at java.lang.Integer.parseInt(Unknown Source)
at basic.IntegerTest.main(IntegerTest.java:7)

竟然說第二條語句有問題,表面上完全看不出來任何問題是不是!
實際上這裡的錯誤原因涉及到一個概念 — 零寬度空格,可能有人接觸過,但相信更多的人甚至都沒聽過,什麼是零寬度空格?它實際上是一個Unicode字元,是一個空格,關鍵是它沒有寬度,因此我們一般肉眼看不到。但可以在vim下看到,上面的第二條語句中的2前面就有一個零寬度空格,放到vim中開啟後你會發現是下面這樣的語句:

System.out.println(Integer.parseInt("<feff>2"));


Unicode規範中定義,每一個檔案的最前面分別加入一個表示編碼順序的字元,這個字元的名字叫做”零寬度非換行空格“(ZEROWIDTHNO-BREAKSPACE),用FEFF

表示。這正好是兩個位元組,而且FF比FE大1。因此下面的語句會輸出65279,剛好是FEFF

System.out.println((int)"2".charAt(0));

2. 類靜態成員初始化

下面的程式能編譯通過麼?如果通過,說結果並解釋,不能編譯,說錯誤原因。

class A
{
    public static int X;
    static { X = B.Y + 1;}
}
public class B
{
    public static int Y = A.X + 1;
    static {}
    public static void main(String[] args) {
        System.out.println("X = "+A.X+", Y = "+B.Y);
    }
}


解析:這個程式能正確執行,類的執行過程如下:

首先載入主類B,初始化靜態成員Y,發現需要類A的資訊,於是載入類A,初始化靜態成員X,也用到B類資訊,由於此時B類的Y還未成功載入因此這裡是預設值0,從而得到A類的X為1,然後返回到B類,得到Y為2。

3. 裝箱拆箱的實際過程

關於自動裝箱,相信大部分人都明白是怎麼一回事,但真的完全明白了嘛?
先看下面的程式碼:

Short s1 = 1;
Short s2 = s1;
System.out.println(s1 == s2);


誰都知道當然列印true了。現在加一句試試:

Short s1 = 1;
Short s2 = s1;
s1++;
System.out.println(s1 == s2);


還是true嗎?No,這次輸出成了false。WHY?難道s1和s2引用的不是同一個物件嗎?有這些疑問的說明你對自動裝箱拆箱的過程還不是非常清楚,實際上上面的程式碼可以翻譯為下面的程式碼(實際執行過程,要掌握):

Short s1 = new Short((short)1);
Short s2 = s1;
short tempS1 = s1.shortValue();
tempS1++;
s1 = new Short(tempS1);
System.out.println(s1 == s2);


哦,原來如此,這下明白了,因此我們在使用自動裝箱的時候小心點為妙。

4. 你自以為是的異常

先來兩句程式碼:

NullTest myNullTest = null;
System.out.println(myNullTest.getInt());


相信很多人看到這段程式碼時,都會自以為是的說:NullPointerException。果真如此嗎?你還沒看到NullTest 這個類是如何定義的呢。現在看看這個類的定義:

class NullTest {
     public static int getInt() {
         return 1;
     }
}


發現getInt()方法體沒有任何類變數和類方法的使用,因此這裡會正常輸出1.
記住:類變數和類方法的使用,僅僅依賴引用的型別。即使引用為null,仍然可以呼叫。從良好實踐的角度來看,明智的做法是使用NullTest.getInt()來代替myNullTest.getInt(),但誰不不能保證不會碰到這樣的程式碼,因此還是小心為妙。

5. 變長引數和陣列,如何變通?

變長引數特性帶來了一個強大的概念,可以幫助開發者簡化程式碼。不過變長引數的背後是什麼呢?Basically,就是一個數組。

public void calc(int... myInts) {} 
calc(1, 2, 3);


編譯器會將前面的程式碼翻譯成類似這樣:

int[] ints = {1, 2, 3};
calc(ints);


不過這裡有兩點需要注意:
- 當心空呼叫語句,這相當於傳遞了一個null作為引數。
calc();
等價於
int[] ints = null;
calc(ints);
- 當然,下面的程式碼會導致編譯錯誤,因為兩條語句是等價的:
public void m1(int[] myInts) { … }
public void m1(int… myInts) { … }

參考資料