收集了幾個易錯的或好玩的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) { … }