数据类型
基本数据类型
char的默认值只能是单引号\u0000
表示NUL
。
在代码中没有任何上下文的0和0.0分别默认为int和double类型。
refvar:引用变量本身。均占用4B
空间
refobj:引用指向的实际对象。无论是多么小的对象,最小占用的存储空间是12B
(用于存储基本信息,称为对象头);由于存储空间分配需要是8B
的整数倍,所以初始分配的空间至少是16B
。
注意,当计算一个对象的大小时,实例占用的空间并不计算在本对象内,依然只计算引用变量大小4字节。
//以下的例子只计算引用变量大小4个字节
RefObject o1 = new RefObject();
对象由对象头、实例数据和填充数据组成:
- 对象头:对象标记+类元信息
- 对象标记:存储对象本身运行时的数据。哈希码、GC标记、锁信息和线程关联信息等。
- 类元信息:对象指向它的类元数据(Klass)的首地址,占用四字节。
- 实例数据:本类对象的实例成员变量和所有可见的父类成员变量。
- 填充数据:分配单位为8个字节。
包装类型
除了Float和Double外,其余都有缓存,会复用已有对象。
缓存机制
Byte
,Short
,Integer
,Long
这 4 种包装类默认创建了数值 [-128,127] 的相应类型的缓存数据,Character
创建了数值在 [0,127] 范围的缓存数据,Boolean
直接返回 True
or False
。
两种浮点数类型的包装类 Float
,Double
并没有实现缓存机制。
所有整型包装类对象之间值的比较,全部使用 equals 方法比较。
Integer是唯一可以修改缓存范围的包装类:在VM options
中加入参数-XX:AutoBoxCacheMax=777
。
装箱和拆箱
- 装箱:将基本类型用它们对应的引用类型包装起来;
- 拆箱:将包装类型转换为基本数据类型;
从字节码中,我们发现装箱其实就是调用了 包装类的valueOf()
方法,拆箱其实就是调用了 xxxValue()
方法。
因此,
Integer i = 10
等价于Integer i = Integer.valueOf(10)
int n = i
等价于int n = i.intValue()
;
注意:**如果频繁拆装箱的话,也会严重影响系统的性能。我们应该尽量避免不必要的拆装箱操作。
如何解决浮点数运算的精度丢失问题?
BigDecimal
可以实现对浮点数的运算,不会造成精度丢失。通常情况下,大部分需要浮点数精确运算结果的业务场景(比如涉及到钱的场景)都是通过 BigDecimal
来做的。
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("0.9");
BigDecimal c = new BigDecimal("0.8");
BigDecimal x = a.subtract(b);
BigDecimal y = b.subtract(c);
System.out.println(x); /* 0.1 */
System.out.println(y); /* 0.1 */
System.out.println(Objects.equals(x, y)); /* true */
超过 long 整型的数据应该如何表示?
基本数值类型都有一个表达范围,如果超过这个范围就会有数值溢出的风险。
在 Java 中,64 位 long 整型是最大的整数类型。
BigInteger
内部使用 int[]
数组来存储任意大小的整形数据。
相对于常规整数类型的运算来说,BigInteger
运算的效率会相对较低。
public void testCompare() {
BigInteger bigNum1 = new BigInteger("52");
BigInteger bigNum2 = new BigInteger("27");
BigInteger c=new BigInteger("5");
BigInteger d=new BigInteger("3");
//1.compareTo():返回一个int型数据(1 大于; 0 等于; -1 小于)
int num = bigNum1.compareTo(bigNum2); //1
//2.max():直接返回大的那个数,类型为BigInteger
//原理:return (compareTo(val) > 0 ? this : val);
BigInteger compareMax = bigNum1.max(bigNum2); //52
System.out.println("d-c="+d.subtract(c));
}
选择基本数据类型还是包装类型
- 所有
POJO
类属性必须使用包装类型 - RPC方法的返回值和参数必须使用包装数据类型
- 所有的局部变量推荐使用基本数据类型
字符串
String对象赋值操作后,会在常量池中进行缓存,如果下次申请创建对象时缓存中有,那么会直接返回相应引用。
StringBuffer:线程安全
StringBuilder:线程不安全,效率更高
String tempStr = "test";
for (int i = 0; i < 5; i++) {
tempStr = tempStr + " hello";
}
通过+号拼接字符串效率不高,如上,内部实现逻辑会转换成每次循环new一个StringBuilder,然后调用append(),最后再toString()
返回String对象。造成了内存资源的浪费并且性能差。