包装类
java语言是面向对象的语言,但是其中的八大基本数据类型不符合面向对象的特征。
因此java为了弥补这样的缺点,为这八种基本数据类型专门设计了八种符合面向对象特征的的类型,这八种具有面向对象特征的类型,统称为包装类,英文单词:wrapper class。
包装类,就是在类的内部,维护了一个基本数据类型的成员变量,以及其他方法,常量等。比如int对应的包装类Integer的部分源码如下:
public final class Integer extends Number implements Comparable<Integer> {
public static final int MIN_VALUE = 0x80000000;
public static final int MAX_VALUE = 0x7fffffff;
private final int value;
public Integer(int value) {this.value = value;}
public Integer(String s) throws NumberFormatException {this.value = parseInt(s, 10);}public static String toHexString(int i) {}
public static String toOctalString(int i) {}
public static String toBinaryString(int i) {}public static int parseInt(String s) throws NumberFormatException {}
public static Integer valueOf(String s) throws NumberFormatException {}
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
public int compareTo(Integer anotherInteger) {}
public static int max(int a, int b) {
return Math.max(a, b);
}
public static int min(int a, int b) {
return Math.min(a, b);
}
//.....
}
所有的基本数据类型和包装类型的对比,参考如下:
基本数据类型 | 包装类型 |
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
boolean | Boolean |
char | Character |
装箱和拆箱
在使用包装类型时,涉及到两个概念:
1. 装箱操作: 基本数据类型 变成其对应的包装类型对象
--第一种方式: 调用包装类的构造器
--第二种方式: 调用工具方法valueOf(....)
2. 拆箱操作: 包装类对象转成其对应的基本数据类型的值
--对象调用xxxValue(),返回对应的基本数据类型的值
装箱操作:
int age = 10;
Integer a1 = new Integer(age); // 装箱操作
Integer a2 = new Integer(11); // 装箱操作
Integer a3 = Integer.valueOf(100); // 装箱操作
// 比较a1和a3
boolean r = a1.equals(a3);
System.out.println("比较结果:"+r); //比较结果:false
Integer a4 =Integer.valueOf("1001"); // int value
System.out.println(a4); //1001拆箱操作:
int i1 = a1.intValue(); // 拆箱操作, 由对象转成值。
int i2 = a2.intValue();
int i3 = a3.intValue();
int i4 = a4.intValue();
System.out.println(i1+","+i2+","+i3+","+i4); //10,11,100,1001
自动装箱和拆箱
为了方便获取包装类对象,从jdk1.5以后,提供了一个自动装箱的操作,也提供了一个包装类到基本数据类的自动拆箱操作。
自动装箱 : 直接使用一个基本数据类型的变量或字面值给一个包装类型的引用进行赋值即可
自动的装箱 : 省略掉的是 valueOf 方法
eg:
Integer age = 10; // 实际上,底层帮助我们调用了工具方法valueOf(....)
double scores = 100;
Double d1 = scores; // 自动装箱。
自动拆箱 : 直接使用一个包装类型的引用给一个基本数据类型的变量进行赋值即可
自动的拆箱 : 省略掉的是 xxValue 方法
eg:
Byte b1 = new Byte("88");
byte r1 = b1; // 实际上,底层帮助我们调用了byteValue()方法,来拆箱
int num1 = new Integer(100); // 发生了自动拆箱操作,对象调用了intValue()方法
包装类的常量池
为什么有常量池:为了减少内存的开销,以及提高个别对象的使用率。
对于装箱操作后的包装类的对象,jvm在堆中,维护了一个常量池。
该常量池适用于调用了valueOf()方法产生的包装类对象,以及自动装箱的包装类对象。
不适用于new关键字创建的包装类对象。
Byte, Short, Integer, Long :默认创建了数值 [-128, 127] 的相应类型的缓存数据,
Character :创建了数值在 [0, 127] 范围的缓存数据,
Boolean : 直接返回 true 或 false。Float, Double :没有实现常量池技术。
Integer i1 = 100; //自动装箱了, 先去常量池中查找是否有和100这个值相同的对象,没有,因此就在常量池创建该对象,返回地址。
Integer i2 = Integer.valueOf("100"); //先去常量池中查找是否有和100这个值相同的对象,有,因此返回的是常量池中对象的地址
System.out.println(i1 == i2); //true
//验证范围
Integer i3 = 128; //128装箱后,并没有进入常量池,而是在堆中。
Integer i4 = 128; //128自动装箱后,也是在堆中。
System.out.println(i3 == i4); //两个地址不相同
//验证new出来的对象
Integer i5 = new Integer(100);
System.out.println(i1 == i5); //因为是false,所以证明了new出来的对象不在常量池中,在堆中。
//验证Character 和 Boolean
Character c1 = 97;
Character c2 = 97;
System.out.println(c1 == c2); //true, 所以在常量池中
Boolean f = true;
Boolean g = true;
System.out.println(f == g); //true , 所以在常量池中
//验证Float和Double
System.out.println(4-3.9); //0.10000000000000009精度不准
Float f1 = 4f-3.9f;
Float f2 = 0.1f;
System.out.println(f1 == f2); //false 验证了没有常量池
System.out.println(f1.equals(f2)); // false 两个浮点型比较,也包含了精确度。
包装类其他常用方法
成员方法: xxxValue() ,用来拆箱,但是现在用的少,一般都是自动拆箱
工具方法:
static WrapperClassName valueOf(xxx value)
static WrapperClassName valueOf(String value)
static xxx parseXXX(String value)
static String toBinaryString(); 转成二进制
static String toHexString(); 转成16进制
static String toOctalString() 转成8进制
eg:
//手动装箱方法,使用的也少,目的就是装箱成包装类对象
Double v = Double.valueOf("3002.15"); //手动装箱
//从控制台上扫描一个纯数字的字符串
String line = "1201";
int i = Integer.parseInt(line);
System.out.println(i+1);
line = "1000.123";
double d1 = Double.parseDouble(line);
System.out.println(d1);//获取int的最大值和最小值。
System.out.println(Integer.MAX_VALUE);
System.out.println(Integer.MIN_VALUE);//转成其他进制
int num = 1001;
System.out.println(Integer.toBinaryString(num)); //1111101001
System.out.println(Integer.toOctalString(num)); // 1751
System.out.println(Integer.toHexString(num)); // 3e9
BigDecimal类
前面提到浮点数运算时容易精度不准确,因此java提供了BigDecimal这个类来完善这类运算,运算可以非常精确,精确到小数点后无数位。
BigDecimal 通常支持任意位数的小数部分,用来对超过16位有效位的数进行精确的运算,(float可以精确到7左右,double是15位左右)
BigInteger: 表示大整数。一般用来处理非常大的数字,大到long也表示不了的数字。
常用构造器
BigDecimal(int val) 创建一个具有参数所指定整数值的对象。
BigDecimal(double val) 创建一个具有参数所指定双精度值的对象。不推荐使用,因为存在精度丢失问题
BigDecimal(long val) 创建一个具有参数所指定长整数值的对象。
BigDecimal(String val) 创建一个具有参数所指定以字符串表示的数值的对象 推荐使用
常用方法
BigDecimal add(BigDecimal other); BigDecimal对象中的值相加,返回BigDecimal对象
BigDecimal subtract(BigDecimal other);BigDecimal对象中的值相减,返回BigDecimal对象
BigDecimal multiply(BigDecimal other);BigDecimal对象中的值相乘,返回BigDecimal对象
BigDecimal divide(BigDecimal other): 做除法时要注意,除数不能是0, 不能出现除不尽的情况,不然都会报异常。
xxx xxxValue() : 将BigDecimal对象中的值转换成相应的基本数据类型
String toString(): 有必要时使用科学计数法。
//想要计算10和20的和
BigDecimal b1 = new BigDecimal("10");
BigDecimal b2 = new BigDecimal("20");
BigDecimal v = b1.add(b2);
System.out.println(v); // 调用了重写的toString,返回字符串样子。
//或者调用xxxValue方法
System.out.println(v.intValue()+1); //
//减法操作
BigDecimal subtract = b1.subtract(b2);
System.out.println(subtract);
//乘法操作
BigDecimal multiply = b1.multiply(b2);
System.out.println(multiply);
//除法操作
BigDecimal b3 = new BigDecimal("10");
BigDecimal b4 = new BigDecimal("200");
BigDecimal divide = b3.divide(b4,1,BigDecimal.ROUND_HALF_DOWN);
System.out.println(divide);
常用的舍入模式
BigDecimal.setScale() 方法用于格式化小数点
setScale(1) 表示保留一位小数,默认用四舍五入方式setScale(1,BigDecimal.ROUND_DOWN) 直接删除多余的小数位,如2.35会变成2.3
setScale(1,BigDecimal.ROUND_UP) 进位处理,2.32变成2.4
setScale(1,BigDecimal.ROUND_HALF_UP) 四舍五入,2.35变成2.4
setScaler(1,BigDecimal.ROUND_HALF_DOWN) 五舍六入,2.35变成2.3,如果是5则向下舍
setScaler(1,BigDecimal.ROUND_CEILING) 接近正无穷大的舍入
setScaler(1,BigDecimal.ROUND_FLOOR) 接近负无穷大的舍入,数字>0和ROUND_UP作用一样,数字<0和ROUND_DOWN作用一样
setScaler(1,BigDecimal.ROUND_HALF_EVEN) 向最接近的数字舍入,如果与两个相邻数字的距离相等,则向相邻的偶数舍入。setScaler(1,BigDecimal.ROUND_UNNECESSARY)计算结果是精确的,不需要舍入模式。如果对获得精确结果的操作指定此舍入模式,则抛出ArithmeticException。
常见问题:
创建 BigDecimal精度丢失的坑
在BigDecimal 中提供了多种创建方式,可以通过new 直接创建,也可以通过 BigDecimal.valueOf 创建。这两种方式使用不当,也会导致精度问题。如下:
public static void main(String[] args) throws Exception {
BigDecimal b1= new BigDecimal(0.1);
System.out.println(b1);
BigDecimal b2= BigDecimal.valueOf(0.1);
System.out.println(b2);
BigDecimal b3= BigDecimal.valueOf(0.111111111111111111111111111234);
System.out.println(b3);
}
执行结果:
0.1000000000000000055511151231257827021181583404541015625
0.1
0.1111111111111111
上面示例中两个方法都传入了double类型的参数0.1
但是 b1 还是出现了精度的问题。造成这种问题的原因是 0.1 这个数字计算机是无法精确表示的,送给 BigDecimal 的时候就已经丢精度了。
而 BigDecimal.valueOf 的实现却完全不同,如下源码所示,BigDecimal.valueOf 中是把浮点数转换成了字符串来构造的BigDecimal,因此避免了问题。
public static BigDecimal valueOf(double val) {
return new BigDecimal(Double.toString(val));
}
第一,在使用BigDecimal构造函数时,尽量传递字符串而非浮点类型;
第二,如果无法满足第一条,则可采用BigDecimal.valueOf方法来构造初始化值。
但是valueOf受double类型精度影响,当传入参数小数点后的位数超过double允许的16位精度还是可能会出现问题的。
等值比较的坑
一般在比较两个值是否相等时,都是用equals 方法,
但是,在BigDecimal 中使用equals可能会导致结果错误,
BigDecimal 中提供了 compareTo 方法,在很多时候需要使用compareTo 比较两个值。
如下所示:
public static void main(String[] args){
BigDecimal b1 = new BigDecimal("1.0");
BigDecimal b2 = new BigDecimal("1.00");
System.out.println(b1.equals(b2));
System.out.println(b1.compareTo(b2));
}
执行结果
false
0
出现此种结果的原因是,equals不仅比较了值是否相等,还比较了精度是否相同。
示例中,由于两个值的精度不同,所有结果也就不相同。
而 compareTo 是只比较值的大小。
返回的值为-1(小于),0(等于),1(大于)。
无限精度的坑
BigDecimal 并不代表无限精度,当在两个数除不尽的时候,就会出现无限精度的坑
如下所示:
public static void main(String[] args){
BigDecimal b1 = new BigDecimal("1.0");
BigDecimal b2 = new BigDecimal("3.0");
b1.divide(b2);
}Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
at java.math.BigDecimal.divide(BigDecimal.java:1693)
at com.demo.controller.Test.main(Test.java:29)
如果在除法(divide)运算过程中,如果商是一个无限小数(如 0.333…),而操作的结果预期是一个精确的数字,那么将会抛出ArithmeticException异常。
此种情况,只需要在使用 divide方法时指定结果的精度即可:
public static void main(String[] args){
BigDecimal b1 = new BigDecimal("1.0");
BigDecimal b2 = new BigDecimal("3.0");
System.out.println(b1.divide(b2,2, RoundingMode.HALF_UP));//0.33
}
在使用BigDecimal进行(所有)运算时,尽量指定精度和舍入模式。