aoe1231

看得完吗

JavaSE——3、操作符

1、算数运算符

即 + 、 - 、 * 、 / 、 %(取余)。

  • 除法得到的是商,取余得到的是余数;
  • 整数相除只能得到整数;
  • 字符的运算是用其在计算机底层对应的数值来进行计算的;
  • 算数表达式中包含多个基本数据类型的值的时候,整个算数表达式的类型会自动进行提升;
  • 两个字符串相加(只要有一个是字符串),结果为字符串的拼接。如:"abc"+"d" 结果为"abcd";

2、赋值运算符

即 = (赋值)。如 a += 1 (即 a = a + 1,扩展的赋值运算符隐含了强制类型转换)。

3、自增自减运算符

即 ++(自增)、--(自减)。如:j = i++,表示 i 先赋值再自增;j = ++i,表示 i 先自增再赋值。

4、关系运算符

即 ==(等于) 、!=(不等于) 、> 、>= 、< 、<=,结果为 bool 类型。

注意,使用 == 做比较时:

  • 双方为基本类型时,比较的是数据值是否相同;
  • 双方为引用类型时,比较的是地址值是否相同。

4.1、基本类型包装类的比较

4.1.1、比较Integer

在使用Integer的时候,你应该只使用equals()。以下示例说明了比较Integer类型时会出现的意料之外的情况。

public class Equivalence {
  static void show(String desc, Integer n1, Integer n2) {
    System.out.println(desc + ":");
    System.out.printf(
      "%d==%d %b %b\n\n", n1, n2, n1 == n2, n1.equals(n2));
  }
  @SuppressWarnings("deprecation")
  public static void test(int value) {
    // 方式1:自动转换为Integer,这其实是通过对Integer.valueOf()的自动调用来完成的
    Integer i1 = value;                             // [1]
    Integer i2 = value;
    show("Automatic", i1, i2);

    // 方式2:使用标准的对象创建语法new。从 Java 9 开始被弃用,因为它的效率远远低于Integer.valueOf()。
    Integer r1 = new Integer(value);                // [2]
    Integer r2 = new Integer(value);
    show("new Integer()", r1, r2);

    // 方式3:从Java 9开始,Integer.value()优于new Integer()
    Integer v1 = Integer.valueOf(value);            // [3]
    Integer v2 = Integer.valueOf(value);
    show("Integer.valueOf()", v1, v2);

    // 方式4:基本类型int也可以当作整数值对象使用。基本数据类型不能使用equals()方法
    int x = value;                                  // [4]
    int y = value;
    // x.equals(y); // Doesn't compile
    System.out.println("Primitive int:");
    System.out.printf("%d==%d %b%n", x, y, x == y);
  }
  public static void main(String[] args) {
    /**
     * 操作符 == 和 != 比较的是对象的引用
     *  对于参数值127来说,比较操作符产生了预期的结果。
     *  对于值范围在-128~127的Integer类型来说,Integer.valueOf()会返回相同的对象。范围之外则不会。
     */
    test(127);
    System.out.println("---------------------------");
    test(128);
  }
}

结果:
Automatic:
127==127 true true

new Integer():
127==127 false true

Integer.valueOf():
127==127 true true

Primitive int:
127==127 true
---------------------------
Automatic:
128==128 false true

new Integer():
128==128 false true

Integer.valueOf():
128==128 false true

Primitive int:
128==128 true

出于效率原因,Integer会通过享元模式来缓存范围在-128~127内的对象,因此多次调用Integer.valueOf(127)生成的其实是同一个对象。而在此范围之外的值则不会这样。比如每次调用Integer.valueOf(128)返回的都是不同的对象。因此需要注意,在进行==和!=比较时,范围不同的值生成对象的方式不一样,这回影响到比较的行为,从而产生不同的结果。另外,通过new Integer()生成的对象都是新创建的,无论其值处于什么范围。

4.1.2、比较Double / Float

