Core Java 總結(數據類型,表達式問題)

2016-10-18 整理html


寫一個程序判斷整數的奇偶java

public static boolean isOdd(int i){
  return i % 2 == 1;
}

百度百科定義:奇數(英文:odd)數學術語 ,口語中也稱做單數, 整數中,能被2整除的數是偶數,不能被2整除的數是奇數,奇數個位爲1,3,5,7,9。偶數可用2k表示,奇數可用2k+1表示,這裏k就是整數。奇數能夠分爲:

正奇數:一、三、五、七、九、十一、1三、1五、1七、1九、2一、2三、2五、2七、2九、3一、33.........
負奇數:-一、-三、-五、-七、-九、-十一、-1三、-1五、-1七、-1九、-2一、-23.-2五、-2七、-2九、-3一、-33.........
奇數能夠被定義爲被2 整除餘數爲1 的整數。可是在 int 數值中,有一半都是負數,而 isOdd 方法對於對全部負奇數的判斷都會失敗。在任何負整數上調用該方法都回返回false ,無論該整數是偶數仍是奇數。正確寫法:

public static boolean isOdd(int i){
  return i % 2 != 0;
}

用位操做符AND(&)來替代對2的取餘操做符會更好,注意優先級。

public static boolean isOdd(int i){
  return (i & 1) != 0;
}
解析

在jdk1.5+的環境下,以下4條語句,討論互相==比較的輸出結果

int i02=59; // 這是一個基本類型,存儲在棧中。

Integer i01=59; // 調用 Integer 的 valueOf 方法,自動裝箱。使用享元模式,看值是否在 [-128,127],且 IntegerCache 中是否存在此對象,若是存在,則直接返回引用,不然建立一個新的對象。因程序初次運行,沒有 59 ,因此直接建立了一個新的對象

Integer i03 =Integer.valueOf(59); //  由於 IntegerCache 中已經存在此對象,因此,直接返回引用。

Integer i04 = new Integer(59) ; // 直接建立一個新的對象。

System. out .println(i01== i02); // i01 是 Integer 對象, i02 是 int ,這裏比較的不是地址,而是值。 Integer 會自動拆箱成 int ,而後進行值的比較。因此爲真。

System. out .println(i01== i03); // 由於 i03 返回的是 i01 的引用,因此,爲真。

System. out .println(i03==i04);  // 由於 i04 是從新建立的對象,因此 i03,i04 是指向不一樣的對象,所以比較結果爲假。

System. out .println(i02== i04); // 由於 i02 是基本類型,因此此時 i04 會自動拆箱,進行值比較,因此,結果爲真。
解析

具體參考:c++

減少內存的佔用問題——享元模式和單例模式的對比分析

用命令行:  java xxx a b c 方式運行如下代碼的結果是?

 

這裏java xxx a b c 表示運行java字節碼文件xxx,參數爲 a b c,由於只輸入了三個參數,且args是數組下標從0開始,而程序中使用到agrs[3]顯然數組越界。拋出數組越界異常。
解析

下面代碼的輸出結果是?

須要知道計算機用補碼存儲數值

10的原碼:0000 0000 | 0000 0000 | 0000 0000 | 00001010

~10:      1111111111111111,1111111111110101  變爲負數,下面求該負數的補碼:

~10反碼:10000000000000000,0000000000001010 符號位不變,其他位取反

~10補碼:10000000000000000,0000000000001011,等於 -11

下面記住公式

-n=~n+1可推出 ~n = -n-1
解析

下面代碼的輸出結果是?

System.out.println(2.00 - 1.10);
可能認爲該程序打印0.90,可是編譯器如何才能知道你想要打印小數點後兩位小數呢?

實際它打印的是0.8999999999999999。問題在於1.1 這個數字不能被精確表示成爲一個double,所以它被表示成爲最接近它的double 值。該程序從2 中減去的就是這個值。更通常地說,問題在於並非全部的小數均可以用二進制浮點數來精確表示的。若是你正在用的是JDK 5.0 或更新的版本,那麼可使用相似c的方式,Java的printf 工具來訂正該程序:

System.out.printf("%.2f%n",2.00 - 1.10);

這條語句打印的是正確的結果,可是這並不表示它就是對底層問題的通用解決方案:它使用的仍舊是二進制浮點數的double 運算。浮點運算在一個範圍很廣的值域上提供了很好的近似,可是它一般不能產生精確的結果。二進制浮點對於貨幣計算是很是不適合的,由於它不可能將0.1,或者10的其它任何次負冪精確表示爲一個長度有限的二進制小數。

解決該問題的一種方式是使用某種整數類型,例如int 或long,而且以分爲單位來執行計算。注意這樣作請確保該整數類型大到足夠表示在程序中你將要用到的全部值。對本題int 就足夠了。下面是用int 以分爲單位表示貨幣值後重寫的println 語句:
System.out.println((200 - 110) + "cents");

解決該問題的另外一種方式是使用執行精確小數運算的BigDecimal工具類。它還能夠經過 JDBC 與 SQL DECIMAL 類型進行互操做。這裏要注意: 必定要用 BigDecimal(String) 構造器,而不用BigDecimal(double)。後一個構造器將用它的參數的「精確」值來建立一個實例:new BigDecimal(.1)將返回一個表示0.100000000000000055511151231257827021181583404541015625 的BigDecimal。經過正確使用BigDecimal,程序就能夠打印出咱們所指望的結果0.90import java.math.BigDecimal;
public class Change1{
  public static void main(String args[]){
    System.out.println(new BigDecimal("2.00").subtract(new BigDecimal("1.10")));
  }
}
這個版本並非十分地完美,由於Java 並無爲BigDecimal 提供任何語言上的支持。使用BigDecimal 的計算頗有可能比那些使用原始類型的計算要慢一些,對某些大量使用小數計算的程序來講,這可能會成爲問題。總之, 在須要精確答案的地方,要避免使用float 和double,對於貨幣計算,要使用int、long 或BigDecimal。
解析

