Java核心技术卷1-3.5 运算符

    在Java中,**使用算术运算符+、-、*、/表示加、减、乘、除运算。当参与/运算的两个操作数都是整数时,表示整数除法;否则,表示浮点除法。整数的求余操作(有时称为取模)用%表示。**例如,15/2等于7,15%2等于1,15.0/2等于7.5.。

    需要注意,整数被0除将会产生一个异常,而浮点数被0除将会得到无穷大或NaN结果。

    可以在赋值语句中采用一种简化的格式书写二元算术运算符。

    例如,

x+=4;
复制代码

等价于

x=x+4;
复制代码

(通常,将运算符放在赋值号的左侧,如*=或%=)

注释:可移植性是Java语言的设计目标之一。无论在哪个虚拟机上运行,同一运算应该得到同样的结果。对于浮点数的算术运算,实现这样的可移植性是相当困难的。double类型使用64位存储一个double数值,而有些处理器使用80位浮点寄存器。这些寄存器增加了中间过程的计算精度。例如,下列运算:

double w=x*y/z;

很多Intel处理器计算x * y,并且将结果存储在80位的寄存器中,再除以z并将结果截断为64位。这样可以得到一个更加精确的计算结果,并且还能够避免产生指数溢出。但是,这个结果可能与始终在64位机器上计算的结果不一样。因此,Java虚拟机的最初规范规定所有的中间计算都必须进行截断。这种行为遭到了数值计算团体的反对。截断计算不仅可能导致溢出,而且由于截断操作需要消耗时间,所以在计算速度上要比精确计算慢。为此,Java程序设计语言承认了最优性能与理想结果之间存在的冲突,并给予了改进。在默认情况下,虚拟机设计者允许将中间计算结果采用扩展的精度。但是,对于使用strictfp关键字标记的方法必须使用严格的浮点计算来产生理想的结果。例如,可以把

main方法标记为

    public static strictfp void main(String[] args)

于是,在main方法中的所有指令都将使用严格的浮点计算。如果将一个类标记为strictfp,这个类中的所有方法都要使用严格的浮点计算。

实际的计算方式将取决于Intel处理器。在默认情况下,中间结果允许使用扩展的指数,但不允许使用扩展的尾数(Intel芯片在截断尾数时并不损失性能)。因此,这两种方式的

区别仅仅在于采用默认的方式不会产生溢出,而采用严格的计算有可能产生溢出。

如果没有仔细阅读这个注释,也没有什么关系。对大多数程序来说,浮点溢出不属于大问题。在本书中,将不使用strictfp关键字。

3.5.1 自增运算符与自减运算符

    当然,程序员都知道加1,减1是数值变量最常见的操作。在Java中,借鉴了C和C++的实现方式,也使用了自增、自减运算符:n++将变量n的当前值加1;n--将n的值减1

例如:

int n=12;
n++;
复制代码

n的值将变为13。因为这些运算符改变了变量的值,所以它的操作数不能是数值。例如,4++就是一条非法的语句。

    实际上,这两个运算符有两种形式。上面介绍的是运算符放在操作数后面的"后缀"形式,还有一种"前缀"形式,++n。两种方式都是对变量值加1。但在表达式中,这两种形式就有区别了。前缀方式先进行加1运算;后缀方式则使用变量原来的值

int m=7;
int n=7;
int a=2*++m;//now a is 16,m is 8
int b=2*n++;//now a is 14,n is 7
复制代码

    我们建议不要在其他表达式的内部使用++,这样编写的代码很容易令人困惑,并会产生烦人的bug。

    (当然,++运算符作为C++语言名称的一部分,也引发了有关程序设计语言的第一个笑话。C++的反对者认为这种语言的名称也存在着bug,他们说:“因为只有对它改进之后,我们才有可能使用它,所以它的名字应该命名为++C。)

3.5.2 关系运算符与boolean运算符

    Java包含各种关系运算符。其中,使用两个等号==检测是否相等。例如,3==7的值为false。

    使用!=检测是否不相等。例如,3!=7的值为true.

    另外,经常使用的运算符还有<(小于)、>(大于)、<=(小于等于)和>=(大于等于)。

    Java沿用了C++的习惯,用&&表示逻辑"与"、用||表示逻辑"或"。从!=运算符很容易看出,!表示逻辑"非"&&和||是按照"短路"方式求值的。如果第一个操作数已经能够确定表达式的值,第二个操作数就不必计算了。如果用&&对两个表达式进行计算:

expression1 && expression2
复制代码

    并且第一个表达式值为false,结果不可能为真。因此,第二个表达式的值就没有必要计算了。这种方式可以避免一些错误的发生。例如,表达式:

x!=0 && 1/x>x+y //nodivision by 0
复制代码

当x为0时,不会计算第二部分。因此,若x为0,1/x不被计算,也不会出现除以0的错误。

    与之类似,对于expression1 || expression2,当第一个表达式为true,结果自动为true,不必再计算第二部分。

    最后,Java支持三元操作符?:。在很多时候,这个操作符非常有用。表达式

x < y ? x : y 
复制代码

返回x和y中较小的那个值。

3.5.3 位运算符

    在处理整型数值时,可以直接对组成整型数值的各个位进行操作。这意味着可以使用屏蔽技术获得整数中的各个位。位运算符包括:

    &(与)、|(或)、^(异或)、~(非)

    这些运算符在位模式下工作。例如,如果n是一个整型变量,并且用二进制表示的n从右数第4位为1,那么

int fourthBitFromRight =(n&0b1000)/0b1000;
复制代码

返回1;否则返回0。通过运用2的幂次方的&运算可以将其他位屏蔽掉,而只保留其中的某一位。

注释:&和1运算符运用于布尔值,得到的结果也是布尔值。这两个运算符与&&和||的运算符非常相似,只是不按"短路"方式及时。即在得到计算结果之前,一定要计算两个操作数的值。

    另外,">>"和"<<"运算符将二进制位进行右移或左移操作。当需要建立位模式屏蔽某些位时,使用这个运算符十分方便:

int fourthBitFromRight =(n&(1<<3))>>3;
复制代码

最后,>>>运算符将用0填充高位; >>运算符用符号位填充高位。没有 <<<运算符

警告:对移位运算符右侧的参数需要进行模32的运算(除非左边的操作数是long类型,

在这种情况下需对右侧操作数模64)。例如,1 << 35与1 << 3或8是相同的。

C++注释:在C或C++中无法确定 >> 操作执行的是算术移位(扩展符号位),还是逻辑移位(高位填0)。在执行中将会选择效率较高的一种。这就是说,在C/C++中,>> 运算符实际上只是为非负数定义的。Java消除了这种含糊性。

3.5.4 数学函数与常量

    在Math类中,包含了各种各样的数学函数。在编写不同类别的程序时,可能需要的函数也不同。

    要想计算一个数值的平方根,可以使用sqrt方法:

double x=4;
double y=Math.sqrt(x);
System.out.println(y);//prints 2.0
复制代码

注释:println方法和sqrt方法存在微小的差异。println方法操作一个定义在System类中的System.out对象。但是,Math类中的sqrt方法处理的不是对象,这样的方法被称为静态方法。有关静态方法的详细内容请参看第4章。

    在Java中,没有幂函数,因此需要借助于Math类的pow方法。语句:

double y = Math.pow(x,a);
复制代码

将y的值设置为x的a次幂(x^a)。pow方法有两个double类型的参数,其返回结果也为double类型。

    Math类提供了一些常用的三角函数:

Math.sin
Math.cos
Math.tan
Math.atan
Math.atan2
复制代码

还有指数函数以及它的反函数---自然对数以及以10为底的对数:

Math.exp
Math.log
Math.log10
复制代码

最后,Java还提供了2个用于表示π和e常量的近似值:

Math.PI
Math.E
复制代码

提示:从JDK 5.0开始,不必在数学方法名和常量名前添加前缀“Math.”,而只要在源文

件的顶部加上下列内容就可以了。

例如:

import static java.lang.Math.*;

在第4章中将讨论静态导入。

System.out.println("The square root of \u03C0 is"+sqrt(PI));

注释:在Math类中,为了达到最快的性能,所有的方法都使用计算机浮点单元中的例程。如果得到一个完全可预测的结果比运行速度更重要的话,那么就应该使用StrictMath类。它使用“自由发布的Math库”(fdlibm)实现算法,以确保在所有平台上得到相同的结果。有关这些算法的源代码请参看www.netlib.org/fdlibm/ index.html(当fdlibm为一个函数提供了多个定义时,StrictMath类就会遵循IEEE 754版本,它的名字将以“e”开头)。

