JAVA基本类型包装类、BigDecimal、BigInteger 的使用

1,137 阅读9分钟

这是我参与8月更文挑战的第25天,活动详情查看:8月更文挑战

文章目录

了解包装类

Java 中预定义了八种基本数据类型,包括:byte,int,long,double,float,boolean,char,short。基本类型与对象类型最大的不同点在于,基本类型基于数值,对象类型基于引用。

例如有一个方法 f() ,它的参数分别是对象类型 和 基本类型:

void f(Object obj){
	//参数引用类型,保存的是内存地址
}

f(123){
	//基本类型
}

基本类型基于数值,所以基本类型是没有类而言的,是不存在类的概念的,也就是说,变量只能存储数值,而不具备操作数据的方法。

对象类型则截然不同,变量实际上是某个类的实例,可以拥有属性方法等信息,不再单一的存储数值,可以提供各种各样对数值的操作方法,但代价就是牺牲一些性能并占用更多的内存空间。

Java 中提供了哪些「包装类型」来弥补「基本类型」不具备面向对象思想的劣势呢?看下表:

基本类型包装类型
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean

java.lang.Number

这里需要提到 java.lang.Number,它是所有数值类的父类,其直接子类包括:Byte、Short、Integer、Long、Float、Double、BigDecimal、BigInteger。

这些类取出内部封装的基本类型值的方法很简单也很好记:byteValue()shortValue()intValue()longValue()floatValue()doubleValue()

其他使用方法也基本相同,下面我们以 Integer 和 Double 为例具体讲一讲

Integer

首先需要明确一点的是,既然 Integer 是 int 的包装类型,那么必然 Integer 也能像 int 一样存储整型数值。有兴趣的同学可以看看源码。

Integer 类的内部定义了一个私有字段 value,专门用于保存一个整型数值,整个包装类就是围绕着这个 value 封装了各种不同操作的方法:
在这里插入图片描述

创建对象

Integer i1 = 21;
Integer i2 = Integer.valueOf("21");

自动拆装箱
所谓「拆箱」就是指,包装类型转换为基本类型的过程,而所谓的「装箱」则是基本类型到包装类型的过程。

Integer integer = new Integer(21);//装箱
int num = integer.intValue();//拆箱

自从 jdk1.5 以后,引入了自动拆装箱的概念,上述代码可以简化成如下代码:

Integer integer = 21;//装箱
int num = integer;//拆箱

我们来进行分析一下。

自动装箱
Integer integer = 21; 给一个 Integer 类型的对象不能直接赋一个基本类型值,integer 保存的是一个内存地址。21 自动封装成一个对象,把这个对象的地址赋值给 integer。这个对象是自动创建的,是自动装箱。在程序中编译成:Integer integer = Integer.valueOf(21);

自动拆箱
int num = integer; 定义一个 int 类型变量,num 应该直接存一个值,integer 保存的是一个内存地址。integer 自动取出对象中的值,然后把值赋值给 num。系统为我们执行了:int num = integer.intValue();

拆装箱是需要方法调用的,耗资源,所以我们的程序中应当尽量避免大量的「拆装箱」操作。

自动拆箱要当心 null 值

我们看以下程序:

public class Main {
    public static void main(String[] args) {
        int j = 0;
        List<Integer> list = new ArrayList<>();
        list.add(3);
        list.add(null);
        for (int i : list) {
            j += i;
        }
        System.out.println(j);
    }
}

运行时会报空指针
在这里插入图片描述
所以在获取到值有可能为空要装箱的时候,一定要加上 null 值的校验。所以可以把程序改为:

在这里插入图片描述
程序结果为:

3

面试题1

		Integer i1 = 100;
        Integer i2 = 100;
        Integer i3 = 200;
        Integer i4 = 200;

        System.out.println(i1==i2);
        System.out.println(i3==i4);

输出结果:

true
false

Integer 类中,存在 Integer 实例缓存数组,范围:-128 - 127。如果在自动装箱的时候给局部变量的int型值是在上面的范围之中,如果是范围内的值,就使用存在的缓存对象;如果不是范围内的值,会新建对象。

面试题2

		Integer i1 = new Integer(21);
        Integer i2 = new Integer(21);
        System.out.println(i1 == i2);
        System.out.println(i1.equals(i2));

