Java 运算符(学习 Java 编程语言 008)

运算符用于连接值。Java 提供了一组丰富的算术和逻辑运算符以及数学函数。

1. 算术运算符

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

如果参与运算的两个操作数都是整数时,表示整数运算,否则表示浮点运算。

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

int resultInt;
double resultDouble;

resultInt =15 / 2;
System.out.println(resultInt);    // 输出 7

resultDouble = 15.0 /2;
System.out.println(resultDouble); // 输出 7.5

resultInt =15 % 2;
System.out.println(resultInt);    // 输出 1

resultDouble = 15.0 /0;
System.out.println(resultDouble); // 输出 Infinity

resultInt = 15/ 0;
System.out.println(resultInt);    // 抛出 java.lang.ArithmeticException: / by zero

可移植性是 Java 语言的设计目标之一。无论在哪个虚拟机上运行,同一运算应该得到同样的结果。对于浮点数的算术运算,实现这样的可移植性是相当困难的。double 类型使用 64 位存储一个数值,而有些处理器使用 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 芯片在截断尾数时并不损失性能)。因此,这两种方式的区别仅仅在于采用默认的方式不会产生溢出,而采用严格的计算有可能产生溢出。

对于大多数程序来说,浮点溢出不属于大问题。

2. 数学函数与常量

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

double x = 4;
double y = Math.sqrt(x); // 计算 x 的平方根
System.out.println(y);   // 打印 2.0
y = Math.pow(x, 3);      // 计算 x 的 3  次幂
System.out.println(y);   // 打印 64.0

pow 方法有两个 double 类型的参数,其返回结果也为 double 类型。

floorMod 方法的目的是解决一个长期存在的有关于整数余数的问题。参考下列代码:

int n;
int mod;

n = 2;
mod = n % 2;
System.out.println(mod); // 打印 0

n = 1;
mod = n % 2;
System.out.println(mod); // 打印 1

n = 0;
mod = n % 2;
System.out.println(mod); // 打印 0

n = -1;
mod = n % 2;
System.out.println(mod); // 打印 -1
        
n = -1;
mod = Math.floorMod(n, 2);
System.out.println(mod); // 打印 1

n = -1;
mod = Math.floorMod(n, -2);
System.out.println(mod); // 打印 -1

n = -2;
mod = n % 2;
System.out.println(mod); // 打印 0

-3 % 2 表达式的值为 -1。而数学家们几遍年来都知道这样一个最优(或称“欧几里得”)规则:余数总是要 ≥ 0。遗憾的是,对于负除数,floorMod 会得到负数结果,不过这种情况在实际情况中很少出现。

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

Math.sin
Math.cos
Math.tan
Math.atan
Math.atan2

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

Math.exp
Math.log
math.log10

提供了两个用于表示 π 和 e 常亮的最接近的近似值。

Math.PI
Math.E

在 Math 类中,为了达到最佳的性能,所有方法都使用计算机浮点单元中的例程。如果得到一个完全可预测的结果比运行速度更重要的话,就应该使用 StrictMath 类。它实现了“可自由分发的数学库(Freely Distributable Math Library, FDLIBM)”的算法,确保在所有的平台上得到相同的结果。

Math 类提供了一些方法使整数有更好的运算安全性。如果一个计算溢出,数学运算符只是悄悄地返回错误的结果而不做任何错误提醒。例如 10 亿乘以 3(1000000000 * 3)的结果为 -1294967296,因为最大的 int 值也只是刚刚超过 20 亿。如果调用 Math.multiplyExact(1000000000, 3),就会产生一个异常,可以捕获这个异常或者让程序终止,而不是允许它给出一个错误的结果后悄无声息地继续运行。还有另外的一些方法:addExact、subtractExat、decremenExact 和 negate 也可以正确的处理 int 和 long 参数。

3. 结合赋值和运算符

可以在赋值中使用二元运算符,这是一种很方便的简写形式。
x += 7;
等价于
x = x + 7;
(一般来说,要把运算符放在 = 号左边,如 *= 或 %=)

如果运算符得到一个值,其类型与左侧操作数的类型不同,就会发生强制类型转换。例如,如果 x 是一个 int,则以下语句
x += 3.5;
是合法的,将把 x 设置为 (int)(x + 3.5)

4. 自增与自减运算符

Java 提供了自增、自减运算符:n++ 将变量 n 的当前值加 1,n-- 将 n 的值减 1。