在处理浮点数的时候,你会遇到不同的相等比较问题,这不是Java的问题,而是因为浮点数的本质:

public class DoubleEquivalence {
  static void show(String desc, Double n1, Double n2) {
    System.out.println(desc + ":");
    System.out.printf(
      "%e==%e %b %b%n", n1, n2, n1 == n2, n1.equals(n2));
  }
  @SuppressWarnings("deprecation")
  public static void test(double x1, double x2) {
    // x1.equals(x2) // Won't compile
    System.out.printf("%e==%e %b%n", x1, x2, x1 == x2);
    Double d1 = x1;
    Double d2 = x2;
    show("Automatic", d1, d2);
    Double r1 = new Double(x1);
    Double r2 = new Double(x2);
    show("new Double()", r1, r2);
    Double v1 = Double.valueOf(x1);
    Double v2 = Double.valueOf(x2);
    show("Double.valueOf()", v1, v2);
  }
  public static void main(String[] args) {
    /**
     * 理论上浮点数的比较应该是很严格的——两个数值之间即使只有小数部分有极小的不同,它们仍然应该不相等。
     */
    test(0, Double.MIN_VALUE);
    System.out.println("------------------------");
    /**
     * 下面的运行结果却是相等的。这是因为当一个非常大的数值减去一个相对较小的数值时,非常大的数值并不会发生显著变化。这叫做舍入误差。
     * 这种误差之所以发生,是因为机器不能存储足够的信息来表示一个大数值的微小变化。
     */
    test(Double.MAX_VALUE,
      Double.MAX_VALUE - Double.MIN_VALUE * 1_000_000);
  }
}

结果:
0.000000e+00==4.900000e-324 false
Automatic:
0.000000e+00==4.900000e-324 false false
new Double():
0.000000e+00==4.900000e-324 false false
Double.valueOf():
0.000000e+00==4.900000e-324 false false
------------------------
1.797693e+308==1.797693e+308 true
Automatic:
1.797693e+308==1.797693e+308 false true
new Double():
1.797693e+308==1.797693e+308 false true
Double.valueOf():
1.797693e+308==1.797693e+308 false true

5、逻辑运算符

即 &(逻辑与) 、|(逻辑或) 、^(逻辑异或) 、!(逻辑非) 。

短路逻辑运算符:&&(短路与,作用与 & 相同,但是有短路效果,即在判断执行过程中发现条件不满足时不执行后面的判断语句而直接输出判断结果)、||(短路或,作用与 | 相同,但是有短路效果)。

6、字面量

6.1、字面量的用法

一般来说,如果程序里使用了一个字面量(literal value),则编译器能准确地知道它是什么类型的。不过当类型模棱两可的时候,你就必须使用与该字面量相关的一些字符,以此添加额外信息来引导编译器。

public class Literals {
  public static void main(String[] args) {
    /**
     *  0x / 0X 前缀表示十六进制数
     */
    int i1 = 0x2f; // Hexadecimal (lowercase)
    System.out.println(
      "i1: " + Integer.toBinaryString(i1));
    int i2 = 0X2F; // Hexadecimal (uppercase)
    System.out.println(
      "i2: " + Integer.toBinaryString(i2));
    /**
     * 0 前缀表示八进制数
     */
    int i3 = 0177; // Octal (leading zero)
    System.out.println(
      "i3: " + Integer.toBinaryString(i3));
    char c = 0xffff; // max char hex value
    System.out.println(
      "c: " + Integer.toBinaryString(c));
    byte b = 0x7f; // max byte hex value 0111111;
    System.out.println(
      "b: " + Integer.toBinaryString(b));
    short s = 0x7fff; // max short hex value
    System.out.println(
      "s: " + Integer.toBinaryString(s));
    /**
     * 字面量的后缀字符标识了它的类型。如L/l表示long类型;F/f表示float类型;D/d表示Double类型
     */
    long n1 = 200L; // long suffix
    long n2 = 200l; // long suffix (can be confusing)
    long n3 = 200;
    /**
     * 0b / 0B 表示二进制数
     */
    // Java 7 Binary Literals:
    byte blb = (byte)0b00110101;
    // 在使用整数类型的值时,二进制形式的表示会非常方便。通过使用Integer和Long类的静态方法toBinaryString()可以很容易实现。
    System.out.println(
      "blb: " + Integer.toBinaryString(blb));
    short bls = (short)0B0010111110101111;
    System.out.println(
      "bls: " + Integer.toBinaryString(bls));
    int bli = 0b00101111101011111010111110101111;
    System.out.println(
      "bli: " + Integer.toBinaryString(bli));
    long bll = 0b00101111101011111010111110101111;
    System.out.println(
      "bll: " + Long.toBinaryString(bll));
    float f1 = 1;
    float f2 = 1F; // float suffix
    float f3 = 1f; // float suffix
    double d1 = 1d; // double suffix
    double d2 = 1D; // double suffix
    // (Hex and Octal also work with long)
  }
}

