一:八个基本类型:
boolean/1byte/8char/16short/16int/32float/32long/64double/64
基本类型都有对应的包装类型,基本类型与其对应的包装类型之间的赋值使用自动装箱与拆箱完成。
例如:
Integer x = 1; // 装箱 int y = x; // 拆箱
二:缓存池
使用享元模式(Flyweight Pattern)即对象缓存池来减少重复对象的创建。
例如:
new Integer(123) 与 Integer.valueOf(123) 的区别在于:
new Integer(123) 每次都会新建一个对象,Integer.valueOf(123) 会使用缓存池中的对象,多次调用会取得同一个对象的引用。
valueOf() 方法的实现比较简单,就是先判断值是否在缓存池中,如果在的话就直接返回缓存池的内容,
编译器会在缓冲池范围内的基本类型自动装箱过程调用 valueOf() 方法,因此多个 Integer 实例使用自动装箱来创建并且值相同,那么就会引用相同的对象。
Integer m = 110; Integer n = 110; System.out.println(m == n); // true
如果在缓冲池之外:
Integer m = 333; Integer n = 333; System.out.println(m == n); // false
valueOf 源码(在 Java 8 中,Integer 缓存池的大小默认为 -128~127):
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
基本类型对应的缓冲池如下:
boolean values true and falseall byte valuesshort values between -128 and 127int values between -128 and 127char in the range u0000 to u007F
在使用这些基本类型对应的包装类型时,就可以直接使用缓冲池中的对象。
三:String、StringBuffer 、StringBuilder
String 被声明为 final,因此它不可被继承。内部使用 char 数组存储数据,该数组被声明为 final,这意味着 value 数组初始化之后就不能再引用其它数组。并且 String 内部没有改变 value 数组的方法,因此可以保证 String 不可变。
public final class String
implements java.io.Serializable, Comparable, CharSequence {
private final char value[];
String 不可变优点:
- 可以缓存 hash 值
因为 String 的 hash 值经常被使用,例如做 HashMap 的 key。不可变的特性可以使得 hash 值也不可变,因此只需要进行一次hash寻址String Pool 的需要
如果一个 String 对象已经被创建过了,在堆中的字符串常量池中直接获取引用。所以只有 String 是不可变的,才可能使用 String Pool线程安全
经常作为参数或者在多个线程中,String 不可变性可以保证参数不可变,具备线程安全特点
String.intern()
使用 String.intern() 可以保证相同内容的字符串变量引用同一的内存对象。
String s1 = new String("aaa");
String s2 = new String("aaa");
System.out.println(s1 == s2); // false
String s3 = s1.intern();
System.out.println(s1.intern() == s3); // true
s1 和 s2 采用 new String() 的方式新建了两个不同对象,而 s3 是通过 s1.intern() 方法取得一个对象引用。intern() 首先把 s1 引用的对象放到 String Pool(字符串常量池)中,然后返回这个对象引用。因此 s3 和 s1 引用的是同一个字符串常量池的对象。
StringBuffer 、StringBuilder和String的区别:
1、可变性:
String 不可变
StringBuffer 和 StringBuilder 可变
2、线程安全性:
String 不可变,因此是线程安全的
StringBuilder 不是线程安全的
StringBuffer 是线程安全的,内部使用 synchronized 进行同步
四:float 与 double
// float f = 1.1; float f = 1.1f;
1.1 字面量属于 double 类型,不能直接将 1.1 直接赋值给 float 变量,因为这是向下转型。Java 不能隐式执行向下转型,因为这会使得精度降低。
很多人都知道,在进行金额表示、金额计算等场景,不能使用 double、float 等类型,而是要使用 java.math 包中提供的一种可以用来进行精确运算的类型、对精度支持的更好的 BigDecimal。
**解释一下为什么说 float 与 double不精确?** 在计算机中只认识二进制的,即 0 和 1,所有数字,包括整数和小数,想要在计算机中存储和展示,都需要转成 二进制,十进制整数转成二进制很简单,通常采用"除2取余,逆序排列"即可,如 10 的二进制为 1010。 但是,小数的二进制如何表示呢? 十进制小数转成二进制,一般采用"乘 2 取整,顺序排列"方法,如 0.625 转成二进制的表示为 0.101 但是,并不是所有小数都能转成二进制,如 0.1 就不能直接用二进制表示,他的二进制是 0.000110011001100… ,这是一个无限循环小数。所以,计算机是没办法用二进制精确的表示 0.1 的。也就是说,在计算机中,很多小数 没办法精确的使用二进制表示出来。那么,这个问题总要解决吧。 那么,人们想出了一种采用一定的精度,使用"近视值"表示一个小数的办法。这就是 IEEE 754(IEEE 二进制浮 点数算术标准)规范的主要思想。 IEEE 754 规定了多种表示浮点数值的方式,其中最常用的就是 32 位单精度浮点数和 64 位双精度浮点数。 在 Java 中,使用 float 和 double 分别用来表示单精度浮点数和双精度浮点数。 所谓精度不同,可以简单的理解为保留有效位数不同。采用保留有效位数的方式近似的表示小数。
BigDecimal 如何精确计数?
public class BigDecimal extends Number implements Comparable{ private final BigInteger intVal; private final int scale; private final transient long intCompact; }
通过BigDecimal 的源码,其实可以发现,实际上一个 BigDecimal 是通过一个"无标度值"和一个"标度"来表示一个数的。
在 BigDecimal 中,标度是通过 scale 字段来表示的。
如果 scale 为零或正值,则该值表示这个数字小数点右侧的位数。如果 scale 为负数,则该数字的真实值需要乘以 10 的该负数的绝对值的幂。例如,scale为-3,则这个数需要乘 1000,即在末尾有 3 个 0。
例如 123.123,那么如果使用 BigDecimal 表示,那么他的无标度值为 123123,他的标度为 3。
而二进制无法表示的 0.1,使用 BigDecimal 就可以表示了,即通过无标度值1和标度1来表示。
禁止使用BigDecimal(double) ,建议使用 BigDecimal(String)创建
通过上面分析我们知道,double 表示的小数是不精确的,如 0.1 这个数字,double 只能表示他的近似值。
所以,当我们使用 new BigDecimal(0.1)创建一个 BigDecimal 的时候,其实创建出来的值并不是正好等于 0.1 的。而是一个近似值。这是因为 doule 自身表示的只是一个近似值。
想要避免这个问题,可以通过 new BigDecimal(“0.1”)创建一个 BigDecimal 的时候,其实创建出来的值正好就是等于 0.1 的。其表现形式是一个无标度数值 1,和一个标度 1 的组合。