浮點數表示能夠參考程序員

從如何判斷浮點數是否等於0提及——浮點數的機器級表示

下面代碼的輸出結果是?

        final long MICROS_PER_DAY = 24 * 60 * 60 * 1000 * 1000;
        final long MILLIS_PER_DAY = 24 * 60 * 60 * 1000;
        System.out.println(MICROS_PER_DAY / MILLIS_PER_DAY);
除數和被除數都是long 類型的,long 類型大到了能夠很容易地保存這兩個乘積而不產生溢出。所以,看起來程序打印的一定是1000。

問題在於常數 MICROS_PER_DAY 的計算「確實」溢出了。儘管計算的結果能安全放入long 中,而且其空間還有富餘,可是這個結果並不適合放入int 中。這個計算徹底是以int 運算來執行的,而且只有在運算完成以後,其結果才被提高到long,而此時已經太遲了,計算已經溢出。

從int提高到long是一種拓寬原始類型轉換(widening primitive conversion),它保留了(不正確的)數值。這個值以後被MILLIS_PER_DAY 整除,而MILLIS_PER_DAY 的計算是正確的,由於它適合int 運算。這樣整除的結果就獲得了5(前者返回的是一個小了200 倍的數值)。

那麼爲何計算會是以int運算來執行的呢?

由於全部乘在一塊兒的因子都是默認int數值。將兩個int 數值相乘時,將獲得另外一個int 數值,這是Java 的語言特性,經過使用long 常量來替代int 常量做爲每個乘積的第一個因子,咱們就能夠修改這個程序。這樣作能夠強制表達式中全部的後續計算都用long 來完成。儘管這麼作只在MICROS_PER_DAY 表達式中是必需的,可是在兩個乘積中都這麼作是一種很好的方式。類似地使用long 做爲乘積的「第一個」數值也並不老是必需的,可是這麼作也是一種很好的形式。在兩個計算中都以long數值開始能夠很清楚地代表它們都不會溢出。下面的程序將打印1000:

final long MICROS_PER_DAY = 24L * 60 * 60 * 1000 * 1000;
final long MILLIS_PER_DAY = 24L * 60 * 60 * 1000;
System.out.println(MICROS_PER_DAY/MILLIS_PER_DAY);



小結:當操做很大的數字時,千萬要提防溢出。即便用來保存結果的變量已顯得足夠大,也並不意味着要產生結果的計算具備正確的類型。當拿不許時,就使用long 運算來執行整個計算。
解析

下面代碼的輸出結果是?

System.out.println(12345 + 5432l);
表面上看,這是一個很簡單的題,打印66666。

實際上,當運行該程序時,它打印的是17777。仔細看 + 操做符的兩個操做數,咱們是將一個int 類型的12345 加到了 long 類型的5432l 上。請注意左操做數開頭的數字1 和右操做數結尾的小寫字母l 之間的細微差別。數字1 的水平筆劃 和 垂直筆劃 之間是一個銳角,而與此相對照的是,小寫字母 l 是一個直角。

這個寫法確實已經引發了混亂,這裏有一個教訓:在 long 型字面常量中,必定要用大寫的L,千萬不要用小寫的l。這樣就能夠徹底避免混亂。

System.out.println(12345 + 5432L);

相似的,要避免使用單獨的一個 l 字母做爲變量名。由於很難經過觀察來判斷它究竟是 l 仍是數字 1。屬於編程不規範。

System.out.println(1);

總之,小寫字母 l 和數字1 在大多數字體中幾乎是同樣的。爲避免程序的讀者對兩者產生混淆,千萬不要使用小寫的 l 來做爲 long 型字面常量的結尾或是做爲變量名。Java 從C 編程語言中繼承良多,包括long 型字面常量的語法。也許當初容許用小寫的 l 來編寫long 型字面常量自己就是一個錯誤。
解析

下面代碼的輸出結果是?

System.out.println(Long.toHexString(0x100000000L + 0xcafebabe));
看起來應該打印1cafebabe。畢竟這是十六進制數字10000000016 與cafebabe16 的和。該程序使用的是long 型運算,它能夠支持16位十六進制數,所以運算溢出是不可能的。

然而,運行該程序,發現它打印出來的是cafebabe,並無任何前導的1。這個輸出表示的是正確結果的低32 位,可是不知何故,第33 位丟失了。看起來程序好像執行的是int 型運算而不是long 型運算。

注意:十進制字面常量具備一個很好的屬性,即全部的十進制字面常量都是正的,而十六進制或是八進制字面常量並不具有這個屬性。要想書寫一個負的十進制常量,可使用一元取反操做符(-減號)鏈接一個十進制字面常量。以這種方式,十進制書寫任何int 或long 型的數值,無論它是正的仍是負的,而且負的十進制常數能夠很明確地用一個減號符號來標識。

十六進制和八進制字面常量並非這麼回事,它們能夠具備正的以及負的數值。若是十六進制和八進制字面常量的最高位被置位了,那麼它們就是負數。在這個程序中,數字0xcafebabe是一個int 常量,它的最高位被置位了,因此它是一個負數。它等於十進制數值-889275714。

