久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

Java戲法

 ctbtcol 2014-11-05

我們經(jīng)常遇到這樣的情況,,有些代碼的行為出乎意料。Java語言有很多奇怪的地方,,即使有經(jīng)驗(yàn)的開發(fā)者也可能會(huì)感到意外,。

老實(shí)說,經(jīng)常有資歷較淺的同事來問,,“執(zhí)行這段代碼有什么樣的結(jié)果,?”,讓人措手不及,?!拔铱梢愿嬖V你,但是如果你自己找出答案,,學(xué)到的會(huì)更多”,,這是很常見的答復(fù)?,F(xiàn)在可別這么說了,可以先吸引一下他的注意力(哦……我想我看到安吉麗娜·朱莉了,,藏在我們的構(gòu)建服務(wù)器后面呢,,你可以快去看一下嗎,?),,利用這個(gè)時(shí)間,快速過一下這篇文章吧,。

本文將介紹一些Java的奇怪之處,,以幫助開發(fā)者做好更充分的準(zhǔn)備,使他們再遇到結(jié)果令人意外的代碼時(shí),,能夠很好地應(yīng)對,。

對于每個(gè)技巧,,我們都會(huì)提供一些看似簡單的代碼,但是這段代碼在編譯時(shí)或運(yùn)行時(shí)的行為就不那么直觀了,。表現(xiàn)如何,,為什么會(huì)這樣,我們會(huì)講清楚背后的原理,。這些例子的復(fù)雜性不同,,有的非常簡單,有的則很費(fèi)腦細(xì)胞,。

不可理喻的標(biāo)識(shí)符

我們很熟悉定義合法的Java標(biāo)識(shí)符的規(guī)則:

  • 一個(gè)標(biāo)識(shí)符是由一個(gè)或多個(gè)字符(可以是字母,、數(shù)字、$或下劃線)組成的集合,。
  • 標(biāo)識(shí)符必須以字母,、$或下劃線開頭。
  • Java關(guān)鍵字不能用作標(biāo)識(shí)符,。
  • 標(biāo)識(shí)符中的字符沒有數(shù)量限制,。
  • 也可以使用從\u00c0到\ud7a3之間的Unicode字符。

規(guī)則非常簡單,,但有些有趣的例子會(huì)讓人驚訝,。比如,開發(fā)者可以將類名用作標(biāo)識(shí)符,,這是沒有限制的:

//類名可以用作標(biāo)識(shí)符
String String = "String"; 
Object Object = null; 
Integer Integer = new Integer(1); 
//讓代碼難以理解怎么樣,? 
Float Double = 1.0f; 
Double Float = 2.0d; 
if (String instanceof String) {
      if (Float instanceof Double) {
          if (Double instanceof Float) {
                System.out.print("Can anyone read this code???");
            }
      }
 }

下面的標(biāo)識(shí)符也都是合法的:

int $ =1;
int € = 2;
int £ = 3;
int _ = 4;
long $€£ = 5;
long €_£_$ = 6;
long $€£$€£$€£$€£$€£$€£$€_________$€£$€£$€£$€£$€£$€£$€£$€£$€£_____ = 7;

此外,,請記住,同樣的名字可以同時(shí)用于變量和標(biāo)簽,。通過分析上下文,,編譯器知道引用的是哪一個(gè)。

int £ = 1;
£: for (int € = 0; € < £; €++) {
     if (€ == £) {
         break £;
     }
}

當(dāng)然,,不要忘了標(biāo)識(shí)符的規(guī)則可以應(yīng)用于變量名,、方法名、標(biāo)簽和類名:

class $ {} 
interface _ {} 
class € extends $ implements _ {}

所以我們學(xué)到了很厲害的一招,,那就是可以編寫沒有人能理解的代碼,,包括我們自己!

NullPointerException從何而來,?

自動(dòng)裝箱是在Java 5中引入的,,給我們帶來了很多方便,我們不用在基本類型和其包裝器類型之間跳來跳去了:

int primitiveA = 1;
Integer wrapperA = primitiveA;
wrapperA++;
primitiveA = wrapperA;

運(yùn)行時(shí)并沒有為了支持這種變化而做修改,,大部分工作都是編譯時(shí)完成的,。對于前面這段代碼,編譯器會(huì)生成類似下面這樣的代碼:

int primitiveA = 1;
Integer wrapperA = new Integer(primitiveA);
int tmpPrimitiveA = wrapperA.intValue();
tmpPrimitiveA++;
wrapperA = new Integer(tmpPrimitiveA);
primitiveA = wrapperA.intValue(); 

前面的自動(dòng)裝箱也可以應(yīng)用于方法調(diào)用:

public static int calculate(int a) {
     int result = a + 3;
     return result;
}
public static void main(String args[]) {
     int i1 = 1;
     Integer i2 = new Integer(1);
     System.out.println(calculate(i1));
     System.out.println(calculate(i2));
}

真棒,,對于以基本類型為參數(shù)的方法,,我們可以向其傳遞相應(yīng)的包裝器類型,讓編譯器來執(zhí)行變換:

public static void main(String args[]) {
     int i1 = 1;
     Integer i2 = new Integer(1);
     System.out.println(calculate(i1));
     int i2Tmp = i2.intValue();
     System.out.println(calculate(i2Tmp));
} 

稍作修改,,再來試試:

public static void main(String args[]) {
     int i1 = 1;
     Integer i2 = new Integer(1);
     Integer i3 = null;
     System.out.println(calculate(i1));
     System.out.println(calculate(i2));
     System.out.println(calculate(i3));
}

和前面一樣,,這段代碼會(huì)被翻譯成:

public static void main(String args[]) {
     int i1 = 1;
     Integer i2 = new Integer(1);
Integer i3 = null;
     System.out.println(calculate(i1));
     int i2Tmp = i2.intValue();
     System.out.println(calculate(i2Tmp));
     int i3Tmp = i3.intValue();
     System.out.println(calculate(i3Tmp));
}

當(dāng)然,這段代碼會(huì)讓我們看到老朋友NullPointerException,。像下面這種更簡單的情況,,同樣如此:

public static void main(String args[]) {
     Integer iW = null;
     int iP = iW;
}

所以在使用自動(dòng)拆箱時(shí)一定要非常小心,它可能導(dǎo)致NullPointerException,;而在該特性引入之前,,是不可能遇到此類異常的。更糟糕的是,,識(shí)別這些代碼模式有時(shí)并不容易,。如果必須將一個(gè)包裝器類型的變量轉(zhuǎn)換成基本類型變量,而且不確定其是否可能為null,,那就要為代碼做好保護(hù)措施,。

包裝器類型遭遇同一性危機(jī)

繼續(xù)自動(dòng)裝箱這個(gè)話題,看一下下面的代碼:

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

當(dāng)然打印true了?,F(xiàn)在來點(diǎn)有趣的:

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

輸出成了false,。等等,什么情況,?難道s1和s2引用的不是同一個(gè)對象嗎,?JVM真是瘋了,!還是用前面提到的代碼翻譯機(jī)制來看看吧:

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

哦……這么看是更合理了,不是嗎,?使用自動(dòng)裝箱的時(shí)候總得小心,!

媽媽快看,沒有異常,!

下面這個(gè)非常簡單,,但是很多有經(jīng)驗(yàn)的Java開發(fā)者都會(huì)中招。閑話少說,,看代碼:

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

當(dāng)看到這段代碼時(shí),,很多人會(huì)以為會(huì)出現(xiàn)NullPointerException,。果真如此嗎?看看其余代碼再說:

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

永遠(yuǎn)記住,,類變量和類方法的使用,僅僅依賴引用的類型,。即使引用為null,,仍然可以調(diào)用,。從良好實(shí)踐的角度來看,明智的做法是使用NullTest.getInt()來代替myNullTest.getInt(),,但鬼知道什么時(shí)候會(huì)碰上這樣的代碼。

變長參數(shù)和數(shù)組,,必要的變通

變長參數(shù)特性帶來了一個(gè)強(qiáng)大的概念,可以幫助開發(fā)者簡化代碼,。不過變長參數(shù)的背后是什么呢?不多不少,,就是一個(gè)數(shù)組,。

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

編譯器會(huì)將前面的代碼翻譯成類似這樣:

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

當(dāng)心空調(diào)用語句,,這相當(dāng)于傳遞了一個(gè)null作為參數(shù)。

calc();
等價(jià)于
int[] ints = null;
calc(ints);

當(dāng)然,,下面的代碼會(huì)導(dǎo)致編譯錯(cuò)誤,,因?yàn)閮蓷l語句是等價(jià)的:

public void m1(int[] myInts) { ...    } 
public void m1(int... myInts) { ...    }

可變的常量

大部分開發(fā)者認(rèn)為,當(dāng)變量定義中出現(xiàn)final關(guān)鍵字時(shí),,指示的就是一個(gè)常量,也就是說,,這個(gè)變量的值不可改變,。這并不完全正確,,當(dāng)final關(guān)鍵字應(yīng)用于變量時(shí),只是說明該變量只能賦值一次,。

class MyClass {
     private final int myVar;
     private int myOtherVar = getMyVar();
     public MyClass() {
         myVar = 10;
     }
     public int getMyVar() {
         return myVar;
     }
     public int getMyOtherVar() {
         return myOtherVar;
     }
     public static void main(String args[]) {
         MyClass mc = new MyClass();
         System.out.println(mc.getMyVar());
         System.out.println(mc.getMyOtherVar());
     }
}

前面的代碼將打印10 0。因此,,在處理final變量時(shí),,必須區(qū)分兩種情況:一種是在編譯時(shí)就賦了默認(rèn)值的,這種就是常量,;另一種是在運(yùn)行時(shí)初始化的,。

覆蓋的特色

請記住,從Java 5開始,,覆蓋方法的返回類型可以與被覆蓋方法不同,。唯一的規(guī)則是,覆蓋方法的返回類型是被覆蓋方法的返回類型的子類型,。所以在Java 5中下面的代碼成了合法的:

class A {
     public A m() {
         return new A();     
}
} 

class B extends A {
     public B m() {
         return new B();
     }
}

重載操作符

就操作符重載而言,,Java不是特別強(qiáng),但它確實(shí)支持+操作符的重載,。該操作符可以用于算術(shù)加法和字符串連接,,具體取決于上下文。

int val = 1 + 2;
String txt = "1" + "2";

當(dāng)字符串中混入了數(shù)值類型,,事情就復(fù)雜了,。但是規(guī)則很簡單,在遇到字符串操作數(shù)之前,,會(huì)一直執(zhí)行算術(shù)加法。一出現(xiàn)字符串,,兩個(gè)操作數(shù)都會(huì)被轉(zhuǎn)為字符串(如果需要的話),,并執(zhí)行一次字符串連接。下面例子說明了不同的組合:

System.out.println(1 + 2); //執(zhí)行加法,打印3 

System.out.println("1" + "2"); //執(zhí)行字符串連接,,打印12
System.out.println(1 + 2 + 3 + "4" + 5); //執(zhí)行加法,,直到發(fā)現(xiàn)"4",然后執(zhí)行字符串連接,,打印645

System.out.println("1" + "2" + "3" + 4 + 5); //執(zhí)行字符串連接,,打印12345

奇怪的日期格式

這個(gè)花招與DateFormat的實(shí)現(xiàn)有關(guān),其使用方式有一定的誤導(dǎo)性,,而且有的時(shí)候,,代碼到了產(chǎn)品中,問題才會(huì)暴露出來,。

DateFormatparse方法會(huì)解析一個(gè)字符串,,并生成一個(gè)日期。解析過程是根據(jù)定義的日期格式掩碼來工作的,。根據(jù)JavaDoc,如果指定的字符串的開頭部分無法解析,,會(huì)拋出一個(gè)ParseException。這個(gè)定義很模糊,,可以有不同的解釋。大部分開發(fā)者認(rèn)為,,如果字符串參數(shù)與定義的格式不匹配,,會(huì)拋出ParseException。但情況并非總是如此,。

對于SimpleDateFormat,,大家應(yīng)該非常小心。當(dāng)面對下面的代碼時(shí),,大部分開發(fā)者認(rèn)為會(huì)拋出ParseException,。

String date = "16-07-2009";

SimpleDateFormat sdf = new SimpleDateFormat("ddmmyyyy");
try {     
Date d = sdf.parse(date);
     System.out.println(DateFormat.getDateInstance(DateFormat.MEDIUM,
                     new Locale("US")).format(d));
} catch (ParseException pe) {
     System.out.println("Exception: " + pe.getMessage());
}

運(yùn)行這段代碼,會(huì)產(chǎn)生下列輸出:Jan 16, 0007,。真是奇怪,,編譯器竟然沒有指出字符串與預(yù)期的格式不匹配,,而是繼續(xù)處理,而且盡其最大努力來解析文本,。請注意,,這里有兩個(gè)隱藏的花招。其一,,月份的掩碼是MM,,而mm用于分鐘,,這就解釋了為什么月份被設(shè)置成了一月。其二,,DecimalFormat類的parse方法將一直解析文本,,直到遇到無法解析的字符,返回的是到目前這個(gè)位置已經(jīng)處理過的數(shù)字,。所以“7-20”將翻譯成7年,。這種差異很容易看出來,但如果使用的是“yyyymmdd”,,情況就更復(fù)雜了,,輸出將是“Jan 7, 0016”。解析“16-0”,,直到遇到第一個(gè)不可解析的字符,,所以16會(huì)被當(dāng)成年份?!?0”不會(huì)影響結(jié)果,,它會(huì)被理解為0分鐘,。之后“7-”就被映射到天數(shù)了。

譯者注:文中關(guān)于自動(dòng)裝箱的說明不夠準(zhǔn)確,,像“Integer wrapperA = primitiveA;”這條語句,,編譯器的處理策略是將其映射為“Integer wrapperA = Integer.valueOf(primitive);”,Short的處理類似,。有興趣的讀者可以自行測試。

另外,,對Java謎題感興趣的讀者可以閱讀Joshua Bloch的《Java解惑》一書,其中列出了很多容易出錯(cuò)的地方,。

關(guān)于作者

Paulo Moreira是葡萄牙的一位自由軟件工程師,目前在盧森堡的財(cái)政部門工作,。他畢業(yè)于米尼奧大學(xué),獲得了計(jì)算機(jī)科學(xué)和系統(tǒng)工程的碩士學(xué)位,。從2001年起,,他一直使用Java,,從事服務(wù)器端的開發(fā)工作,涉及電信,、零售,、軟件和金融市場等領(lǐng)域,。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式,、誘導(dǎo)購買等信息,,謹(jǐn)防詐騙,。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報(bào),。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請遵守用戶 評(píng)論公約

    類似文章 更多