结果:
i1: 101111
i2: 101111
i3: 1111111
c: 1111111111111111
b: 1111111
s: 111111111111111
blb: 110101
bls: 10111110101111
bli: 101111101011111010111110101111
bll: 101111101011111010111110101111

6.2、字面量里的下划线

Java 7 中有一个十分有用的新增功能:可以在数字字面量里使用下划线,这样更易于阅读。

public class Underscores {
  public static void main(String[] args) {
    double d = 341_435_936.445_667;
    System.out.println(d);
    int bin = 0b0010_1111_1010_1111_1010_1111_1010_1111;
    System.out.println(Integer.toBinaryString(bin));
    /**
     * %x 表示输出十六进制数
     */
    System.out.printf("%x%n", bin);               // [1]
    long hex = 0x7f_e9_b7_aa;
    System.out.printf("%x%n", hex);
  }
}

结果:
3.41435936445667E8
101111101011111010111110101111
2fafafaf
7fe9b7aa

这里有几条合理的规则:

  1. 只能使用单个下划线,不能连续使用多个;
  2. 数字的开头或结尾不能有下划线;
  3. 像F、D、L这样的后缀周围不能有下划线;
  4. 在二进制或十六进制标识符b和x的周围不能有下划线。

6.3、科学计数法

public class Exponents {
  public static void main(String[] args) {
    // e大小写都可以,含义相同
    float expFloat = 1.39e-43f;
    expFloat = 1.39E-43f;
    System.out.println(expFloat);
    double expDouble = 47e47d; // d是可选的
    double expDouble2 = 47e47; // 默认就是double类型的
    System.out.println(expDouble);
  }
}

结果:
1.39E-43
4.7E48

7、按位操作符

按位操作符用来操作整数基本数据类型中的单个二进制位(bit)。按位操作符会对两个参数中对应的二进制位进行布尔代数运算,并生成一个结果。

- 与(&)操作
1 & 1 = 1
1 & 0 = 0
0 & 0 = 0
    
- 或(|)操作
1 | 1 = 1
1 | 0 = 1
0 | 0 = 0
    
- 异或(^)操作(异1同0)
1 ^ 1 = 0
1 ^ 0 = 1
0 ^ 0 = 0
    
- 非(~)操作(取反)
~1 = 0
~0 = 1

按位操作符可与等号(=)联合使用,来合并运算和赋值操作:&=、|=、^=都是合法的(~是一元操作符,所以不能与等号联合使用)。

布尔类型作为一种单位值有些独特。你可以对它执行按位“与”、按位“或”和按位“异或”运算,但不能执行按位“非”(大概是为了避免与逻辑操作符!混淆)。对于布尔值,按位操作符和逻辑操作符具有相同的效果(如 & 和 &&、| 和 ||),但它们不会“短路”。此外,针对布尔值的按位运算还比逻辑操作符多了一个“异或”运算。移位表达式中不能使用布尔类型。

