数据与运算
本节介绍 Java 中的数据与运算模块,包括基本数据类型、包装类型以及运算的部分。
数据类型
Java 中有八大基本数据类型
| 数据类型 | 关键字 | 内存占用 |
|---|---|---|
| 字节型 | byte | 1个字节 |
| 短整型 | short | 2个字节 |
| 整型 | int | 4个字节 |
| 长整型 | long | 8个字节 |
| 单精度浮点数 | float | 4个字节 |
| 双精度浮点数 | double | 8个字节 |
| 字符型 | char | 2个字节 |
| 布尔类型 | boolean | 1个字节 |
其中字面量的整数默认是 int 类型,字面量的浮点数默认是 double 类型。运算的时候需要极其注意,避免因为隐式类型转换带来其他问题。
其中八大基本数据类型它们还有对应的包装类型
| 基本类型 | 包装类(java.lang) |
|---|---|
| byte | Byte |
| short | Short |
| int | Integer |
| long | Long |
| float | Float |
| double | Double |
| char | Character |
| boolean | Boolean |
此外还有一个很重要的引用数据类型:String,关于它放在这一篇里面说:揭开String的神秘面纱
其中 switch 支持的数据类型有:char 、byte 、short 、int 、Character 、Byte 、Short 、Integer 、String 、enum
自动装箱与自动拆箱
包装类型和基本数据类型之间的互相赋值由自动装箱、自动拆箱完成。
用一个最简单的例子阐述自动装箱与自动拆箱
Integer a = 10; // 自动装箱 基本数据类型->包装类型
int b = a; // 自动拆箱 包装类型->基本数据类型包装类型与基本类型的区别
从类型上来看:包装类型可用于泛型,而基本类型不可以。因为泛型在编译时会进行类型擦除,最后只保留原始类型,而原始类型只能是 Object 类及其子类。包装类型的都是Object的子类,而基本类型没有所谓的原始类型。
从值来看:包装类型可以为 null,基本类型不可以。包装类型可以应用于 POJO 的属性类型,基本一般来说不太推荐。因为数据库的查询结果可能是 null,如果使用基本类型的话,就可能会抛出
NullPointerException的异常。从比较环节来看:基本类型使用 == 进行比较;包装类型需要使用 equals 方法进行比较,用 == 比较的是地址值。此外,包装类型和基本类型进行 == 比较的时候会自动拆箱。
总体上来说:基本类型比包装类型更高效。基本类型在栈中直接存储的具体数值,而包装类型在栈中存储的是堆中的引用。很显然,相比较于基本类型而言,包装类型需要占用更多的内存空间。
包装类的缓存机制
这是一个非常 nice 的设计,有点字符串常量池的意思~
我们不妨从一个经典的例子引入这个话题
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 方法。
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来完成运算
常用方法
// 定义数据
public BigDecimal(String val);
// 比较两个数值之间的大小关系
public int compareTo(BigDecimal value);可以看到构造方法我们推荐使用字符串来构造一个 BigDecimal 数据,使用 Double 类型来构造会造成精度丢失
运算
除法运算、模运算
关于这两个运算不提太多,只提一下一些比较容易掉进坑里的场景。
浮点数参与除法运算,无论除法是浮点数或被除数是浮点数或者两者皆是浮点数,运算的结果类型都是浮点数。
除不尽的情况会得到一个不太精准的结果。
System.out.println(10.0 / 3); // 3.3333333333333335模运算实际上就是取除法运算中的余数
浮点数参与模运算,无论除法是否能除尽,运算的结果类型都是浮点数。
System.out.println(10.0 % 3); // 1.0
System.out.println(10.0 % 5); // 0.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
System.out.println(-9.0 % -5); // -9.0/(-5)=1...(-4.0)位运算
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
}移位运算
同样地,使用一个小例子来阐述移位运算的功能
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
}
}另外需要注意几个小细节:
对于
byte和short类型数值进行移位运算时会先升级为int类型再进行操作。当进行乘法/除法运算时,如果乘数/除数是2的n次幂,可以使用移位操作完成,效率更高。