該程序執行的這個加法是一種「混合類型的計算(mixed-type computation):左操做數是long 類型的,而右操做數是int 類型的。爲了執行該計算,Java 將int 類型的數值用拓寬原始類型轉換提高爲一個long 類型,而後對兩個long 類型數值相加。由於int 是一個有符號的整數類型,因此它將負的int 類型的數值提高爲一個在數值上相等的long 類型數值。這個加法的右操做數0xcafebabe 被提高爲了long 類型的數值0xffffffffcafebabeL。這個數值以後被加到了左操做數0x100000000L 上。看成爲int 類型來被審視時,通過符號擴展以後的右操做數的高32 位是-1,而左操做數的高32 位是1,將這兩個數值相加就獲得了0,這也就解釋了爲何在程序輸出中前導1 丟失了。下面所示是用手寫的加法實現。(在加法上面的數字是進位)
      1111111
    0xf f f f f f f f c a f e b a b eL
+  0x00000001 0 0 00 0 0 0 0L
----------------------------------
    0x00000000 c a f e b a b eL

修改需用一個long 十六進制字面常量來表示右操做數便可。這就能夠避免具備破壞力的符號擴展,而且程序也就能夠打印出咱們所指望的結果1cafebabe:
public class JoyOfHex{
  public static void main(String[] args){
    System.out.println(Long.toHexString(0x100000000L + 0xcafebabeL));
  }
}

本題給咱們的教訓是:混合類型的計算可能會產生混淆,尤爲是十六進制和八進制字面常量無需顯式的減號符號就能夠表示負的數值。最好是避免混合類型的計算。
解析

Math.round(11.5)等於多少? Math.round(-11.5)等於多少?

Java的Math類中提供了三個與取整有關的方法:ceil、floor、round,這些方法的做用與它們的英文名稱的含義相對應,

floor:向下取整數。返回double類型-----n. 地板,地面

         例如:Math.floor(-4.2) = -5.0

-----------------------------------------------------------

ceil:   向上取整數。返回double類型-----vt. 裝天花板;

         例如:Math.ceil(5.6) = 6.0

-----------------------------------------------------------

最不舒服的是round方法,算法爲Math.floor(x+0.5),即將原數加0.5再向下取整,因此,Math.round(11.5)的結果爲12.0,Math.round(-11.5)的結果爲-11.0。
解析

下面代碼的輸出結果是?

這裏主要是有一點:

ceil 方法上有這麼一段註釋:若是參數小於0且大於-1.0,結果爲 -0

其實ceil 和 floor 方法 上註釋都有:若是參數是 NaN、無窮、正 0、負 0,那麼結果與參數相同,若是是 -0.0,那麼其結果是 -0.0

答案是 -0.0 和 -1.0
解析

下面代碼的輸出結果是?

        int a = 0;
        int b = 1;
        System.out.println(b / a);
沒什麼好說的吧,拋出除數爲0的算術異常

Exception in thread "main" java.lang.ArithmeticException: / by zero
解析

下面代碼的輸出結果是?

        double a = 0;
        int b = 1;
        System.out.println(b / a);
Infinity

浮點數除以0之因此不會拋出異常的一個最重要的緣由是浮點數0不一樣於整數0,是不能準確表示的。實際上浮點數0指的是一個無限趨近於0的數,一個正數除以一個無限趨近於0的數結果就是無限趨近於正無窮大,也就是infinity。

infinity一般也稱之爲非數(NaN,not a number),是浮點數的一種特殊形態。
解析

通過強制類型轉換之後,變量a,b的值分別爲多少?

short類型,a的二進制是:0000 0000 1000 0000,強制轉換截後8位,正數用源碼錶示,負數用補碼錶示,第一位是符號。所以a截取後8位的二進制是:1000 0000,第一位是1,表示是一個負數,二進制的值是128,因此結果是 b = -128,a仍是128。
解析

下面代碼的輸出結果是?

這裏涉及java的自動裝包/自動拆包(AutoBoxing/UnBoxing), Byte的首字母爲大寫,是類,在add函數內實現++操做,會自動拆包成byte類型,因此add函數仍是不能實現自增功能。也就是說add函數只是個擺設,沒有任何做用。

Byte類型值大小爲-128~127之間。 add(++a);這裏++a會越界,a的值變爲-128 。add(b); 前面說了,add不起任何做用,b仍是127。
解析

下面代碼的輸出結果是?

被final修飾的變量是常量,這裏的b6=b4+b5能夠當作是b6=10;在編譯時就已經變爲b6=10了,而b1和b2是byte類型,java中進行計算時候將他們提高爲int類型再進行計算,b1+b2計算後已是int類型,賦值給b3,b3是byte類型,類型不匹配,編譯不會經過,須要進行強制轉換。

記住:Java中的byte,short,char變量進行計算時都會提高爲int類型。
解析 

下面代碼的輸出結果是?

Byte是byte的包裝類型,自動初始化爲null而不是0,答案爲:

null 42 42
解析

下面代碼的輸出結果是?

 1         Integer integer = 42;
 2         Long long1 = 42L;
 3         Double double1 = 42.0;
 4 
 5         System.out.println(integer == long1);
 6         System.out.println(integer == double1);
 7         System.out.println(long1 == double1);
 8         System.out.println(integer.equals(double1));
 9         System.out.println(double1.equals(long1));