8、移位操作符

移位操作符操纵二进制位,它们只能用来处理基本数据类型里的整数类型。

  • 左移位操作符(<<)会将操作符左侧的操作数向左移动,移动的位数在操作符右侧指定,低位补0。如:num << 8 代表num左移8位,低位补0。
  • “有符号”的右移位操作符(>>)则将操作符左侧的操作数向右移动,移动的位数在操作符右侧指定。“有符号”的右移操作符使用了“符号扩展”:如果符号为正,则在高位插入0;否则在高位插入1。Java还新增加了一种“无符号”的右移位操作符(>>>),它使用“零扩展”:无论符号为正还是为负,都在高位插入0。这一操作符是C和C++中所没有的。

如果对char、byte或者short类型的数值进行移位运算,在移位操作前它们会被转换为int类型,并且结果也是int类型。右端的可移位数中只会用到低5位(2^5=32),这样可以防止移位超过int型值所具有的位数。如果处理的是long类型,最后得到的结果也是long类型,此时只会用到右端指定移位数值的低6位(2^6=64),这样移位操作就不会超出long类型的最大位数。

移位操作符可以与等号组合使用(<<=、>>=或>>>=),操作符左边的值会移动右边指定的位数,然后再将得到的结果赋值给左边的变量。但“无符号”右移操作符结合赋值操作符可能会出现一个问题:如果对byte或short值进行移位运算,得到的可能不是正确的结果。它们会先被提升为int类型,进行右移操作,然后在被赋值回给原来的变量时被截断,这时得到结果是-1。下面是一个示例:

public class URShift {
  public static void main(String[] args) {
    int i = -1;
    System.out.println(Integer.toBinaryString(i));
    i >>>= 10;
    System.out.println(Integer.toBinaryString(i));
    long l = -1;
    System.out.println(Long.toBinaryString(l));
    l >>>= 10;
    System.out.println(Long.toBinaryString(l));
    short s = -1;
    System.out.println(Integer.toBinaryString(s));
    s >>>= 10;
    System.out.println(Integer.toBinaryString(s));
    byte b = -1;
    System.out.println(Integer.toBinaryString(b));
    b >>>= 10;
    System.out.println(Integer.toBinaryString(b));
    b = -1;
    System.out.println(Integer.toBinaryString(b));
    System.out.println(Integer.toBinaryString(b>>>10));
  }
}

结果:
11111111111111111111111111111111
1111111111111111111111
1111111111111111111111111111111111111111111111111111111111111111
111111111111111111111111111111111111111111111111111111
11111111111111111111111111111111
11111111111111111111111111111111
11111111111111111111111111111111
11111111111111111111111111111111
11111111111111111111111111111111
1111111111111111111111

在最后一个移位运算中,结果没有赋回给b,而是直接打印了出来,所以是正确的。下面这个示例演示了所有涉及位操作的操作符:

public class BitManipulation {
  public static void main(String[] args) {
    Random rand = new Random(47);
    int i = rand.nextInt();
    int j = rand.nextInt();
    printBinaryInt("-1", -1);
    printBinaryInt("+1", +1);
    int maxpos = 2147483647;
    printBinaryInt("maxpos", maxpos);
    int maxneg = -2147483648;
    printBinaryInt("maxneg", maxneg);
    printBinaryInt("i", i);
    printBinaryInt("~i", ~i);
    printBinaryInt("-i", -i);
    printBinaryInt("j", j);
    printBinaryInt("i & j", i & j);
    printBinaryInt("i | j", i | j);
    printBinaryInt("i ^ j", i ^ j);
    printBinaryInt("i << 5", i << 5);
    printBinaryInt("i >> 5", i >> 5);
    printBinaryInt("(~i) >> 5", (~i) >> 5);
    printBinaryInt("i >>> 5", i >>> 5);
    printBinaryInt("(~i) >>> 5", (~i) >>> 5);

    long l = rand.nextLong();
    long m = rand.nextLong();
    printBinaryLong("-1L", -1L);
    printBinaryLong("+1L", +1L);
    long ll = 9223372036854775807L;
    printBinaryLong("maxpos", ll);
    long lln = -9223372036854775808L;
    printBinaryLong("maxneg", lln);
    printBinaryLong("l", l);
    printBinaryLong("~l", ~l);
    printBinaryLong("-l", -l);
    printBinaryLong("m", m);
    printBinaryLong("l & m", l & m);
    printBinaryLong("l | m", l | m);
    printBinaryLong("l ^ m", l ^ m);
    printBinaryLong("l << 5", l << 5);
    printBinaryLong("l >> 5", l >> 5);
    printBinaryLong("(~l) >> 5", (~l) >> 5);
    printBinaryLong("l >>> 5", l >>> 5);
    printBinaryLong("(~l) >>> 5", (~l) >>> 5);
  }