int n = 12;
n++;

上述代码中,将 n 的值改为 13。由于这些运算符改变的是变量的值,所以它们不能应用于数值本身。例如,7++ 就不是一个合法的语句。

自增、自减运算符有两种形式:

  • 后缀形式,如 n++,变量 n 会使用原来的值,然后再加 1。
  • 前缀形式: 如 ++n,变量 n 先完成加 1。

二者的区别是在表达式中,前缀形式会先完成加 1;而后缀形式会使用变量原来的值。

int m = 7;
int n = 7;
int a = ++m; // now a is 8, m is 8
int b = n++; // new b is 7, n is 8

建议不要在表达式中使用 ++,因为这样的代码很容让人困惑,而且会带来烦人的 bug。

5. 关系和 booolean 运算符

要检测相等性,可以使用两个等号 ==,使用 != 检测不等性。

Java 经常使用<(小于)、>(大于)、<=(小于等于)和>=(大于等于)运算符。

Java 使用 && 表示逻辑“与”运算符,使用 || 表示逻辑“或”运算符。感叹号 ! 就是逻辑非运算符。

&&|| 运算符是按照“短路”方式来求值的:如果第一个操作数已经能够确定表达式的值,第二个操作数就不必计算了。比如 && 运算符合并两个表达式:
expression1 && expression2
计算得到第一个表达式的值为 false,那么结果就不可能为 true。因此,第二个表达式就不必计算了。可以利用这一点来避免错误。例如下面表达式:
x != 0 && 1 / x > x + y // no division by 0
如果 x 等于 0,那么第二部分就不会计算。因此,如果 x 为 0,也就不计算 1 / x,除以 0 的错误就不会出现。

如果第一个表达式为 true,expression1 || expression2 的值就自动为 true,而无需计算第二个表达式。

Java 支持三元操作符 ?:,这个操作符有时很有用。如果条件为 true,下面的表达式
condition ? exprestion1 : expression2
就为第一个表达式的值,否则计算为第二个表达式的值。

例如:
x < y ? x : y
会返回 x 和 y 中较小的一个。

6. 位运算符

处理整数类型时,可以直接对组成整型数值的各个位完成操作。这意味着可以使用掩码技术得到整数中的各个位。位元算符包括:
&("and") |("or") ^("xor") ~("not")
这些运算符按位模式处理。例如,如果 n 整数变量,而且用二进制表示的 n 从右边数第 4 位为 1,则
int fourthBitFromRight = (n & 0b1000) / 0b1000;
会返回 1,否则返回 0。利用 & 并结合使用适当的 2 的幂,可以把其他位掩饰掉,而只保留其中的某一位。

应用在布尔值上时,& 和 | 运算符也会得到一个布尔值。这些元算符与 && 和 || 运算符很类似,不过 & 和 | 不采用“短路”的方式来求值,也就是说,得到计算结果之前两个操作数都按需要计算。

另外,还有 >><< 运算符将位模式左移或右移。需要建立位模式来完成位掩码时,这两个元算符会很方便:
int fourthBitFromRight = (n & (1 << 3)) >> 3;

>>> 运算符会用 0 填充高位,这与 >> 不同,它会用符号位填充高位。不存在 <<< 运算符。

警告: 位运算符的右操作数要完成模 32 的运算(除非左操作数是 long 类型,在这种情况下需要对右侧操作数模 64)。例如,1 << 35 的值等同于 1 << 38

在 C++ 中,不能保证 >> 是完成算术移位(扩展符号位)还是伙计移位(填充 0)。实现者可以选择其中更高效的的任何一种做法。这意味着 C/C++ 中的 >> 运算符对于负数生成的结果可能会依赖于具体的实现。Java 则消除了这种不确定性。

7. 括号与运算符级别

不使用圆括号时,就按照给出的运算符优先级次序进行计算(但右结合运算符除外)。

运算符优先级:

运算符结合性
[] . ()(方法调用)从左向右
! ~ ++ – + (一元运算) - ( 一元运算) () (强制类型转换) new从右向左
* / %从左向右
+ -从左向右
<< >> >>>从左向右
< <= > >= instanceof从左向右
== !=从左向右
&从左向右
^从左向右
|从左向右
&&从左向右
||从左向右
?:从右向左
= += -= *= /= %= &= |= ^= <<= >>= >>>=从右向左
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值