10         System.out.println(integer.equals(long1));
11         System.out.println(long1.equals(42));
12         System.out.println(long1.equals(42L));
13         System.out.println(integer.equals(new Integer(42)));
5,6,7編譯錯誤,包裝類的「==」運算在不遇到算術運算的狀況下,兩邊都是包裝類那麼不會自動拆箱(只要有一個是基本類型,就會自動拆箱),不能直接比較不一樣類型的變量,必須強制轉化。

下面看包裝類Integer的equals方法源碼,其它包裝類也重寫了equals方法:先判斷是否是屬於一個類型,而後拆箱比較數值大小。

public boolean equals(Object obj) {
  if (obj instanceof Integer) {
    return value == ((Integer)obj).intValue();
  }
  return false;
}

8行輸出false,不是一個類型

9行輸出false

10行輸出false

11行輸出false,42默認是int類型

12行true

13行true



補充下,看String的equals方法源碼:先比較是否是一個對象,如不是,一樣的思路,先使用instanceof判斷是否是一個類型的,若是是才判斷字符串內容。

public boolean equals(Object anObject) {
  if (this == anObject) {
    return true;
  }
  if (anObject instanceof String) {
    String anotherString = (String)anObject;
    int n = value.length;
    if (n == anotherString.value.length) {
      char v1[] = value;
      char v2[] = anotherString.value;
      int i = 0;
      while (n-- != 0) {
        if (v1[i] != v2[i])
          return false;
        i++;
      }
      return true;
    }
  }
  return false;
}
解析

下面代碼的輸出結果是?

        Map<String, Boolean> map = new HashMap<>();
        System.out.println((map != null ? map.get("test") : false));
報異常:Exception in thread "main" java.lang.NullPointerException

回憶:基本數據類型的自動裝箱(autoboxing)、拆箱(unboxing)是自J2SE 5.0開始提供的功能。

通常咱們要建立一個類的對象實例的時候,咱們會這樣:

Class a = new Class(parameters);

當咱們建立一個Integer對象時,卻能夠這樣:

Integer i = 100;

注意和 int i = 100; 是有區別的,實際上,執行上面那句代碼的時候,系統爲咱們執行了: Integer i = Integer.valueOf(100); 這裏暫且不討論這個原理是怎麼實現的(什麼時候拆箱、什麼時候裝箱),也略過普通數據類型和對象類型的區別。咱們能夠理解爲,當咱們本身寫的代碼符合裝(拆)箱規範的時候,編譯器就會自動幫咱們拆(裝)箱。那麼這種不被程序員控制的自動拆(裝)箱必定格外當心,可能存在問題。

注意:三目運算符的語法規範,當第二,第三位操做數分別爲基本類型和對象時,其中的對象就會拆箱爲基本類型進行操做。因此,結果就是因爲使用了三目運算符,而且第2、第三位操做數分別是基本類型和對象。因爲該對象爲null,因此在拆箱過程當中調用null.booleanValue()的時候就報了空指針異常。

若是代碼這麼寫,就不會報錯:

Map<String, Boolean> map = new HashMap<>();

System.out.println((map != null ? map.get("test") : Boolean.FALSE));

保證了三目運算符的第二第三位操做數都爲對象類型。
解析

下面代碼的輸出結果是?

char x = 'X';
int i = 0;
System.out.println(true ? x : 0);
System.out.println(false ? i : x);
打印 X88。第一個print 語句打印的是X,而第二個打印的倒是88。

請注意在這兩個表達式中,每個表達式的第二個和第三個操做數的類型都不相同:x 是char 類型的,而0 和i 都是int 類型的。而混合類型的計算會引發混亂,條件表達式結果類型的規則過於冗長和複雜,其核心就是4點:

若是第二個和第三個操做數具備相同的類型,那麼它就是條件表達式的類型。
若是一個操做數的類型是T,T 表示byte、short 或char,而另外一個操做數是一個int 類型的常量表達式,它的值是能夠用類型T 表示的,那麼條件表達式的類型就是T。
不然,將對操做數類型運用二進制數字提高,而條件表達式的類型就是第二個和第三個操做數被提高以後的類型。
若是第二,三個操做數有一個是基本類型,另外一個是對象類型,那麼對象類型會進行自動拆箱。
程序的兩個條件表達式中,一個操做數的類型是char,另外一個的類型是int。在兩個表達式中,int 操做數都是0,它能夠被表示成一個char。然而,只有第一個表達式中的int 操做數是常量(0),而第二個表達式中的int 操做數是變量(i)。所以,第2 點被應用到了第一個表達式上,它返回的類型是char,而第3 點被應用到了第二個表達式上,其返回的類型是對int 和char 運用了二進制數字提高以後的類型,即int。

條件表達式的類型將肯定哪個重載的print 方法將被調用。對第一個表達式來講,PrintStream.print(char)將被調用,而對第二個表達式來講,PrintStream.print(int)將被調用。前一個重載方法將變量x 的值做爲Unicode字符(X)來打印,然後一個重載方法將其做爲一個十進制整數(88)來打印。

總之,最好是在條件表達式中使用類型相同的第二和第三操做數。
解析  

下面代碼的輸出結果是?

張飛算計魏延,關羽,或者調戲婦女。

==  優先級高於 三目運算符,先判斷   true == true,此時返回爲  true,

boolean b=true ? false : true == true ? false : true;轉化爲

true ? false : (true == true) ? false : true;

true ? false : ((true == true) ? false : true);

false,後面不會執行了。
解析

