好東西分享
有些運算符在JAVA語言中存在著,但是在實際開發中我們或許很少用到它們,在面試題中卻時常出現它們的身影,對于這些運算符的含義和用法,你是否還記得呢?
自增(++)和自減(--)運算符
我們先來回答幾個問題吧:
- int ?i?=? 0 ; ??
- int ?j?=?i++; ??
- int ?k?=?--i;??
int i = 0; int j = i++; int k = --i;
這段代碼運行后,i等于多少?j等于多少?k等于多少?太簡單了?好,繼續:
- int ?i?=? 0 ; ??
- int ?j?=?i++?+?++i; ??
- int ?k?=?--i?+?i--;??
int i = 0; int j = i++ + ++i; int k = --i + i--;
代碼執行后i、j、k分別等于多少呢?還是很簡單?好,再繼續:
- int ?i= 0 ; ??
- System.out.println(i++);??
int i=0; System.out.println(i++);
這段代碼運行后輸出結果是什么?0?1?
- float ?f= 0 .1F; ??
- f++; ??
- double ?d= 0 .1D; ??
- d++; ??
- char ?c= 'a' ; ??
- c++;??
float f=0.1F; f++; double d=0.1D; d++; char c='a'; c++;
上面這段代碼可以編譯通過嗎?為什么?如果你能順利回答到這里,說明你對自增和自減運算符的掌握已經很好了。
為了分析出上面提出的幾個問題,我們首先來回顧一下相關知識:
- 自增(++):將變量的值加1,分前綴式(如++i)和后綴式(如i++)。前綴式是先加1再使用;后綴式是先使用再加1。
- 自減(--):將變量的值減1,分前綴式(如--i)和后綴式(如i--)。前綴式是先減1再使用;后綴式是先使用再減1。
在第一個例子中,int j=i++;是后綴式,因此i的值先被賦予j,然后再自增1,所以這行代碼運行后,i=1、j=0;而int k=--i;是前綴式,因此i先自減1,然后再將它的值賦予k,因此這行代碼運行后,i=0、k=0。
在第二個例子中,對于int j=i++ + ++i;,首先運行i++,i的值0被用于加運算(+),之后i自增值變為1,然后運行++i,i先自增變為2,之后被用于加運算,最后將i兩次的值相加的結果0+2=2賦給j,因此這行代碼運行完畢后i=2、j=2;對于int k=--i + i--;用一樣的思路分析,具體過程在此不再贅述,結果應該是i=0、k=2。
自增與自減運算符還遵循以下規律:
- 可以用于整數類型byte、short、int、long,浮點類型float、double,以及字符串類型char。
- 在Java5.0及以上版本中,它們可以用于基本類型對應的包裝器類Byte、Short、Integer、Long、Float、Double、Character。
- 它們的運算結果的類型與被運算的變量的類型相同。
下面的這個例子驗證以上列出的規律,它可以編譯通過并執行。
- public ? class ?Test?{ ??
- ???? public ? static ? void ?main(String[]?args)?{ ??
- ???????? //?整型 ??
- ???????? byte ?b?=? 0 ; ??
- ????????b++; ??
- ???????? //?整型 ??
- ???????? long ?l?=? 0 ; ??
- ????????l++; ??
- ???????? //?浮點型 ??
- ???????? double ?d?=? 0.0 ; ??
- ????????d++; ??
- ???????? //?字符串 ??
- ???????? char ?c?=? 'a' ; ??
- ????????c++; ??
- ???????? //?基本類型包裝器類 ??
- ????????Integer?i?=? new ?Integer( 0 ); ??
- ????????i++; ??
- ????} ??
- }??
public class Test { public static void main(String[] args) { // 整型 byte b = 0; b++; // 整型 long l = 0; l++; // 浮點型 double d = 0.0; d++; // 字符串 char c = 'a'; c++; // 基本類型包裝器類 Integer i = new Integer(0); i++; } }
按位運算符
你還能說出來按位運算符一共有哪幾種嗎?對比下面的列表看看,有沒有從你的記憶中消失了的:
- 按位與運算(&):二元運算符。當被運算的兩個值都為1時,運算結果為1;否則為0。
- 按位或運算(|):二元運算符。當被運算的兩個值都為0時,運算結果為0;否則為1。
- 按位異或運算(^):二元運算符。當被運算的兩個值中任意一個為1,另一個為0時,運算結果為1;否則為0。
- 按位非運算(~):一元運算符。當被運算的值為1時,運算結果為0;當被運算的值為0時,運算結果為1。
這里不像我們看到的邏輯運算符(與運算&&、或運算||、非運算!)操作的是布爾值true或false,或者是一個能產生布爾值的表達式;“按位運算符”所指的“位”就是二進制位,因此它操作的是二進制的0和1。在解釋按位運算符的執行原理時,我們順便說說它們和邏輯運算符的區別。
[list=1]
下面的例子驗證了這條規律:
- public ? class ?BitOperatorTest?{ ??
- ???? public ? static ? void ?main(String[]?args)?{ ??
- ???????? //?整型 ??
- ???????? byte ?b1?=? 10 ,?b2?=? 20 ; ??
- ????????System.out.println( "(byte)10?&?(byte)20?=?" ?+?(b1?&?b2)); ??
- ???????? //?字符串型 ??
- ???????? char ?c1?=? 'a' ,?c2?=? 'A' ; ??
- ????????System.out.println( "(char)a?|?(char)A?=?" ?+?(c1?|?c2)); ??
- ???????? //?基本類型的包裝器類 ??
- ????????Long?l1?=? new ?Long( 555 ),?l2?=? new ?Long( 666 ); ??
- ????????System.out.println( "(Long)555?^?(Long)666?=?" ?+?(l1?^?l2)); ??
- ???????? //?浮點型 ??
- ???????? float ?f1?=? 0 .8F,?f2?=? 0 .5F; ??
- ???????? //?編譯報錯,按位運算符不能用于浮點數類型 ??
- ???????? //?System.out.println("(float)0.8?&?(float)0.5?=?"?+?(f1?&?f2)); ??
- ????} ??
- }??
public class BitOperatorTest { public static void main(String[] args) { // 整型 byte b1 = 10, b2 = 20; System.out.println("(byte)10 & (byte)20 = " + (b1 & b2)); // 字符串型 char c1 = 'a', c2 = 'A'; System.out.println("(char)a | (char)A = " + (c1 | c2)); // 基本類型的包裝器類 Long l1 = new Long(555), l2 = new Long(666); System.out.println("(Long)555 ^ (Long)666 = " + (l1 ^ l2)); // 浮點型 float f1 = 0.8F, f2 = 0.5F; // 編譯報錯,按位運算符不能用于浮點數類型 // System.out.println("(float)0.8 & (float)0.5 = " + (f1 & f2)); } }
運行結果:
- (byte)10 & (byte)20 = 0
- (char)a | (char)A = 97
- (Long)555 ^ (Long)666 = 177
- public ? class ?OperatorTest?{ ??
- ???? public ? boolean ?leftCondition()?{ ??
- ????????System.out.println( "執行-返回值:false;方法:leftCondition()" ); ??
- ???????? return ? false ; ??
- ????} ??
- ??
- ???? public ? boolean ?rightCondition()?{ ??
- ????????System.out.println( "執行-返回值:true;方法:rightCondition()" ); ??
- ???????? return ? true ; ??
- ????} ??
- ??
- ???? public ? int ?leftNumber()?{ ??
- ????????System.out.println( "執行-返回值:0;方法:leftNumber()" ); ??
- ???????? return ? 0 ; ??
- ????} ??
- ??
- ???? public ? int ?rightNumber()?{ ??
- ????????System.out.println( "執行-返回值:1;方法:rightNumber()" ); ??
- ???????? return ? 1 ; ??
- ????} ??
- ??
- ???? public ? static ? void ?main(String[]?args)?{ ??
- ????????OperatorTest?ot?=? new ?OperatorTest(); ??
- ??
- ???????? if ?(ot.leftCondition()?&&?ot.rightCondition())?{ ??
- ???????????? //?do?something ??
- ????????} ??
- ????????System.out.println(); ??
- ??
- ???????? int ?i?=?ot.leftNumber()?&?ot.rightNumber(); ??
- ????} ??
- }??
public class OperatorTest { public boolean leftCondition() { System.out.println("執行-返回值:false;方法:leftCondition()"); return false; } public boolean rightCondition() { System.out.println("執行-返回值:true;方法:rightCondition()"); return true; } public int leftNumber() { System.out.println("執行-返回值:0;方法:leftNumber()"); return 0; } public int rightNumber() { System.out.println("執行-返回值:1;方法:rightNumber()"); return 1; } public static void main(String[] args) { OperatorTest ot = new OperatorTest(); if (ot.leftCondition() && ot.rightCondition()) { // do something } System.out.println(); int i = ot.leftNumber() & ot.rightNumber(); } }
運行結果:
- 執行-返回值:false;方法:leftCondition()
- 執行-返回值:0;方法:leftNumber()
- 執行-返回值:1;方法:rightNumber()
運行結果已經很明顯地顯示了短路和非短路的區別,我們一起來分析一下產生這個運行結果的原因。當運行“ot.leftCondition() && ot.rightCondition()”時,由于方法leftCondition()返回了false,而對于“&&”運算來說,必須要運算符兩邊的值都為true時,運算結果才為true,因此這時候就可以確定,不論rightCondition()的返回值是什么,“ot.leftCondition() && ot.rightCondition()”的運算值已經可以確定是false,由于邏輯運算符是短路的形式,因此在這種情況下,rightCondition()方法就不再被運行了。
而對于“ot.leftNumber() & ot.rightNumber()”,由于“leftNumber()”的返回值是0,對于按位運算符“&”來說,必須要運算符兩邊的值都是1時,運算結果才是1,因此這時不管“rightNumber()”方法的返回值是多少,“ot.leftNumber() & ot.rightNumber()”的運算結果已經可以確定是0,但是由于按位運算符是非短路的,所以rightNumber()方法還是被執行了。這就是短路與非短路的區別。
[/list]
移位運算符
移位運算符和按位運算符一樣,同屬于位運算符,因此移位運算符的位指的也是二進制位。它包括以下幾種:
- 左移位(<<):將操作符左側的操作數向左移動操作符右側指定的位數。移動的規則是在二進制的低位補0。
- 有符號右移位(>>):將操作符左側的操作數向右移動操作符右側指定的位數。移動的規則是,如果被操作數的符號為正,則在二進制的高位補0;如果被操作數的符號為負,則在二進制的高位補1。
- 無符號右移位(>>>):將操作符左側的操作數向右移動操作符右側指定的位數。移動的規則是,無論被操作數的符號是正是負,都在二進制位的高位補0。
注意,移位運算符不存在“無符號左移位(<<<)”一說。與按位運算符一樣,移位運算符可以用于byte、short、int、long等整數類型,和字符串類型char,但是不能用于浮點數類型float、double;當然,在Java5.0及以上版本中,移位運算符還可用于byte、short、int、long、char對應的包裝器類。我們可以參照按位運算符的示例寫一個測試程序來驗證,這里就不再舉例了。
與按位運算符不同的是,移位運算符不存在短路不短路的問題。
寫到這里就不得不提及一個在面試題中經常被考到的題目:
這里所謂的最有效率,實際上就是通過最少、最簡單的運算得出想要的結果,而移位是計算機中相當基礎的運算了,用它來實現準沒錯了。左移位“<<”把被操作數每向左移動一位,效果等同于將被操作數乘以2,而2*8=(2*2*2*2),就是把2向左移位3次。因此最有效率的計算2乘以8的方法就是“2<<3”。
最后,我們再來考慮一種情況,當要移位的位數大于被操作數對應數據類型所能表示的最大位數時,結果會是怎樣呢?比如,1<<35=?呢?
這里就涉及到移位運算的另外一些規則:
- byte、short、char在做移位運算之前,會被自動轉換為int類型,然后再進行運算。
- byte、short、int、char類型的數據經過移位運算后結果都為int型。
- long經過移位運算后結果為long型。
- 在左移位(<<)運算時,如果要移位的位數大于被操作數對應數據類型所能表示的最大位數,那么先將要求移位數對該類型所能表示的最大位數求余后,再將被操作數移位所得余數對應的數值,效果不變。比如1<<35=1<<(35%32)=1<<3=8。
- 對于有符號右移位(>>)運算和無符號右移位(>>>)運算,當要移位的位數大于被操作數對應數據類型所能表示的最大位數時,那么先將要求移位數對該類型所能表示的最大位數求余后,再將被操作數移位所得余數對應的數值,效果不變。。比如100>>35=100>>(35%32)=100>>3=12。
下面的測試代碼驗證了以上的規律:
- public ? abstract ? class ?Test?{ ??
- ???? public ? static ? void ?main(String[]?args)?{ ??
- ????????System.out.println( "1?<<?3?=?" ?+?( 1 ?<<? 3 )); ??
- ????????System.out.println( "(byte)?1?<<?35?=?" ?+?(( byte )? 1 ?<<?( 32 ?+? 3 ))); ??
- ????????System.out.println( "(short)?1?<<?35?=?" ?+?(( short )? 1 ?<<?( 32 ?+? 3 ))); ??
- ????????System.out.println( "(char)?1?<<?35?=?" ?+?(( char )? 1 ?<<?( 32 ?+? 3 ))); ??
- ????????System.out.println( "1?<<?35?=?" ?+?( 1 ?<<?( 32 ?+? 3 ))); ??
- ????????System.out.println( "1L?<<?67?=?" ?+?(1L?<<?( 64 ?+? 3 ))); ??
- ???????? //?此處需要Java5.0及以上版本支持 ??
- ????????System.out.println( "new?Integer(1)?<<?3?=?" ?+?( new ?Integer( 1 )?<<? 3 )); ??
- ????????System.out.println( "10000?>>?3?=?" ?+?( 10000 ?>>? 3 )); ??
- ????????System.out.println( "10000?>>?35?=?" ?+?( 10000 ?>>?( 32 ?+? 3 ))); ??
- ????????System.out.println( "10000L?>>>?67?=?" ?+?(10000L?>>>?( 64 ?+? 3 ))); ??
- ????} ??
- }??
public abstract class Test { public static void main(String[] args) { System.out.println("1 << 3 = " + (1 << 3)); System.out.println("(byte) 1 << 35 = " + ((byte) 1 << (32 + 3))); System.out.println("(short) 1 << 35 = " + ((short) 1 << (32 + 3))); System.out.println("(char) 1 << 35 = " + ((char) 1 << (32 + 3))); System.out.println("1 << 35 = " + (1 << (32 + 3))); System.out.println("1L << 67 = " + (1L << (64 + 3))); // 此處需要Java5.0及以上版本支持 System.out.println("new Integer(1) << 3 = " + (new Integer(1) << 3)); System.out.println("10000 >> 3 = " + (10000 >> 3)); System.out.println("10000 >> 35 = " + (10000 >> (32 + 3))); System.out.println("10000L >>> 67 = " + (10000L >>> (64 + 3))); } }
運行結果:
- 1 << 3 = 8
- (byte) 1 << 35 = 8
- (short) 1 << 35 = 8
- (char) 1 << 35 = 8
- 1 << 35 = 8
- 1L << 67 = 8
- new Integer(1) << 3 = 8
- 10000 >> 3 = 1250
- 10000 >> 35 = 1250
- 10000L >>> 67 = 1250
?
向原作者致敬,轉自:
作者:臧圩人(zangweiren)
網址:
http://zangweiren.iteye.com
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
