JAVA面試題解惑系列(十一)——這些運算符你是否還記得?

JAVA面試題解惑系列(十一)——這些運算符你是否還記得?


有些運算符在JAVA語言中存在着,但是在實際開發中我們或許很少用到它們,在面試題中卻時常出現它們的身影,對於這些運算符的含義和用法,你是否還記得呢?

自增(++)和自減(--)運算符

我們先來回答幾個問題吧:

Java代碼 複製代碼
  1. int i = 0;   
  2. int j = i++;   
  3. int k = --i;  
int i = 0;
int j = i++;
int k = --i;

這段代碼運行後,i等於多少?j等於多少?k等於多少?太簡單了?好,繼續:
Java代碼 複製代碼
  1. int i = 0;   
  2. int j = i++ + ++i;   
  3. int k = --i + i--;  
int i = 0;
int j = i++ + ++i;
int k = --i + i--;

代碼執行後i、j、k分別等於多少呢?還是很簡單?好,再繼續:
Java代碼 複製代碼
  1. int i=0;   
  2. System.out.println(i++);  
int i=0;
System.out.println(i++);

這段代碼運行後輸出結果是什麼?0?1?
Java代碼 複製代碼
  1. float f=0.1F;   
  2. f++;   
  3. double d=0.1D;   
  4. d++;   
  5. char c='a';   
  6. 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。

自增與自減運算符還遵循以下規律:

  1. 可以用於整數類型byte、short、int、long,浮點類型float、double,以及字符串類型char。
  2. 在Java5.0及以上版本中,它們可以用於基本類型對應的包裝器類Byte、Short、Integer、Long、Float、Double、Character。
  3. 它們的運算結果的類型與被運算的變量的類型相同。


下面的這個例子驗證以上列出的規律,它可以編譯通過並執行。

Java代碼 複製代碼
  1. public class Test {   
  2.     public static void main(String[] args) {   
  3.         // 整型   
  4.         byte b = 0;   
  5.         b++;   
  6.         // 整型   
  7.         long l = 0;   
  8.         l++;   
  9.         // 浮點型   
  10.         double d = 0.0;   
  11.         d++;   
  12.         // 字符串   
  13.         char c = 'a';   
  14.         c++;   
  15.         // 基本類型包裝器類   
  16.         Integer i = new Integer(0);   
  17.         i++;   
  18.     }   
  19. }  
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時,運算結果爲1;否則爲0。
  2. 按位或運算(|):二元運算符。當被運算的兩個值都爲0時,運算結果爲0;否則爲1。
  3. 按位異或運算(^):二元運算符。當被運算的兩個值中任意一個爲1,另一個爲0時,運算結果爲1;否則爲0。
  4. 按位非運算(~):一元運算符。當被運算的值爲1時,運算結果爲0;當被運算的值爲0時,運算結果爲1。


這裏不像我們看到的邏輯運算符(與運算&&、或運算||、非運算!)操作的是布爾值true或false,或者是一個能產生布爾值的表達式;“按位運算符”所指的“位”就是二進制位,因此它操作的是二進制的0和1。在解釋按位運算符的執行原理時,我們順便說說它們和邏輯運算符的區別。

[list=1]

邏輯運算符只能操作布爾值或者一個能產生布爾值的表達式;按位運算符能操作整型值,包括byte、short、int、long,但是不能操作浮點型值(即float和double),它還可以操作字符型(char)值。按位運算符不能夠操作對象,但是在Java5.0及以上版本中,byte、short、int、long、char所對應的包裝器類是個例外,因爲JAVA虛擬機會自動將它們轉換爲對應的基本類型的數據。


下面的例子驗證了這條規律:

Java代碼 複製代碼
  1. public class BitOperatorTest {   
  2.     public static void main(String[] args) {   
  3.         // 整型   
  4.         byte b1 = 10, b2 = 20;   
  5.         System.out.println("(byte)10 & (byte)20 = " + (b1 & b2));   
  6.         // 字符串型   
  7.         char c1 = 'a', c2 = 'A';   
  8.         System.out.println("(char)a | (char)A = " + (c1 | c2));   
  9.         // 基本類型的包裝器類   
  10.         Long l1 = new Long(555), l2 = new Long(666);   
  11.         System.out.println("(Long)555 ^ (Long)666 = " + (l1 ^ l2));   
  12.         // 浮點型   
  13.         float f1 = 0.8F, f2 = 0.5F;   
  14.         // 編譯報錯,按位運算符不能用於浮點數類型   
  15.         // System.out.println("(float)0.8 & (float)0.5 = " + (f1 & f2));   
  16.     }   
  17. }  
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
邏輯運算符的運算遵循短路形式,而按位運算符則不是。所謂短路就是一旦能夠確定運算的結果,就不再進行餘下的運算。

下面的例子更加直觀地展現了短路與非短路的區別:

Java代碼 複製代碼
  1. public class OperatorTest {   
  2.     public boolean leftCondition() {   
  3.         System.out.println("執行-返回值:false;方法:leftCondition()");   
  4.         return false;   
  5.     }   
  6.   
  7.     public boolean rightCondition() {   
  8.         System.out.println("執行-返回值:true;方法:rightCondition()");   
  9.         return true;   
  10.     }   
  11.   
  12.     public int leftNumber() {   
  13.         System.out.println("執行-返回值:0;方法:leftNumber()");   
  14.         return 0;   
  15.     }   
  16.   
  17.     public int rightNumber() {   
  18.         System.out.println("執行-返回值:1;方法:rightNumber()");   
  19.         return 1;   
  20.     }   
  21.   
  22.     public static void main(String[] args) {   
  23.         OperatorTest ot = new OperatorTest();   
  24.   
  25.         if (ot.leftCondition() && ot.rightCondition()) {   
  26.             // do something   
  27.         }   
  28.         System.out.println();   
  29.   
  30.         int i = ot.leftNumber() & ot.rightNumber();   
  31.     }   
  32. }  
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]
移位運算符

移位運算符和按位運算符一樣,同屬於位運算符,因此移位運算符的位指的也是二進制位。它包括以下幾種:

  1. 左移位(<<):將操作符左側的操作數向左移動操作符右側指定的位數。移動的規則是在二進制的低位補0。
  2. 有符號右移位(>>):將操作符左側的操作數向右移動操作符右側指定的位數。移動的規則是,如果被操作數的符號爲正,則在二進制的高位補0;如果被操作數的符號爲負,則在二進制的高位補1。
  3. 無符號右移位(>>>):將操作符左側的操作數向右移動操作符右側指定的位數。移動的規則是,無論被操作數的符號是正是負,都在二進制位的高位補0。


注意,移位運算符不存在“無符號左移位(<<<)”一說。與按位運算符一樣,移位運算符可以用於byte、short、int、long等整數類型,和字符串類型char,但是不能用於浮點數類型float、double;當然,在Java5.0及以上版本中,移位運算符還可用於byte、short、int、long、char對應的包裝器類。我們可以參照按位運算符的示例寫一個測試程序來驗證,這裏就不再舉例了。

與按位運算符不同的是,移位運算符不存在短路不短路的問題。

寫到這裏就不得不提及一個在面試題中經常被考到的題目:

引用
請用最有效率的方法計算出2乘以8等於幾?


這裏所謂的最有效率,實際上就是通過最少、最簡單的運算得出想要的結果,而移位是計算機中相當基礎的運算了,用它來實現準沒錯了。左移位“<<”把被操作數每向左移動一位,效果等同於將被操作數乘以2,而2*8=(2*2*2*2),就是把2向左移位3次。因此最有效率的計算2乘以8的方法就是“2<<3”。

最後,我們再來考慮一種情況,當要移位的位數大於被操作數對應數據類型所能表示的最大位數時,結果會是怎樣呢?比如,1<<35=?呢?

這裏就涉及到移位運算的另外一些規則:

  1. byte、short、char在做移位運算之前,會被自動轉換爲int類型,然後再進行運算。
  2. byte、short、int、char類型的數據經過移位運算後結果都爲int型。
  3. long經過移位運算後結果爲long型。
  4. 在左移位(<<)運算時,如果要移位的位數大於被操作數對應數據類型所能表示的最大位數,那麼先將要求移位數對該類型所能表示的最大位數求餘後,再將被操作數移位所得餘數對應的數值,效果不變。比如1<<35=1<<(35%32)=1<<3=8。
  5. 對於有符號右移位(>>)運算和無符號右移位(>>>)運算,當要移位的位數大於被操作數對應數據類型所能表示的最大位數時,那麼先將要求移位數對該類型所能表示的最大位數求餘後,再將被操作數移位所得餘數對應的數值,效果不變。。比如100>>35=100>>(35%32)=100>>3=12。


下面的測試代碼驗證了以上的規律:

Java代碼 複製代碼
  1. public abstract class Test {   
  2.     public static void main(String[] args) {   
  3.         System.out.println("1 << 3 = " + (1 << 3));   
  4.         System.out.println("(byte) 1 << 35 = " + ((byte1 << (32 + 3)));   
  5.         System.out.println("(short) 1 << 35 = " + ((short1 << (32 + 3)));   
  6.         System.out.println("(char) 1 << 35 = " + ((char1 << (32 + 3)));   
  7.         System.out.println("1 << 35 = " + (1 << (32 + 3)));   
  8.         System.out.println("1L << 67 = " + (1L << (64 + 3)));   
  9.         // 此處需要Java5.0及以上版本支持   
  10.         System.out.println("new Integer(1) << 3 = " + (new Integer(1) << 3));   
  11.         System.out.println("10000 >> 3 = " + (10000 >> 3));   
  12.         System.out.println("10000 >> 35 = " + (10000 >> (32 + 3)));   
  13.         System.out.println("10000L >>> 67 = " + (10000L >>> (64 + 3)));   
  14.     }   
  15. }  
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. 1 << 3 = 8
  2. (byte) 1 << 35 = 8
  3. (short) 1 << 35 = 8
  4. (char) 1 << 35 = 8
  5. 1 << 35 = 8
  6. 1L << 67 = 8
  7. new Integer(1) << 3 = 8
  8. 10000 >> 3 = 1250
  9. 10000 >> 35 = 1250
  10. 10000L >>> 67 = 1250

轉載:

 

作者:臧圩人(zangweiren)
發佈了103 篇原創文章 · 獲贊 5 · 訪問量 37萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章