Skip to content

数据与运算

本节介绍 Java 中的数据与运算模块,包括基本数据类型、包装类型以及运算的部分。

数据类型

Java 中有八大基本数据类型

数据类型关键字内存占用
字节型byte1个字节
短整型short2个字节
整型int4个字节
长整型long8个字节
单精度浮点数float4个字节
双精度浮点数double8个字节
字符型char2个字节
布尔类型boolean1个字节

其中字面量的整数默认是 int 类型,字面量的浮点数默认是 double 类型。运算的时候需要极其注意,避免因为隐式类型转换带来其他问题。

其中八大基本数据类型它们还有对应的包装类型

基本类型包装类(java.lang)
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean

此外还有一个很重要的引用数据类型:String,关于它放在这一篇里面说:揭开String的神秘面纱

其中 switch 支持的数据类型有:charbyteshortintCharacterByteShortIntegerStringenum

自动装箱与自动拆箱

包装类型和基本数据类型之间的互相赋值由自动装箱、自动拆箱完成。

用一个最简单的例子阐述自动装箱与自动拆箱

java
Integer a = 10;	// 自动装箱 基本数据类型->包装类型
int b = a;		 // 自动拆箱 包装类型->基本数据类型

包装类型与基本类型的区别

  1. 从类型上来看:包装类型可用于泛型,而基本类型不可以。因为泛型在编译时会进行类型擦除,最后只保留原始类型,而原始类型只能是 Object 类及其子类。包装类型的都是Object的子类,而基本类型没有所谓的原始类型。

  2. 从值来看:包装类型可以为 null,基本类型不可以。包装类型可以应用于 POJO 的属性类型,基本一般来说不太推荐。因为数据库的查询结果可能是 null,如果使用基本类型的话,就可能会抛出 NullPointerException 的异常。

  3. 从比较环节来看:基本类型使用 == 进行比较;包装类型需要使用 equals 方法进行比较,用 == 比较的是地址值。此外,包装类型和基本类型进行 == 比较的时候会自动拆箱。

  4. 总体上来说:基本类型比包装类型更高效。基本类型在栈中直接存储的具体数值,而包装类型在栈中存储的是堆中的引用。很显然,相比较于基本类型而言,包装类型需要占用更多的内存空间

包装类的缓存机制

这是一个非常 nice 的设计,有点字符串常量池的意思~

我们不妨从一个经典的例子引入这个话题

java
Integer x = new Integer(123);
Integer y = new Integer(123);
System.out.println(x == y);    	// false
Integer z = Integer.valueOf(123);
Integer k = Integer.valueOf(123);
System.out.println(z == k);   	 // true

// 简单分析
// new Integer(123) 每次都会在堆中创建一个数值为123的对象
// Integer.valueOf(123) 使用缓存池中的对象,多次调用会取到同一个对象的引用

valueOf 这个方法每次执行的时候都会检查 IntegerCache.cache 这个数组中查询是否有这个对象,有的话就使用;没有的话才创建新的对象。如果直接使用 Integer x = 10; 的话会触发自动装箱,自动装箱最终其实还是调用 valueOf 方法。

java
public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

Integer 默认的缓存池数据范围是-128~127,可以通过调整虚拟机参数 -XX:AutoBoxCacheMax=<size> 来设置缓存的最大值

其他包装类也有类似的缓存池

  • Boolean:TRUE、FALSE
  • Byte:-128~127
  • Short:-128~127
  • Long:-128~127
  • Character:\u0000~\u007F

可能是考虑到开发人员使用包装类的习惯、频率,Java针对数据自动装箱时设计了一个对象缓存池。对象缓存池简单来说就是一个数组,当符合一定规则的时候,不去创建对象,而是从数组中拿对象出来使用,提高对象的复用率和性能。

BigDecimal

由于浮点型数据无论基本数据类型还是包装类是都可能会是一个近似值,不是精确值。所以在一些数据敏感的计算场景下(如:金额计算),就需要使用保证数据精确的 BigDecimal 来完成运算

