Java定义了几个位运算符,它们都可以用于整数类型(long、int、short、byte以及char)。这些运算符对操作数的单个位进行操作。表1 对位运算符进行了总结。

表1  位运算符

由于位运算符是对整数中的位进行操作,因此理解这类操作会对数值造成什么影响是很重要的。特别是,掌握Java存储整数数值的方式以及如何表示负数是有用的。因此,在介绍位运算符之前,先简要描述以下这两个主题。

在Java中,所有整数类型都由宽度可变的二进制数字表示。例如,byte型数值42的二进制形式是00101010,其中每个位置表示2的幂,从最右边的20开始。向左的下一个位置位21,即2;接下来是22,即4;然后是8、16、32,等等,所以42在位置1、3、5(从右边开始计算,最右边的位计数位0)被设置1;因此,42是21+23+25的和,即2+8+32。

所有整数类型(char类型除外)都是有符号整数,这意味着它们既可以表示正数,也可以表示负数。java使用所谓的“2的补码”进行编码,这意味着负数的表示方法为:首先反转数值中的所有位(1变为0,0变为1),然后再将结果加1.例如,-42的表示方法位:首先反转42中所有位(00101010),得到11010101,然后加1,结果为11010110,即-42。为了解码负数,首先先反转所有位,然后加1.例如,反转-42(11010110),得到00101001,即41,所以再加上1就得到了42。

如果分析“零交叉”问题,就不难理解Java(以及大多数其它计算机语言)使用2的补码表示负数的原因。假定对于byte型数值,0被表示为00000000.如果使用1的补码,简单地反转所有位,得到11111111,这会创建-0.但问题是,再整数数学中,-0是无效的。使用2的补码表示负数可解决这个问题。如果使用2的补码,1被加到补码上,得到100000000,这样就在左边新增了一位,超出了byte类型表示的范围,从而得到了所期望的行为,即-0和0相同,并且-1被编码位11111111.尽管再前面的例子中使用的是byte数值,但是相同的基本原则被应用与Java中的所有整数类型。

因为Java使用2的补码存储负数,并且因为Java中的所有整数都是有符号数值,所以应用位运算符时很有可能产生意外的结果。例如,不管是有意还是无意的,将高阶位改为1,都会导致结果值被解释为负数。为了避免产生不愉快的结果,只需要记住高阶位决定了整数的符号,而不管高阶位是如何设置的。

1  位逻辑运算符

位逻辑运算符包括&、|、^和~。表2显示了各种位逻辑运算的结果。在后续的使用中,请牢记位运算符是针对操作数中的每个位进行操作的。

表2  位逻辑运算

1)按位取反

也称为“位求补”。一元非运算符“~”可以反转操作数中的所有位。例如数字42,位模式如下:

00101010

进行“非”运算之后,变为:

11010101

2)按位与

对于按位与运算符“&”,如果两个操作数都是1,结果为1,只要其中任何一个操作数为0,结果就为0。下面是一个例子:

00101010  42

&00001111  15

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

00001010  10

3)按位或

按位或运算符“|”的运算规则为:只要两个操作数中有一个为1,结果就为1。实例如下所示:

00101010   42

|00001111   10

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

00101111   47

4)按位异或

按位异或运算符“^”的运算规则为:如果只有一个操作数为1,结果为1,否者结果为0。下面的例子演示了“^”运算符的效果。这个例子还演示了按位异或运算的一个有用特性。请注意,只要第二个操作数中的某位为1,就会反转42的位模式中的对应位;只要第二个操作数中的某位为0,第一个操作数中的对应就保持不变。当执行某些类型的位数操作时,将会发现该特性很有用。

00101010   42

^00001111   10

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

00101010   37

5)使用位逻辑运算符

下面的程序演示了位逻辑运算符的用法:

public class BitLogic{

public static void main(String[] args){

String[] binary = {"0000","0001","0010","0011","0100","0101","0110", "0111","1000","1001","1010","1011","1100","1101","1110","1111"};

int a = 3;

int b = 6;

int c = a | b;

int d = a & b;

int e = a ^ b;

int f = (~a & b) | (a & ~b);

int g = ~a & 0x0f;

System.out.println("        a = " + binary[a]);

System.out.println("        b = " + binary[b]);

System.out.println("      a|b = " + binary[c]);

System.out.println("      a&b = " + binary[d]);

System.out.println("      a&b = " + binary[e]);

System.out.println("~a&b|a&~b = " + binary[f]);

System.out.println("       ~a = " + binary[g]);

}

}

在这个例子中,a和b的位模式包含了两个二进制位的所有4种可能:0-0、0-1、1-0以及1-1。根据c和d中的结果,可以看出“|”和“&”对每一位的操作方式。e和f被赋值为相同的值,并演示了“^”运算的工作原理。字符串数组binary中保存了介于0到15的数字的二进制表示形式。在这个例子中,为了显示每个结果的二进制表示形式,对数组进行了索引。二进制数值n的字符串表示恰好存储在binary[n]中。将~a和0x0f(二进制00001111)进行按位与运算,以减小其值,使其小于16,从而可以使用binary数组输出结果。下面图1是该程序的输出。

图1  BitLogic运行结果

2  左移

左移运算符“<<”可以将数值中的所有位向左一定指定的次数,它的一般形式为:

value << num

其中,num指定了将value中的数值向左移动的位数,即“<<”将指定值中的所有位向左移动由num指定的位数。对于每次位移,高阶位被移出(并丢失),右边的位用0补充。这意味着左移int型操作数时,如果某些位一旦超出位31,那么这些位将丢失。如果操作数为long类型,那么超出位63的位会丢失。

当左移byte和short型数值时,Java的自动类型转换会导致意外的结果。您知道,当表达式进行求值时,byte换个short型数值会被提升为int型。而且,这种表达式的结果也是int型。这意味着对byte和short型数值进行左移操作的结果为int型,并且移动的位不会丢失,除非它们超过位31。此外,当将负数的byte和short型数值提升为int型时,会进行符号扩展。因此,高阶位将使用1填充。所以,对byte和short型数值进行左移操作,必须抛弃int型结果的高阶字节。例如,如果左移byte型数值,会先将该数值提升为int型,然后左移。这意味着如果您想要的结果是移位后的byte型数值,就必须丢弃结果的前三个字节。完成这个任务最容易的方法是,简单地将结果转换为byte类型。下面的程序演示了这一概念。

public class ByteShift{

public static void main(String[] args){

byte a = 64;

int i = a << 2;

byte b = (byte)(a << 2);

System.out.println("a : " + a);

System.out.println("i : " + i);

System.out.println("b : " + b);

}

}

编译并运行这个程序,控制台将显示如图2所示的信息。

图2  ByteShift运行结果

由于为了进行求值,a被提升为int类型,因此对64(01000000)左移两次,使得i包含256(100000000)。但是,b中的值为0,因为移位之后,现在低字节为0。只有一位被移出了。

因为每次左移相当于将原始值乘以2,所以程序员经常利用这个事实作为乘以2的高效替代方法。但是需要小心。如果将二进制1移进高阶位(第31位或第63位),结果会变为负数。

2.3  右移

右移运算符“>>”可以将数值中的所有位向右移动指定的次数,它的一般形式为:

value >> num

其中,num指定了将value中的数值向右边移动的位数,即“>>”将指定值中的所有位向右移动由num指定的位数。

下面的代码将数值32向右边移动两位,结果是a被设置为8:

int a = 32;

a = a >> 2;

如果数值中的有些位被“移出”了,这些位会丢失。例如,下面的代码将35右移两位,从而导致两个低阶位丢失,结果是再次将a设置为8:

int a = 35;

a = a >> 2;

用二进制形式分析同一操作,可以更清晰地看出操作过程;

00100011   35

>> 2

00001000    8