  /**
   * 输出int类型参数的二进制格式
   * @param s
   * @param i
   */
  static void printBinaryInt(String s, int i) {
    System.out.println(
      s + ", int: " + i + ", binary:\n   " +
      Integer.toBinaryString(i));
  }

  /**
   * 输出long类型参数的二进制格式
   * @param s
   * @param l
   */
  static void printBinaryLong(String s, long l) {
    System.out.println(
      s + ", long: " + l + ", binary:\n    " +
      Long.toBinaryString(l));
  }
}

结果:
-1, int: -1, binary:
   11111111111111111111111111111111
+1, int: 1, binary:
   1
maxpos, int: 2147483647, binary:
   1111111111111111111111111111111
maxneg, int: -2147483648, binary:
   10000000000000000000000000000000
i, int: -1172028779, binary:
   10111010001001000100001010010101
~i, int: 1172028778, binary:
   1000101110110111011110101101010
-i, int: 1172028779, binary:
   1000101110110111011110101101011
j, int: 1717241110, binary:
   1100110010110110000010100010110
i & j, int: 570425364, binary:
   100010000000000000000000010100
i | j, int: -25213033, binary:
   11111110011111110100011110010111
i ^ j, int: -595638397, binary:
   11011100011111110100011110000011
i << 5, int: 1149784736, binary:
   1000100100010000101001010100000
i >> 5, int: -36625900, binary:
   11111101110100010010001000010100
(~i) >> 5, int: 36625899, binary:
   10001011101101110111101011
i >>> 5, int: 97591828, binary:
   101110100010010001000010100
(~i) >>> 5, int: 36625899, binary:
   10001011101101110111101011
-1L, long: -1, binary:
    1111111111111111111111111111111111111111111111111111111111111111
+1L, long: 1, binary:
    1
maxpos, long: 9223372036854775807, binary:
    111111111111111111111111111111111111111111111111111111111111111
maxneg, long: -9223372036854775808, binary:
    1000000000000000000000000000000000000000000000000000000000000000
l, long: -8652529054300476342, binary:
    1000011111101100000010101010101100001101101011000110110001001010
~l, long: 8652529054300476341, binary:
    111100000010011111101010101010011110010010100111001001110110101
-l, long: 8652529054300476342, binary:
    111100000010011111101010101010011110010010100111001001110110110
m, long: 2955289354441303771, binary:
    10100100000011010011000000001010010011111101111010011011011011
l & m, long: 72066398748419146, binary:
    100000000000010000000001000000001101001000010010001001010
l | m, long: -5769306098607591717, binary:
    1010111111101111010011101010101110011111111111111110111011011011
l ^ m, long: -5841372497356010863, binary:
    1010111011101111010001101010100110011110010110111100101010010001
l << 5, long: -179768631971968704, binary:
    1111110110000001010101010110000110110101100011011000100101000000
l >> 5, long: -270391532946889886, binary:
    1111110000111111011000000101010101011000011011010110001101100010