输出结果:

false
true

如果new就会在堆内存开辟空间,new两次就开辟两段空间,两段空间地址值一定不一样。不推荐这么写,过时了。

方法
1、从 Number 继承的 6 个方法
byteValue()shortValue()intValue()longValue()floatValue()doubleValue()

2、字符串解析成 int
Integer.parseInt("255");//将字符串转换为 int 类型的数值,按十进制解析成 255
Integer.parseInt("11111111", 2)//按二进制解析成 255
Integer.parseInt("377",8);//按八进制解析成 255
Integer.parseInt("ff", 16);//按十六进制解析成 255
3、进制转换
Integer.toBinaryString(255);//将数字转成二进制,解析成"11111111"
Integer.toOctalString(255);//将数字转换成八进制,解析成"377"
Integer.toHexString(255);//将数字转换成十六进制,解析成"ff"

完整程序

		System.out.println("i1:" + Integer.parseInt("255"));
        System.out.println("i2:" + Integer.parseInt("11111111", 2));
        System.out.println("i3:" + Integer.parseInt("377", 8));
        System.out.println("i4:" + Integer.parseInt("ff", 16));

        System.out.println("s1:" + Integer.toBinaryString(255));
        System.out.println("s2:" + Integer.toOctalString(255));
        System.out.println("s3:" + Integer.toHexString(255));

输出结果

255
255
255
255
11111111
377
ff

Double

Double 类在对象中包装了一个基本类型 double 的值。Double 类对象包含一个 double 类型的字段。此外,该类还提供了多个方法,可以将 double 类型与 String 类型相互转换,同时 还提供了处理 double 类型时比较常用的常量和方法。

创建对象

Double d1 = 21d;
Double d2 = Double.valueOf("21");

方法

1、从Number类继承的6个方法,同上
2、将数字字符串转换为 Double 数值 Double.parseDouble("3.14");
3、判断浮点数特殊值
Double.isInfinite(double d):如果此对象表示的值是正无穷大或负无穷大,则返回 true;否则返回
Double.isNaN(double d):如果此 Double 值是一个非数字值,则返回 true,否则返回 false

Infinity:无穷大
NaN:not a number

BigDecimal

浮点数的运算是不精确的,我们看以下运算

System.out.println(2 - 1.9);
System.out.println(4.35 * 100);

运算结果:
在这里插入图片描述
那为什么会出现这种情况呢?

因为不论是 float 还是 double 都是浮点数,而计算机是二进制的,浮点数会失去一定的精确度。究其根本原因是,十进制值通常没有完全相同的二进制表示形式,十进制数的二进制表示形式可能不精确。只能无限接近于那个值。

BigDecimal 的作用就是做精确的浮点运算

构造方法

public BigDecimal(double val)double表示形式转换为BigDecimal *不建议使用
public BigDecimal(int val)  将int表示形式转换成BigDecimal
public BigDecimal(String val)  将String表示形式转换成BigDecimal * 推荐使用

参数类型为 double 的构造方法的结果有一定的不可预知性。所以通常情况下不使用。

String 构造方法是完全可预知的:写入 newBigDecimal(“0.1”), 将创建一个 BigDecimal,它正好等于预期的 0.1。因此,比较而言,通常建议优先使用 String 构造方法。

当 double 必须用作 BigDecimal 的源时,请使用 Double.toString(double) 转成String,再使用 String 构造方法。或使用 BigDecimal 的静态方法 valueOf。

举例:使用 double 做构造参数的正确使用
如下程序中 bd1 展示了 参数类型为 double 的构造方法的的不可预知性,db2 展示了 String 的构造方法,bd3、bd4 展示了正确使用 double 做为参数的构造方法。

		BigDecimal bd1 = new BigDecimal(1.9);
        BigDecimal bd2 = new BigDecimal("1.9");
        BigDecimal bd3 = BigDecimal.valueOf(1.9);
        BigDecimal bd4 = new BigDecimal(Double.toString(1.9));


        System.out.println("db1:" + bd1);
        System.out.println("db2:" + bd2);
        System.out.println("db3:" + bd3);
        System.out.println("db4:" + bd4);

运行结果:
在这里插入图片描述