參考文章:Java也適用。面試

c/c++系列的運算符優先級總結

下面代碼的輸出結果是?

JVM 加載 class 文件時,就會執行靜態代碼塊,靜態代碼塊中初始化了一個變量x並初始化爲5,因爲該變量是個局部變量,靜態代碼快執行完後變被釋放。

接着兩個靜態成員變量x,y,並無賦初值,會有默認值,int類型爲0

main方法裏執行x--操做,變量單獨進行自增或自減操做x--和--x的效果同樣,此時x變爲了-1。

調用MyMethod()方法,在該方法中對x和y進行計算,因爲x和y都是靜態成員變量,因此在整個類的生命週期內的x和y都是同一個。y=x++ + ++x能夠當作是y=(x++)+(++x),當++或者--和其它變量進行運算時,x++表示先運算,再自增,++x表示先自增再參與運算,因此x爲-1參與運算,而後自增,x此時爲0,++x後x爲1,而後參與運算,那麼y=-1+1就爲0,此時x爲1

執行並打印x+y + ++x運算方式和前面相同,最後計算結果就爲3
解析

類Demo中存在方法func0、func一、func二、func3和func4,請問該方法中,哪些是不合法的定義?

數據類型的轉換,分爲自動轉換和強制轉換。自動轉換是程序在執行過程當中 「 悄然 」 進行的轉換,不須要用戶提早聲明,通常是從位數低的類型向位數高的類型轉換;強制類型轉換則必須在代碼中聲明,轉換順序不受限制。

自動數據類型轉換

自動轉換按從低到高的順序轉換。不一樣類型數據間的優先關係以下: 
    低 --------------------------------------------->byte,short,char-> int -> long -> float -> double

強制數據類型轉換

強制轉換的格式是在須要轉型的數據前加上 「( )」 ,而後在括號內加入須要轉化的數據類型。有的數據通過轉型運算後,精度會丟失,而有的會更加精確

答案:

1沒有返回值,4也不對。
解析

下面代碼的輸出結果是?

        short s1 = 1;
        s1 = s1 + 1;
        s1 += 1;
        System.out.println(s1); 
s1 = s1 + 1;

因爲s1+1運算時會自動提高表達式的類型,因此結果是int型,再賦值給short類型s1時,編譯器將報告須要強制轉換類型的錯誤。

s1 += 1;

因爲 +=是java語言規定的運算符,java編譯器會對它進行特殊處理,且效率高,所以能夠正確編譯。當使用+=、-=、*=、/=、%= 運算符對基本類型進行運算時,遵循規則:運算符右邊的數值將首先被強制轉換成與運算符左邊數值相同的類型,而後再執行運算,且運算結果與運算符左邊數值類型相同。
解析

下面代碼的輸出結果是?

System.out.println((int) (char) (byte) -1);
以int 數值-1 開始,而後從int轉型爲byte,以後轉型爲char,最後轉型回int。第一個轉型將數值從32 位窄化到了8 位,第二個轉型將數值從8 位拓寬到了16 位,最後一個轉型又將數值從16 位拓寬回了32 位。

運行該程序,打印65535。

由於Java 使用了基於2 的補碼的二進制運算,所以int 類型的數值-1 的全部32 位都是置位的,補碼錶示就是全f的16進制。從int 到byte 的轉型是很簡單的,它執行了一個窄化原始類型轉化(narrowing primitiveconversion),直接將除低8 位以外的全部位所有砍掉。這樣作留下的是一個8位都被置位了的byte,它仍舊錶示-1。

從byte 到char 的轉型稍微麻煩一點,由於byte 是一個有符號類型,而char是一個無符號類型。在將一個整數類型轉換成另外一個寬度更寬的整數類型時,一般是能夠保持其數值的,可是卻不可能將一個負的byte 數值表示成一個char。所以,從byte 到char 的轉換有些複雜。

有一條很簡單的規則可以描述從較窄的整型轉換成較寬的整型時的符號擴展行爲:

若是最初的數值類型是有符號的,那麼就執行符號擴展;
若是它是char,那麼無論它將要被轉換成什麼類型,都執行零擴展。
瞭解這條規則可使咱們很容易地解決這個題。由於byte 是一個有符號的類型,因此在將byte 數值-1 轉換成char 時,會發生符號擴展。做爲結果的char 數值的16 個位就都被置位了,所以它等於2^16-1,即65535。從char 到int 的轉型也是一個拓寬原始類型轉換,它將執行零擴展而不是符號擴展。做爲結果的int 數值也就成了65535。

儘管這條簡單的規則描述了在有符號和無符號整型之間進行拓寬原始類型時的符號擴展行爲,可是最好仍是不要編寫出依賴於它的程序。最好將你的意圖明確地表達出來。

若是在將一個char 數值 c 轉型爲一個寬度更寬的類型,而且不但願有符號擴展,那麼爲清晰表達意圖,能夠考慮使用一個位掩碼,即便它並非必需的:
int i = c & 0xffff;

或者,書寫一句註釋來描述轉換的行爲:

int i = c; //不會執行符號擴展

若是在將一個char 數值c 轉型爲一個寬度更寬的整型,而且但願有符號擴展,那麼就先將char 轉型爲一個short,它與char 具備一樣的寬度,可是它是有符號的。在給出了這種細微的代碼以後,應也爲它書寫一句註釋:

int i = (short) c; //轉型將引發符號擴展

若是在將一個byte 數值b 轉型爲一個char,而且不但願有符號擴展,那麼必須使用一個位掩碼來限制它。這是一種通用作法,因此不須要任何註釋:

char c = (char) (b & 0xff);
解析

下面代碼的輸出結果是?

int x = 1984; // (0x7c0)
int y = 2001; // (0x7d1)
x ^= y ^= x ^= y;
System.out.println("x= " + x + "; y= " + y);
乍一看,會認爲程序應該交換變量x 和y 的值。

實際上運行卻打印 x = 0; y = 1984。出錯了。關於亦或交換算法,好久之前,當中央處理器只有少數寄存器時,人們發現能夠經過利用異或操做符(^)的屬性(x ^ y ^ x) == y 來避免使用臨時變量:

x = x ^ y;
y = y ^ x;
x = y ^ x;

這個慣用法曾經在C 語言中被普遍使用過,並進一步被構建到了C++ 中,可是它並不保證在兩者中均可以正確運行。有一點是確定的,那就是它在Java 中確定是不能正確運行的。

Java 語言規範描述到:操做符的操做數是從左向右求值。爲了求表達式 x ^= expr 的值,x 的值是在計算expr 以前被提取的,而且這兩個值的異或結果被賦給變量x。在程序中,變量x 的值被提取了兩次——每次在表達式中出現時都提取一次——可是兩次提取都發生在全部的賦值操做以前。

// Java 中x^= y^= x^= y 的實際行爲
int tmp1 = x ; // x 在表達式中第一次出現
int tmp2 = y ; // y 的第一次出現
int tmp3 = x ^ y ; // 計算x ^ y
x = tmp3 ; // 最後一個賦值:存儲x ^ y 到 x
y = tmp2 ^ tmp3 ; // 第二個賦值:存儲最初的x 值到y 中
x = tmp1 ^ y ; // 第一個賦值:存儲0 到x 中

在C 和C++中,並無指定表達式的計算順序。當編譯表達式x ^= expr 時,許多C 和C++編譯器都是在計算expr 以後才提取x 的值的,這就使得上述的慣用法能夠正常運轉。儘管它能夠正常運轉,可是它仍然違背了C/C++有關不能在兩個連續的序列點之間重複修改變量的規則。所以,這個慣用法的行爲在C 和C++中也沒有明肯定義。爲了看重其價值,咱們仍是能夠寫出不用臨時變量就能夠互換兩個變量內容的Java 表達式的。可是它一樣是醜陋而無用的:
// 殺雞用牛刀的作法,千萬不要這麼作!
y = (x^= (y^= x))^ y ;

在單個的表達式中不要對相同的變量賦值兩次。表達式若是包含對相同變量的屢次賦值,就會引發混亂,而且不多可以執行你但願的操做。即便對多個變量進行賦值也很容易出錯。更通常地講,要避免所謂聰明的編程技巧。它們都是易於產生bug 的,很難以維護,而且運行速度常常是比它們所替代掉的簡單直觀的代碼要慢。

修改:

int x = 1984; // (0x7c0)
int y = 2001; // (0x7d1)
x = x ^ y;
y = y ^ x;
x = y ^ x;
System.out.println("x= " + x + "; y= " + y);

另外,用異或交換變量既不會加快運行速度(反而更慢,六讀三寫加三次異或),也不會節省空間(中間變量tmp 一般會用寄存器,而不是內存空間存儲)。
解析

詳細參考理論:算法

一道面試題:用多種方法實現兩個數的交換 

下面代碼的輸出結果是?

 

首先,咱們要知道,靜態的方法也是能夠經過對象來訪問的,這一點很奇怪,可是確實是能夠。其次,null能夠被強制類型轉換成任意類型的對象,因而能夠經過它來執行靜態方法。輸出testMethod
解析

在JAVA中如何跳出當前的多重嵌套循環?寫一段代碼示意。

break語句能夠強迫程序中斷循環,當程序執行到break語句時,即會離開循環,繼續執行循環外的下一個語句,若是Break語句出如今嵌套循環中的內層循環,則break語句只會跳出當前層的循環,若是跳出多重循環,可在外面的循環語句前定義一個標號,而後在裏層循環體的代碼中使用帶有標號的break語句,便可跳出外層循環。

flag: for (int i = 0; i < 100; i++) {
  for (int j = 0; j < 100; j++) {
    System.out.println("i = " + i + ", " + "j = " + j);
    if (j == 2) {
      break flag;
    }
  }
}

打印:

i = 0, j = 0
i = 0, j = 1
i = 0, j = 2

區別continue語句:它能夠強迫程序跳到當前循環的起始處,當程序運行到continue語句時,即會中止運行剩餘的循環主體,而是回到循環的開始處繼續運行,記住,不是跳出整個循環執行下一條語句,這是Break和continue的主要區別所在,實際上使用continue就是中斷一次循環的執行
解析

下面代碼的輸出結果是?

 

for(條件1;條件2;條件3) {

    //語句

}

執行順序是條件1->條件2->語句->條件3->條件2->語句->條件3->條件2.......

若是條件2爲true,則一直執行。若是條件2位false,則for循環結束。

打印 ABDCBDCB
解析

下面代碼的輸出結果是?

        for (byte b = Byte.MIN_VALUE; b < Byte.MAX_VALUE; b++) {
            if (b == 0x90)
                System.out.print("Joy!");
        }