(~l) >> 5, long: 270391532946889885, binary:
    1111000000100111111010101010100111100100101001110010011101
l >>> 5, long: 306069219356533602, binary:
    10000111111011000000101010101011000011011010110001101100010
(~l) >>> 5, long: 270391532946889885, binary:
    1111000000100111111010101010100111100100101001110010011101

 数字的二进制表示形式被称为“有符号的二进制补码”。

9、三元运算符

三元操作符也叫条件操作符,最终会生成一个结果值。

即 关系表达式 ?表达式1:表达式2 。

如 a > b ? a+b : a-b 。如果值为true,结果为a+b;如果值为false,结果为a-b;

10、字符串操作符

如果表达式以一个字符串开头,则其后的所有操作数都必须是字符串类型的(编译器会自动把双引号里的字符序列转换成字符串):

public class StringOperators {
  public static void main(String[] args) {
    int x = 0, y = 1, z = 2;
    String s = " x, y, z ";
    System.out.println(x + y + z + s);
    System.out.println(s + x + y + z);
    // Converts x to a String:
    System.out.println(x + " " + s);
    s += "(summed) = "; // Concatenation operator
    System.out.println(s + (x + y + z));
    // Shorthand for Integer.toString():
    System.out.println("" + x);
  }
}

结果:
3 x, y, z 
 x, y, z 012
0  x, y, z 
 x, y, z (summed) = 3
0

11、类型转换操作符

在适当的时候,Java会自动将一种类型的数据更改为另一种类型的数据。类型转换机制使得这类转换清晰明确,还可以在无法自动转换的时候进行强制类型转换。要对某个值执行类型转换,可以将希望得到的数据类型放在括号内,置于该值的左边。如下所示:

public class Casting {
  public static void main(String[] args) {
    int i = 200;
    long lng = (long)i;
    lng = i; // 宽化,因此不需要强制类型转换
    long lng2 = (long)200;
    lng2 = 200;
    // 一个窄化转型
    i = (int)lng2; // 需要强制类型转换
  }
}

11.1、截尾和舍入

public class CastingNumbers {
  public static void main(String[] args) {
    double above = 0.7, below = 0.4;
    float fabove = 0.7f, fbelow = 0.4f;
    System.out.println("(int)above: " + (int)above);
    System.out.println("(int)below: " + (int)below);
    System.out.println("(int)fabove: " + (int)fabove);
    System.out.println("(int)fbelow: " + (int)fbelow);
  }
}

结果:
(int)above: 0
(int)below: 0
(int)fabove: 0
(int)fbelow: 0

将float或double转型成为整型时,总是对该数值执行截尾。如果想要对结果进行舍入,就需要使用java.lang.Math中的round()方法:

public class RoundingNumbers {
  public static void main(String[] args) {
    double above = 0.7, below = 0.4;
    float fabove = 0.7f, fbelow = 0.4f;
    System.out.println(
      "Math.round(above): " + Math.round(above));
    System.out.println(
      "Math.round(below): " + Math.round(below));
    System.out.println(
      "Math.round(fabove): " + Math.round(fabove));
    System.out.println(
      "Math.round(fbelow): " + Math.round(fbelow));
  }
}

结果:
Math.round(above): 1
Math.round(below): 0
Math.round(fabove): 1
Math.round(fbelow): 0

12、Java没有sizeof()

 在C和C++中,sizeof()操作符会告诉你给数据项分配的字节数。这些语言使用sizeof()的原因是可移植性。不同的数据类型在不同机器上可能有不同的大小,所以在做一些与数据大小有关的运算时,程序员必须知道这些类型有多大。但是Java不需要sizeof()操作符来处理可移植性,因为所有的数据类型在所有机器中的大小都是相同的。我们不必考虑这种程度的可移植性——它已经被设计在语言里了。

 

posted on 2022-09-18 15:48  啊噢1231  阅读(28)  评论(0编辑  收藏  举报

导航

回到顶部