常用方法

java
// 定义数据
public BigDecimal(String val);
// 比较两个数值之间的大小关系
public int compareTo(BigDecimal value);

可以看到构造方法我们推荐使用字符串来构造一个 BigDecimal 数据,使用 Double 类型来构造会造成精度丢失

运算

除法运算、模运算

关于这两个运算不提太多,只提一下一些比较容易掉进坑里的场景。

浮点数参与除法运算,无论除法是浮点数或被除数是浮点数或者两者皆是浮点数,运算的结果类型都是浮点数。

除不尽的情况会得到一个不太精准的结果。

java
System.out.println(10.0 / 3);		// 3.3333333333333335

模运算实际上就是取除法运算中的余数

浮点数参与模运算,无论除法是否能除尽,运算的结果类型都是浮点数。

java
System.out.println(10.0 % 3);		// 1.0

System.out.println(10.0 % 5);		// 0.0

负数参与模运算,最好列一下式子,运算符合和被除数符号相同

java
System.out.println(-9.0 % 5);		// -9.0/5=-1...(-4.0)

System.out.println(9.0 % -5);		// 9.0/(-5)=-1...4.0

System.out.println(-9.0 % -5);		// -9.0/(-5)=1...(-4.0)

位运算

Java 中数据的展示用原码,运算用补码。

用一个小例子展示位运算的功能

java
public static void main(String[] args) {
    byte a = 15;
    byte b = -15;
  
    // 把a、b换成补码形式
    a = 0000 1111
    b = 1111 0001

    // &按位与运算
    System.out.println(a & b);    //0000 0001(补) = 0000 0001(原) = 1
    // |按位或运算
    System.out.println(a | b);    //1111 1111(补) = 1000 0001(原) = -1
    // ^按位异或运算
    System.out.println(a ^ b);    //1111 1110(补) = 1000 0010(原) = -2
    // ~按位取反运算
    System.out.println(~a);       //1111 0000(补) = 1001 0000(原) = -16
    System.out.println(~b);       //0000 1110(补) = 0000 1110(原) = 14
}

移位运算

同样地,使用一个小例子来阐述移位运算的功能

java
public class test{
    public static void main(String[] args) {
        //左移运算符用“<<”表示,是将运算符左边的对象,向左移动运算符右边指定的位数,并且在低位补零。
        int a = 2, b = 2, c;
        c = a<<b;
        System.out.print("a 移位的结果是"+c); 	//2<<2 = 0000 0010(原、补)<<2 = 0000 1000(原、补)=8
    }
}

public class test{
    public static void main(String[] args) {
        //无符号右移运算符无符号用“>>>”表示,是将运算符左边的对象向右移动运算符右边指定的位数,并且在高位补0,其实右移n位,就相当于除上2的n次方
        int a = 16, b=2;
        System.out.println("a 移位的结果是"+(a>>>b)); //16>>>2 = 0001 0000(原、补)>>>2 = 0000 0100(原、补)=4
    }
}

public class test{
    public static void main(String[] args) {
        //带符号右移运算符用“>>”表示,是将运算符左边的运算对象,向右移动运算符右边指定的位数。如果是正数,在高位补零,如果是负数,则在高位补1
        int a=16, c=-16, b=2, d=2;
        System.out.println("a 的移位结果:"+(a>>b));  //16>>2 = 0001 0000(原、补)>>2 = 0000 0100(原、补)=4
        System.out.println("c 的移位结果:"+(c>>d));  //-16>>2 = 1001 0000(原) 1111 0000(补)>>2 = 1111 1100(补) = 1000 0100(原)=-4
    }
}

另外需要注意几个小细节:

  • 对于 byteshort 类型数值进行移位运算时会先升级为 int 类型再进行操作。

  • 当进行乘法/除法运算时,如果乘数/除数是2的n次幂,可以使用移位操作完成,效率更高。