方法
add(BigDecimal bd)+
subtract(BigDecimal bd)-
multiply(BigDecimal bd)*
divide(BigDecimal bd)/
divide(BigDecimal bd,保留位数,舍入方式)
setScale(保留位数,舍入方式) 舍入运算,运算结果会封装成新的大数字对象返回

举例:实现本节开头的精确运算

		BigDecimal bd1 = new BigDecimal("2");
        BigDecimal bd2 = new BigDecimal("1.9");
        System.out.println("2 - 1.9 = " + bd1.subtract(bd2));

        BigDecimal bd3 = new BigDecimal("4.35");
        BigDecimal bd4 = new BigDecimal("100");
        System.out.println("4.35 * 100 = " + bd3.multiply(bd4));

运行结果:
在这里插入图片描述
举例:四舍五入

BigDecimal bd = BigDecimal.valueOf(2.345);
double result = bd.setScale(2, RoundingMode.HALF_UP).doubleValue();
System.out.println("result:" + result);

运行结果:
在这里插入图片描述
举例:计算降落距离

		System.out.println("输入降落时间");
        double t = new Scanner(System.in).nextDouble();
        //1/2*9.8*t*t
        BigDecimal a = BigDecimal.valueOf(0.5);
        BigDecimal b = BigDecimal.valueOf(9.8);
        BigDecimal c = BigDecimal.valueOf(t);

        double result = a.multiply(b).multiply(c.pow(2)).doubleValue();

        System.out.println("降落距离:" + result + "米");

运行结果:
在这里插入图片描述

BigInteger

作用:做超大整数运算

在 Java中,由CPU原生提供的整型最大范围是 64 位 long 型整数。使用 long 型整数可以直接通过 CPU 指令进行计算,速度非常快。

如果我们使用的整数范围超过了 long 型怎么办?这个时候,就只能用软件来模拟一个大整数。java.math.BigInteger 就是用来表示任意大小的整数。

我们通过一个例子就可以了解它的使用。

举例:计算阶乘

public class Main {
    public static void main(String[] args) {
        System.out.println("输入数字来计算阶乘:");
        int number = new Scanner(System.in).nextInt();
        System.out.println("阶乘结果:" + f(number));
    }

    private static String f(int number) {
        //超大整数运算
        BigInteger bd = BigInteger.valueOf(number);
        for (int i = number - 1; i >= 1; i--) {
            BigInteger bi = BigInteger.valueOf(i);
            bd = bd.multiply(bi);
        }
        return bd.toString();
    }
}

运行结果:
在这里插入图片描述
方法

BigInteger abs()  返回大整数的绝对值
BigInteger add(BigInteger val) 返回两个大整数的和
BigInteger and(BigInteger val)  返回两个大整数的按位与的结果
BigInteger andNot(BigInteger val) 返回两个大整数与非的结果
BigInteger divide(BigInteger val)  返回两个大整数的商
double doubleValue()   返回大整数的double类型的值
float floatValue()   返回大整数的float类型的值
BigInteger gcd(BigInteger val)  返回大整数的最大公约数
int intValue() 返回大整数的整型值
long longValue() 返回大整数的long型值
BigInteger max(BigInteger val) 返回两个大整数的最大者
BigInteger min(BigInteger val) 返回两个大整数的最小者
BigInteger mod(BigInteger val) 用当前大整数对val求模
BigInteger multiply(BigInteger val) 返回两个大整数的积
BigInteger negate() 返回当前大整数的相反数
BigInteger not() 返回当前大整数的非
BigInteger or(BigInteger val) 返回两个大整数的按位或
BigInteger pow(int exponent) 返回当前大整数的exponent次方
BigInteger remainder(BigInteger val) 返回当前大整数除以val的余数
BigInteger leftShift(int n) 将当前大整数左移n位后返回
BigInteger rightShift(int n) 将当前大整数右移n位后返回
BigInteger subtract(BigInteger val)返回两个大整数相减的结果
byte[] toByteArray(BigInteger val)将大整数转换成二进制反码保存在byte数组中
String toString() 将当前大整数转换成十进制的字符串形式
BigInteger xor(BigInteger val) 返回两个大整数的异或

参考
基本数据类型及其包装类(一)
Java BigDecimal详解