每次右移一个值,相当于将改值除以2,并丢弃所有余数。可以利用这一特性,实现高性能的整数除以2操作。

当进行右移操作时,右移后的顶部(最左边)位使用右移前顶部位的值填充。这称为符号扩展,当对负数景行右移操作时,该特性可以保留负数的符号。例如,-8>>1的结果是-4,用二进制表示为:

11111000   -8

>> 1

11111100   -4

有趣的是,如果对-1进行右移,结果总是-1,因为符号扩展使得高阶位总是1。

2.4  无符号右移

每次位移时,“>>”运算符自动使用原来的内容填充高阶位。这个特性可以保持数值的符号。但是,有时这不是期望的效果。例如,如果对那些表示非数值的内容进行位置操作,可能不希望发生符号扩展。当操作基于像素的值和图形时,这种情况非常普遍。对于这种情形,不管高阶位的初始值时什么,通常会希望将0移进高阶位。这就是所谓的无符号右移。为了完成无符号右移,需要使用Java的无符号右移运算符“>>>”,该运算符总是将0移进高阶位。

下面的代码演示了“>>>”的用法。其中,a被设置为-1,这会将所有的32位设置为二进制1。然后将该值右移24位,并使用0填充高端的24位,而忽略常规的符号扩展。该操作将a设置位255.

int a = -1;

a = a >>> 24;

下面时同一操作的二进制表示形式,以进一步演示这个操作的具体过程:

11111111 11111111 11111111 11111111   -1

>>> 24

00000000 00000000 00000000 11111111  255

“>>>”运算符可能不是那么有用,因为只有对于32位和62位数值它才有意义。请记住,表达式中更小的数值会自动提升为int型。

2.5  位运算符与赋值的组合

所有二元位运算符都具有与算术运算符类似的复合形式,这些运算符将赋值运算符和位运算组合到一起。例如,下面的两行语句是等价的。都是将a的值右移4位:

a = a >> 4;

a >>= 4;

类似地,下面这两行语句也是等价的,都是将a设置位表达式“a|b”:

a = a | b;

a |= b;

下面的程序创建了几个整型变量,然后使用复合位运算符对这些变量进行操作:

public class OpBitEquals{

public static void main(String[] args){

int a = 1;

int b = 2;

int c = 3;

a |= 4;

b >>= 1;

c <<= 1;

a ^= c;

System.out.println("a = " + a);

System.out.println("b = " + b);

System.out.println("c = " + c);

}

}

编译并运行这个程序,控制台将显示图3所示的信息。

图3  OpBitEquals运行结果