3.5.5 数值类型之间的转换

    在程序运行时,经常需要将一种数值类型转换为另一种数值类型。图3-1给出了数值之间的合法转换。

    在图3-1中有6个实心箭头,表示无信息丢失的转换;有3个虚箭头,表示可能有精度损失的转换。例如,123456789是一个大整数,它所包含的位数比float类型所能够表达的位数多。当将这个整型数值转换为float类型时,将会得到同样大小的结果,但却失去了一定的精度。

int n=123456789;
float f=n;// f is 1.234567892E8
复制代码

    当使用上面两个数值进行二元操作时(例如n+f,n是整数,f是浮点数),先要将两个操作数转换为同一种类型,然后在进行计算。

  • 如果两个操作数中有一个是double类型的,另一个操作数就会转换为double类型。
  • 否则,如果其中一个操作数是float类型,另一个操作数将会转换为float类型。
  • 否则,如果其中一个操作数是long类型,另一个操作数将会转换为long类型。
  • 否则,两个操作数都将被转换为int类型。

3.5.6 强制类型转换

    在上一小节中看到,在必要的时候,int类型的值将会自动地转换为double类型。但另一方面,有时也需要将double转换成int。在Java中,允许进行这种数值之间的类型转换。当然,有可能会丢失一些信息。在这种情况下,需要通过强制类型转换(cast)实现这个操作。强制类型转换的语法格式是在圆括号中给出想要转换的目标类型,后面紧跟待转换的变量名。例如:

double x=9.997;
int nx=(int)x;
复制代码

这样,变量nx的值为9。强制类型转换通过截断小数部分将浮点值转换为整型。

    如果想对浮点数进行舍入运算,以便得到最接近的整数(在很多情况下,希望使用这种操作方式),那就需要使用Math.round方法:

double x=9.997
int nx=(int)Math.round(x);
复制代码

现在,变量nx的值为10。当调用round的时候,仍然需要使用强制类型转换(int)。其原因是round方法返回的结果是long类型,由于存在信息丢失的可能性,所以只有使用显式的强制类型转换才能够将long类型转换成int类型。

警告:如果试图将一个数值从一种类型强制转换为另一种类型,而又超出了目标类型的表示范围,结果就会截断成一个完全不同的值。例如,(byte)300的实际值为44。

C++注释:不要在boolean类型与任何数值类型之间进行强制类型转换,这样可以防止发生错误。只有极少数的情况才需要将布尔类型转换为数值类型,这时可以使用条件表达式b?1:0。

3.5.7 括号与运算符级别

    表3-4给出了运算符的优先级。如果不使用圆括号,就按照给出的运算符优先级次序进行计算。同一个级别的运算符按照从左到右的次序进行计算(除了表中给出的右结合运算符外。)例如,由于&&的优先级比||的优先级高,所以表达式

a && b || c
复制代码

等价于

(a && b) || c
复制代码

又因为+=是右结合运算符,所以表达式

a += b += c
复制代码

等价于

a += (b += c)
复制代码

也就是将b+=c的结果(加上c之后的b)加到a上。

C++注释:与C或C++不同,Java不使用逗号运算符。不过,可以在for语句中使用逗号分隔表达式列表。

3.5.8 枚举类

    有时候,变量的取值只在一个有限的集合内。例如:销售的服装或比萨饼只有小、中、大和超大这四种尺寸。当然,可以将这些尺寸分别编码为1、2、3、4或S、M、L、X。但这样存在着一定的隐患。在变量中很可能保存的是一个错误的值(如0或m)。

     从JDK 5.0开始,针对这种情况,可以自定义枚举类型。枚举类型包括有限个命名的值。

例如,

enum Size {SMALL,MEDIUM,LARGE,EXTRA_LARGE};
复制代码

现在,可以声明这样一种类型的变量:

Size s=Size.MEDIUM;
复制代码

Size类型的变量只能存储这个类型声明中给定的某个枚举值,或者null值,null表示这个变量没有设置任何值。

    有关枚举类型的详细内容将在第5章介绍。

猜你喜欢

转载自juejin.im/post/7097158180576165902