0x90 是一個int 常量,它超出了byte 數值的範圍。由於0x90 是一個兩位的十六進制字面常量,每個十六進制位都佔據4 個比特的位置,因此整個數值也只佔據8 個比特,即1 個byte。問題在於byte 是有符號類型。常量0x90 是一個正的最高位被置位的8 位int 數值。合法的byte數值是從-128 到+127,可是int 常量0x90 等於+144。拿一個byte 與一個int 進行的比較是一個混合類型比較(mixed-type comparison)。

若是把byte 數值想象爲蘋果,把int 數值想象成爲桔子,那麼該程序就是在拿蘋果與桔子比較。爲了比較byte 數值(byte)0x90 和int 數值0x90,Java 經過拓寬原始類型轉換將byte 提高爲一個int,而後比較這兩個int 數值。由於byte 是一個有符號類型,因此這個轉換執行的是符號擴展,將負的byte 數值提高爲了在數字上相等的int數值。在本例中,該轉換將(byte)0x90提高爲int數值-112,它不等於int 數值0x90,即+144。

能夠將int 轉型爲byte,以後就能夠進行比較了:

if (b == (byte)0x90)
  System.out.println("Joy!");

或者,能夠用一個屏蔽碼來消除符號擴展的影響,從而將byte 轉型爲int:

if ((b & 0xff) == 0x90)
  System.out.print("Joy!");

上面的兩個解決方案均可以正常運行,可是避免這類問題的最佳方法仍是將常量值移出到循環的外面,並將其在一個常量聲明中定義它。
解析

下面代碼的輸出結果是?

public class Test1 {
    private static final byte TARGET = 0x90;

    public static void main(String[] args) {
        for (byte b = Byte.MIN_VALUE; b < Byte.MAX_VALUE; b++) {
            if (b == TARGET) {
                System.out.print("Joy!");
            }
        }
    }
}
通不過編譯。常量聲明有問題,0x90 對於byte 類型來講不是一個有效的數值。修改:

private static final byte TARGET = (byte)0x90;

總之,要避免混合類型比較,由於它們內在地容易引發混亂。請使用聲明的常量替代「魔幻數字」,它說明了常量的含義,集中了常量的定義,而且根除了重複的定義。
解析

下面代碼的輸出結果是?

public class Test1 {
    public static final int END = Integer.MAX_VALUE;
    public static final int START = END - 100;

    public static void main(String[] args) {
        int count = 0;
        for (int i = START; i <= END; i++)
            count++;
        System.out.println(count);
    }
}
死循環

這個循環會在循環索引(i)小於或等於Integer.MAX_VALUE 時持續運行,當i 達到Integer.MAX_VALUE,而且再次被執行增量操做時,它就有繞回到了Integer.MIN_VALUE。

若是循環會迭代到int 數值的邊界附近時,最好是使用一個long 變量做爲循環索引。只需將循環索引的類型從int 改變爲long 就能夠解決該問題,從而使程序打印出指望的101:

更通常地講,這裏的教訓就是int 不能表示全部的整數。不管什麼時候使用了一個整數類型,都要意識到其邊界條件。因此一般最好是使用一個取之範圍更大的類型。(整數類型包括byte、charshortint 和long)
解析

下面代碼的輸出結果是?

public class Test1 {
    public static void main(String[] args) {
        int i = 0;
        while (-1 << i != 0)
            i++;
        System.out.println(i);
    }
}
常量-1 是全部32 位都被置位的int 數值(0xffffffff)。左移操做符將最右邊的 i 位設置爲0,並保持其他的32 - i 位爲1。很明顯,這個循環將完成32 次迭代,由於-1 << i 對任何小於32 的i 來講都不等於0。故推斷打印32。

實際上,它不會打印任何東西,而是進入了一個死循環。

由於(-1 << 32)等於-1 而不是0。若是左值是int,移位長度老是介於0 到31 之間,若是左操做數是long ,介於0 到63 之間。這個長度是對32取餘的,若是左操做數是long 類型的,則對64 取餘。若是對一個int 數值移位32 位,或者是對一個long 數值移位64 位,都只能返回這個數值自身的值。這條規則做用於所有的三個移位操做符:<<、>>和>>>。沒有任何移位長度可讓一個int 數值丟棄其全部的32 位,或者是讓一個long數值丟棄其全部的64 位。

修改:不讓-1 重複地移位,而是將前一次移位操做的結果保存起來,而且讓它在每次迭代時左移 1 位,這樣避免了移位32,出現死循環,下面打印32:

public class Shifty {
  public static void main(String[] args) {
    int distance = 0;
    for (int val = -1; val != 0; val <<= 1)
      distance++;
    System.out.println(distance);
  }
}

記住原則:若是可能的話,移位長度應該是常量。雖然並不可能老是可使用常量的移位長度,可是當必須使用一個很是量的移位長度時,請確保程序能夠應付這種問題。
解析

下面代碼的輸出結果是?

        int distance = 1;
        distance <<= 1;
        System.out.println(distance);
        distance <<= -1;
        System.out.println(distance);
可能會猜測:負的移位長度的右移操做符能夠起到左移操做符的做用,反之亦然。可是狀況並不是如此。

銘記:右移操做符老是起到右移的做用,而左移操做符也老是起到左移的做用。負的移位長度經過只保留低5 位而剔除其餘位的方式被轉換成了正的移位長度——若是左操做數是long 類型的,則保留低6 位。所以,若是要將一個int數值左移,其移位長度爲-1,那麼移位的效果是它被左移了31 位。