IT兄弟连 Java语法教程 位运算符的更多相关文章

  1. IT兄弟连 Java语法教程 算符运算符

    Java提供了丰富的运算符环境.可以将大部分Java运算符划分为4组:算术运算符.位运算符.关系运算符以及逻辑运算符.Java还定义了一些用于处理某些特定情况的附加运算符.本章将介绍除类型比较运算符i ...

  2. IT兄弟连 Java语法教程 关系运算符

    关系运算符用来判定一个操作数与另外一个操作数之间的关系.特别是,它们可以判定相等和排序关系.表7中列出了关系运算符. 表7  关系运算符 关系运算符的结果为布尔值.关系运算符最常用与if语句和各种循环 ...

  3. IT兄弟连 Java语法教程 标识符和关键字

    Java语言也和其它编程语言一样,使用标识符作为变量.对象的名字.也提供了一系列的关键字用以实现特别的功能.本小节将详细介绍Java语言的标识符和关键字等内容. 1.分隔符 Java语言里的分号“;” ...

  4. IT兄弟连 Java语法教程 Java的发展历程

    只有少数几种编程语言对程序设计带来过根本性的影响.其中,Java的影响由于迅速和广泛而格外突出.可以毫不夸张的说,1995年Sun公司发布的Java1.0给计算机程序设计领域带来了一场变革.这场变革迅 ...

  5. IT兄弟连 Java语法教程 数组 经典案例

    案例需求: 编程实现双色球中奖号码的生成 1)应用知识: ●  数组的声明 ●  数组的使用 ●  for循环 2)需求解析: 在该程序中,需要定义一个长度为7的数组,用来存储中奖号码,使用Rando ...

  6. IT兄弟连 Java语法教程 数组 多维数组 二维数组的初始化

    二维数组的初始化与一位数组初始化类似,同样可以使用静态初始化或动态初始化. 1)静态初始化 静态初始化的格式如下: 数组名字 = new 数组元素的类型[][]{new 数组元素的类型[]{元素1,元 ...

  7. IT兄弟连 Java语法教程 数组 多维数组 二维数组的声明

    Java语言里提供了支持多维数组的语法.但是这里还想说,从数组底层的运行机制上来看是没有多维数组的. Java语言里的数组类型是引用类型,因此数组变量其实是一个引用,这个引用指向真实的数组内存,数组元 ...

  8. IT兄弟连 Java语法教程 Java语法基础 经典面试题

    1.Java语言中有几种基本类型?分别是什么?请详细说明每种类型的范围以及所占的空间大小? Java语言中有8中基本类型,分别是代表整形的byte.short.int和long,代表浮点型的float ...

  9. IT兄弟连 Java语法教程 逻辑运算符

    表8中显示的布尔逻辑运算符只能操作布尔类型的操作数,所有的二元逻辑运算符都可以组合两个布尔值,得到的结果为布尔类型. 表8  布尔逻辑运算符 布尔逻辑运算符”&“.”|“以及”^“,都会布尔值 ...

随机推荐

  1. RU/RUR的安装

    RU/RUR的安装方法是仍然使用现有的Opatch技术来安装RU/RUR. 更多常见问题,请参考文档: Release Update and Release Update Revisions for ...

  2. TICK技术栈(五)Kapacitor安装及使用

    1.什么是Kapacitor? Kapacitor是InfluxData开源的数据处理引擎.它可以处理来自InfluxDB的流数据和批处理数据,并且用户可以用tickScript脚本来处理,监视和警报 ...

  3. 西北师大-2108Java】第十三次作业成绩汇总

    [西北师大-2108Java]第十三次作业成绩汇总 作业题目 面向对象程序设计(JAVA) 第15周学习指导及要求 实验目的与要求 (1)掌握菜单组件用途及常用API: (2)掌握对话框组件用途及常用 ...

  4. tensorflow基础-数据类型

    一:tensorflow中的计算定义和执行 首先,对于tensorflow来说,最重要的概念就是图(Graph)和会话(Session),tensorflow的计算思想是:以图的形式来表示模型,表示和 ...

  5. Python文件操作:文件的打开关闭读取写入

    Python文件操作:文件的打开关闭读取写入 一.文件的打开关闭 Python能以文本和二进制两种方式处理文件,本文主要讨论在Python3中文本文件的操作. 文件操作都分为以下几个步骤: 1.打开文 ...

  6. Java中的String为什么要设计成不可变的?

    一.不可变类和不可变对象 Normally,you create an object and allow its contents to be changed later.However ,occas ...

  7. Web前端基础(10):JavaScript(四)

    1. 伪数组arguments arguments代表的是实参.有个讲究的地方是:arguments只在函数中使用. 1.1 返回参数个数 返回函数实参的个数:arguments.length 例子: ...

  8. form表单提交与ajax消息传递

    form表单提交与ajax消息传递 1.前后端传输数据编码格式contentType: urlencoded 对应的数据格式:name=xxx&password=666 后端获取数据:requ ...

  9. Java集合专题

    1.Java 中常用的容器有哪些? 常见容器主要包括 Collection 和 Map 两种,Collection 存储着对象的集合,而 Map 存储着键值对(两个对象)的映射表. Collectio ...

  10. socket经典案例-发送数据

    一:客户端向服务端发送数据. 服务端: package com.company.s; import java.io.*; import java.net.ServerSocket; import ja ...