8. java入门:运算符(二)

576 阅读4分钟

本文介绍数据运算的溢出、类型转换和运算符优先级等知识。

溢出

Java中的几种数据类型都有其能表示的范围。

public class NumOverflow {
  public static void main(String[] args) {
    int intMin = Integer.MIN_VALUE;
    int intMax = Integer.MAX_VALUE;
​
    long longMin = Long.MIN_VALUE;
    long longMax = Long.MAX_VALUE;
​
    System.out.println("int 的范围为 " + intMin + " ~ " + intMax);
    System.out.println("long 的范围为 " + longMin + " ~ " + longMax);
  }
}

输出的结果为:

int 的范围为 -2147483648 ~ 2147483647
long 的范围为 -9223372036854775808 ~ 9223372036854775807

思考一个问题,如果在一个int类型的变量加上某一个值后,超过了int能表示的最大值,那么结果是什么呢,程序会报错吗?

public class NumOverflow {
  public static void main(String[] args) {
    int intMin = Integer.MIN_VALUE;
    int intMax = Integer.MAX_VALUE;
​
    int a = intMax + 10;
    int b = intMin - 10;
    // -2147483639
    System.out.println(a);
    // 2147483638
    System.out.println(b);
  }
}

实际上程序并没有报错,它仍然会正常计算,只不过符号位改变了,导致结果不符合我们的预期。

2147483647:  0111 1111 1111 1111 1111 1111 1111 1111
+10:         0000 0000 0000 0000 0000 0000 0000 1010
————————————————————————————————————————————————————
-2147483639: 1000 0000 0000 0000 0000 0000 0000 1001                                     

溢出问题看似是小问题,但往往会成为各大公司的研发团队的绊脚石。

比如,在公司发展的初期,可能把用户id、商品id等字段定义为int类型,或者是前后端定义的数据类型不一致,随着数据量的持续增长,数据发生了溢出,造成线上故障。这时候,研发团队就会花一些精力去排查各个产线的数据溢出隐患。

整数运算在除数为0时会报错,而浮点数运算在除数为0时,不会报错,但会返回几个特殊值:

  • NaN表示Not a Number
  • Infinity表示无穷大
  • -Infinity表示负无穷大

例如:

public class FloatOverflow {
  public static void main(String[] args) {
    // NaN
    double d = 0.0 / 0;
    // Infinity
    double e = 10.0 / 0;
    // -Infinity
    double f = -10.0 / 0;
​
    System.out.println(e);
​
    System.out.println(Double.isNaN(d));
    System.out.println(Double.isFinite(e));
    System.out.println(Double.isInfinite(f));
  }
}

Double类提供了三个方法来判断浮点数是否是无限大,是否不是一个数字。

类型转换

两个数进行运算时,会进行隐式的转换,把其中一种类型转换为另一种类型,两种相同类型的变量再进行操作。

隐式转换的规则是向箭头指向的方向进行转换。

  • 有double操作数,另一个数就转double;
  • 有float,就转float;
  • 有long,就转long;
  • 其他情况都转int;

上图中,橙色箭头是无损精度的转换,黑色箭头是有损精度的转换。

除了隐式转换外,Java运行强制类型转换。比如:

public class NumberCast {
  public static void main(String[] args) {
    double f = 89.98;
    // a = 89
    int a = (int) f;
​
    long b = 900L;
    a = (int) b;
    
    // 向上转换,不需要强制转换
    long c = a;
  }
}

运算符优先级

类别操作符关联性
后缀() [] . (点操作符)左到右
一元expr++ expr--从左到右
一元++expr --expr + - ~ !从右到左
乘性* / %左到右
加性+ -左到右
移位>> >>> <<左到右
关系> >= < <=左到右
相等== !=左到右
按位与左到右
按位异或左到右
按位或左到右
逻辑与&&左到右
逻辑或左到右
条件?:从右到左
赋值= += -= *= /= %= >>= <<= = ^= |=从右到左

记住几个关键点:

  • 括号优先级最高,赋值运算符最低;
  • 加减乘除四则运算符合数学中的优先级定义;
  • 自增自减高于算术运算符;
  • 算术运算符高于移位和关系运算符。

在写程序的时候,如果不确定运算符的结合性和优先级,可以加入括号使代码可读性更高,也减少出错的可能。

总结

  • 溢出可能造成运行结果不符合预期,写代码时要注意。
  • 类型转换分隐式转换和强制转换。隐式转换会自动向上转换。
  • 运算符区分优先级,不确定时加括号。

Info
以上代码托管在github