總之,移位長度是對32 取餘的,或者若是左操做數是long 類型的,則對64 取餘。所以,使用任何移位操做符和移位長度,都不可能將一個數值的全部位所有移走。同時,咱們也不可能用右移操做符來執行左移操做,反之亦然。請使用常量的移位長度,若是移位長度不能設爲常量,那麼就要小心。

答案:

2
0
解析

下面代碼的輸出結果是?

        double i = 1.0 / 0;
        double j = 1.0e40;

        while (i != i + 1) {
            System.out.println("hello");
            while (j != j + 1) {
                System.out.println("world");
            }
        }
可能會有人回答:兩個while都是無限循環,由於i不可能等於i + 1,同理j。先打印hello(換行)在無限循環打印world換行

實際上運行以後沒有任何輸出,Java 強制要求使用IEEE 754 浮點數算術運算,它可讓你用一個double 或float 來表示無窮大,而無窮大加1 仍是無窮大。若是 i 在循環開始以前被初始化爲無窮大,那麼終止條件測試(i != i + 1)就會被計算爲false,從而使循環永遠都不會執行。

同理,一個浮點數值越大,它和其後繼數值之間的間隔就越大。浮點數的這種分佈是用固定數量的有效位來表示它們的必然結果。對一個足夠大的浮點數加1 不會改變它的值,由於1 不足以「填補它與其後繼者之間的空隙」。浮點數操做返回的是最接近其精確的數學結果的浮點數值。一旦毗鄰的浮點數值之間的距離大於2,那麼對其中的一個浮點數值加1 將不會產生任何效果,由於其結果沒有達到兩個數值之間的一半,舍掉了,無效的。

對於float 類型,加1 不會產生任何效果的最小級數是2的25,即33,554,432;而對於double 類型,最小級數是2的54,大約是1.8 × 10^16。

毗鄰的浮點數值之間的距離被稱爲一個ulp,它是「最小單位(unit in the last place)」的首字母縮寫詞。在5.0 版中,引入了Math.ulp 方法來計算float 或 double 數值的ulp。

總之,用一個double 或一個float 數值來表示無窮大是能夠的。第二點,將一個很小的浮點數加到一個很大的浮點數上時,將不會改變大的浮點數的值。記住二進制浮點算術只是對實際算術的一種近似。
解析

下面代碼的輸出結果是什麼,爲何?

        double i = 0.0 / 0;
        while (i == i) {
            System.out.println("hello world");
        }
什麼輸出都沒有。

由於 IEEE 754 浮點算術保留了一個特殊的值用來表示一個不是數字的數量。這個值就是NaN(「不是一個數字(Not a Number)」的縮寫),對於全部沒有良好的數字定義的浮點計算,例如 0.0 / 0.0,其值都是它。規範中描述道,NaN 不等於任何浮點數值,包括它自身在內。所以,若是 i 在循環開始以前被初始化爲NaN,那麼終止條件測試(i == i)的計算結果就是false,循環就永遠不會執行。很奇怪但倒是事實。

 爲了表達清晰,可使用標準類庫提供的常量:

double i = Double.NaN;

NaN 還有其餘的驚人之處。任何浮點操做,只要它的一個或多個操做數爲NaN,那麼其結果爲NaN。這條規則是很是合理的。

總之,float 和double 類型都有一個特殊的NaN 值,用來表示不是數字的數量。
解析

下面代碼的輸出結果是?

        short i = -1;
        while (i != 0) {
            i >>>= 1;
        }
        System.out.println(i);
無限循環

無符號右移操做符把0 從左邊移入,而>>>=是一個複合賦值操做符,,而符合運算符會進行自動的類型窄化轉換。(複合賦值操做符包括*=、/=、%=、+=、-=、<<=、>>=、>>>=、&=、^=和|=。)

由於i 的初始值((short)0xffff)是非0 的,因此循環體會被執行。在執行移位操做時,第一步是將i 提高爲int 類型。全部算數操做都會對short、byte和char 類型的操做數執行這樣的提高。這種提高是一個拓寬原始類型轉換,所以沒有任何信息會丟失。這種提高執行的是符號擴展,所以所產生的int 數值是0xffffffff。而後數值右移1 位,但不使用符號擴展,所以產生了int數值0x7fffffff。最後,這個數值被存回到 i 中。爲了將int 數值存入short變量,Java 複合運算符執行的是的窄化轉換,它直接將高16 位截掉。這樣就只剩下 (short)oxffff 了,咱們又回到了開始處。循環的第二次以及後續的迭代行爲都是同樣的,所以循環將永遠不會終止。

若是將i 聲明爲一個short 或byte 變量,而且初始化爲任何負數,那麼這種行爲也會發生。若是 聲明 i 爲一個char,那麼沒法獲得無限循環,由於 char 是無符號的,因此發生在移位以前的拓寬原始類型轉換不會執行符號擴展。

總之,不要在short、byte 或char 類型的變量之上使用複合賦值操做符。由於這樣的表達式執行的是混合類型算術運算,它容易形成混亂。它們執行將隱式地執行會丟失信息的窄化轉型。
修改:

long i = -1; // 64 bits,int也行

這樣循環執行迭代的次數與最大的整數類型所佔據的位數相同
解析

下面代碼的輸出結果是?

        for (int i = 1; i <= 100; i++) {
            if (i % 10 * 10 == 0) {
                System.out.println(i);
            }
        }
也許有人回答是100

實際運行是:

10
20
30
40
50
60
70
80
90
100

由於算術運算符裏,%和*,/同時出現是從左向右運算,相似+,-。%的優先級和*,/ 同樣。
解析
相關文章
相關標籤/搜索