跳到主要内容

第16章-包装类

概述

  • 在Java世界中,一切皆为对象

  • Java中的数据类型分类有

    • 基本数据类型,共有8种,运算效率、存储效率非常高

    • 引用数据类型,有Java体系中提供的,如String、Scanner等;也可以自定义,如Student

  • 引入包装类的原因

    • 8种基本数据类型并不符合Java语言面向对象的特性
    • 包装类能提供更多的功能;如前面使用过的Integer包装类的输出二进制字符串方法、字符串转基本数据类型等
    • 有些场景,属性的值不确定,基本数据类型就不合适了,因为都有默认值;此时,使用包装类,并赋值null,就能满足此类需要;如年龄属性,使用int,默认为0就不一定合适
    • 有了包装类后,有些工具类,就不支持基本数据类型了,而只支持包装类;如集合类中的ArrayList等
  • 基本数据类型与包装类的差异

    • 存储位置不同,基本数据类型变量值直接存放在栈中,而包装类对象存放在堆中
    • 默认值不同,基本数据类型的成员变量默认值为相应类型的默认值,包装类默认值为null
    • 运算效率不同,基本数据类型的运算效率更高,包装类相对要低一些
    • ==比较方式不同,基本数据类型直接比较,包装类,要使用其xxxValue方法转为基本数据类型再比较
  • 基本数据类型与包装类对应关系,如下图:

    包装类-包装类图示

    基本数据类型与包装类对应关系
  • 其他特点

  • 所有的包装类都实现了Serializable接口,表示可序列化

  • 所有的包装类都实现了Comparable接口,并覆盖了其中的comparTo方法,表示可用于两两比较,直接应用于排序工具中实现排序

  • Short、Byte、Integer、Long、Float、Double都继承了Number抽象类,该类主要提供一些转基本数据类型的抽象方法

装箱和拆箱

  • 在实际的开发过程中,由于基本数据类型和包装类同时存在,会经常进行基本数据类型和包装类的相互转换,转换过程称之为装箱和拆箱

  • 频繁的装箱和拆箱,会带来性能上的损失

  • 从JDK1.5后,基本已经完成了自动装箱自动拆箱,自动装箱是通过valueOf方法实现,自动拆箱是通过xxxValue方法实现

  • 装箱:基本数据类型转包装类

    //方式1、使用new创建包装类对象,自动装箱
    Integer integerReference1 = new Integer(20);
    //方式2、使用字面量创建包装类对象,自动装箱
    Integer integerReference2 = 20;
    //方式3、使用valueOf创建包装类对象,自动装箱
    Integer integerReference3 = Integer.valueOf(20);
    Integer integerReference4 = Integer.valueOf("20"); //方法同上,但字符串中必须为数值
  • 拆箱:包装类变成基本数据类型

    //定义包装类
    Integer integerReference1 = new Integer(20);
    //方式1、使用包装类对象直接赋值,自动拆箱
    int intVariable1 = integerReference1;
    //方式2、使用包装类对象的xxxValue方法,自动拆箱
    int intVariable2 = integerReference1.intValue();
    //方式3、包装类与字面量、基本数据类型进行运算,也会进行自动拆箱后,进行计算
    integerReference1 = integerReference1 + 30; //会先拆箱,再装箱

基本原则

  • 基本数据类型和包装类进行比较运算,会先将包装类进行拆箱成基本数据类型,然后进行比较
  • 两个包装类之间的运算,会被自动拆箱成基本类型进行计算
  • 基本数据类型放入集合类中的时候,会进行自动装箱
  • 三元运算符的使用过程中。两个表达式分别为基本数据类型和包装类时,会将包装类拆箱为基本数据类型,然后进行操作

如何选择?

问题:什么时候使用基本数据类型?什么时候使用包装类?

  • 成员变量使用包装类
  • 方法中形式参数、返回值推荐使用包装类
  • 局部变量使用基本数据类型

常用静态常量、静态方法和普通方法(主要以Integer包装类为例)

  • 静态常量

    • Integer.MAX_VALUE,最大值
    • Integer.MIN_VALUE,最小值
    • Integer.SIZE,二进制位数
    • Integer.BYTES,字节数
  • 静态方法

    • Integer.paseInt,字符串转int
    • Integer.valueOf,字符串转Integer
    • Integer.compare,比较两个int整数大小 , 返回1表示参数1大于参数2,0表示参数1等于参数2,1表示参数1小于参数2
    • Integer.max,获取两个int整数的最大值
    • Integer.min,获取两个int整数的最小值
    • Integer.sum,获取两个int整数的汇总值
    • Integer.toBinaryString,int整数转二进制字符串
    • Integer.toOctalString,int整数转八进制字符串
    • Integer.toHexString,int整数转十六进制字符串
  • 普通方法

    • compareTo,当前Integer对象跟另一个Integer对象比较
    • xxxValue,包装类转指定基本数据类型

    应用实例

    package com.bjpowernode.demo;

    /**
    * 包装类示例
    */
    public class WrapperDemo {
    public static void main(String[] args) {
    System.out.println("--------------Integer及其他包装类中的静态常量--------------");
    System.out.println("-----Integer-----");
    System.out.println(Integer.MIN_VALUE);
    System.out.println(Integer.MAX_VALUE);
    System.out.println(Integer.SIZE);
    System.out.println(Integer.BYTES);
    System.out.println("-----Long-----");
    System.out.println(Long.MIN_VALUE);
    System.out.println(Long.MAX_VALUE);
    System.out.println(Long.SIZE);
    System.out.println(Long.BYTES);
    System.out.println("-----Float-----");
    System.out.println(Float.MIN_VALUE);
    System.out.println(Float.MAX_VALUE);
    System.out.println(Float.SIZE);
    System.out.println(Float.BYTES);

    System.out.println("--------------字符串转int--------------");
    String str1 = "10086";
    int intVariable = Integer.parseInt(str1);
    System.out.println(intVariable);

    System.out.println("--------------字符串转包装类--------------");
    String str2 = "10086";
    Integer integerVariable = Integer.valueOf(str2);
    System.out.println(integerVariable);

    System.out.println("--------------比较两个int整数大小--------------");
    System.out.println(Integer.compare(15, 20));
    System.out.println(Integer.compare(20, 15));
    System.out.println(Integer.compare(100, 100));

    System.out.println("--------------获取两个int整数的最大值、最小值、汇总值--------------");
    System.out.println(Integer.max(15,20));
    System.out.println(Integer.min(15,20));
    System.out.println(Integer.sum(15,20));

    System.out.println("--------------int整数二进制、八进制、十六进制字符串--------------");
    System.out.println(Integer.toBinaryString(31));
    System.out.println(Integer.toOctalString(31));
    System.out.println(Integer.toHexString(31));

    System.out.println("--------------当前Integer对象跟另一个Integer对象比较,实现Comparable接口的compareTo方法--------------");
    Integer first = Integer.valueOf(15);
    Integer second = Integer.valueOf(20);
    System.out.println(first.compareTo(second));
    System.out.println(second.compareTo(first));
    }
    }

缓存机制

  • \==比较问题,包装类进行比较时,不要直接使用==比较,有可能会返回false,如下面两个代码段,会出现不同的返回

    • 代码段1,返回true,由于比较的数据-128~127之间,使用常量池中的值直接比较

      //进行比较的两个变量定义,并赋初始值
      Integer first = 127;
      Integer second = 127;

      //使用==比较
      System.out.println(first == second); //返回true,由于比较的数据在-128~127之间,使用内部缓存
    • 代码段2,返回false,由于比较的数据不在-128~127之间,不使用常量池中的值进行比较,比较的是堆中地址

      //进行比较的两个变量定义,并赋初始值
      Integer first = 128;
      Integer second = 128;

      //使用==比较
      System.out.println(first == second); //返回false,由于比较的数据不在-128~127之间,不使用内部缓存,比较的是堆中地址
    • 代码段3,此时,需要使用.xxxValue()方法,转换成基本数据类型进行比较,才能进行非-128~127之外的值的比较;上述代码段2修改成如下代码后,符合比较逻辑;故,建议所有的包装类比较都转换成基本数据类型进行

      //进行比较的两个变量定义,并赋初始值
      Integer first = 128;
      Integer second = 128;

      //使用==比较
      System.out.println(first.intValue() == second.intValue());
  • 问题根源

    • 除了Float、Double类型以外,其他6种包装类都存在缓存机制,缓存一定范围的值(具体范围见下图“缓存范围”),以提高性能与效率

    • 使用valueOf装箱构造包装类对象时,如果是缓存范围的值,会直接从缓存内部类IntegerCache的cache[]数组中获取包装类对象;注意,使用new生成包装类对象没有此效果

    • 比较时,如果是缓存范围的值,都指向是缓存内部类IntegerCache的cache[]数组中相同的对象,会相等;如果不是缓存范围的值,则会在堆中创建一两个新的对象,地址不同,会不相等

      包装类-包装类图示

【练习】

  1. 练习应用实例的内容,完成代码编写
  2. 编写程序,定义一个MyOperation类,里面包含多个operate重载方法,用于进行两个包装类型的操作,返回相同的包装类型,分别尝试任意3种包装类

BigInteger【扩展】

  • 用于解决需要进行超过long的长度的变量定义或运算,将会产生的数据失真问题

  • 定义方式

    • 方式1,使用字符串初始化,字符串中,就可支持超过long最大值的数字字符串;注意字符串中不能有非数字的字符,否则会产生异常

      BigInteger bigInteger = new BigInteger("9223372036854775808");      //long类型最大值为9223372036854775807
    • 方式2,使用字面量初始化

      BigInteger bigInteger = BigInteger.valueOf(9223372036854775807L);      //long类型最大值为9223372036854775807
  • 常用接口,由于数据太大,使用运算符进行运算会产生数据失真,BigInteger提供了一系列的方法进行运算来保证数据不失真,常见的方法有

    • add:加
    • subtract:减
    • multiply:乘
    • divide:除,整除
    • remainder:取余
    • pow:次方
    • byteValue:返回byte基本数据类型值
    • shortValue:返回short基本数据类型值
    • intValue:返回int基本数据类型值
    • longValue:返回long基本数据类型值
    • ...
  • 应用实例

    • 实例1,常见方法使用

      实例代码

      package com.bjpowernode.demo;

      import java.math.BigInteger;

      /**
      * BigInteger示例
      */
      public class BigIntegerDemo {
      public static void main(String[] args) {
      //定义变量
      BigInteger first = BigInteger.valueOf(9223372036854775807L); //long类型最大值为9223372036854775807
      BigInteger second = BigInteger.valueOf(9223372036854775807L); //long类型最大值为9223372036854775807
      BigInteger result;

      //加法
      result = first.add(second);
      System.out.println("加法计算结果:" + result);

      //减法
      result = first.subtract(second);
      System.out.println("减法计算结果:" + result);

      //乘法
      result = first.multiply(second);
      System.out.println("乘法计算结果:" + result);

      //除法
      result = first.divide(second);
      System.out.println("除法计算结果:" + result);

      //取余
      result = first.remainder(second);
      System.out.println("取余计算结果:" + result);

      //次方
      result = first.pow(5);
      System.out.println("次方计算结果:" + result);
      }
      }

      输出结果,运行查看结果

    • 实例2,从控制台接收输入BigInteger数据

      实例代码

      package com.bjpowernode.demo;

      import java.math.BigInteger;
      import java.util.Scanner;

      /**
      * BigInteger示例
      */
      public class BigIntegerDemo {
      public static void main(String[] args) {
      //定义变量
      BigInteger bigInteger;

      //接收输入
      System.out.print("请输入一个很大的数:");
      Scanner scanner = new Scanner(System.in);
      bigInteger = scanner.nextBigInteger();

      //输出
      System.out.println("输入的BigInteger值为:" + bigInteger);
      }
      }

      输出结果,运行查看结果

【练习】

  1. 练习应用实例内容,完成代码编写

BigDecimal【扩展】

  • 用于解决精度超过double类型的16位时,精度丢失的问题

  • 定义方式,有多种,但推荐使用字符串作为构造参数方式,不会有精度丢失

    BigDecimal bigDecimal = new BigDecimal("0.1234567890123456789");   //超过16位小时,依然保持完整精度
  • 常用接口,由于精度要求更高,原有的运算符进行运算会产生精度丢失,BigDecimal提供了一系列的方法进行运算来保证精度不丢失,常见的方法有

    • add:加
    • subtract:减
    • multiply:乘
    • divide:除,整除
    • remainder:取余
    • pow:次方
    • floatValue:返回float基本数据类型值
    • doubleValue:返回double基本数据类型值
    • ...
  • 应用实例

    • 应用实例1,常见方法使用;注意,类似0E-4这种科学计数法,表示0.0000的结果

      实例代码

      package com.bjpowernode.demo;

      import java.math.BigDecimal;

      /**
      * BigDecimal示例
      */
      public class BigDecimalDemo {
      public static void main(String[] args) {
      //定义变量
      BigDecimal first = new BigDecimal("0.123456790123456789"); //19位小数
      BigDecimal second = new BigDecimal("0.123456790123456789"); //19位小数,也可尝试普通小数0.1
      BigDecimal result;

      //加法
      result = first.add(second);
      System.out.println("加法计算结果:" + result);

      //减法
      result = first.subtract(second);
      System.out.println("减法计算结果:" + result);

      //乘法
      result = first.multiply(second);
      System.out.println("乘法计算结果:" + result);

      //除法
      result = first.divide(second);
      System.out.println("除法计算结果:" + result);

      //取余
      result = first.remainder(second);
      System.out.println("取余计算结果:" + result);

      //次方
      result = first.pow(5);
      System.out.println("次方计算结果:" + result);
      }
      }

      输出结果,运行查看结果

    • 应用实例2,从控制台接收输入BigDecimal数据

      实例代码

      package com.bjpowernode.demo;

      import java.math.BigDecimal;
      import java.util.Scanner;

      /**
      * BigDecimal示例
      */
      public class BigDecimalDemo {
      public static void main(String[] args) {
      //定义变量
      BigDecimal bigDecimal;

      //接收输入
      System.out.print("请输入超过16位小数位的数:");
      Scanner scanner = new Scanner(System.in);
      bigDecimal = scanner.nextBigDecimal();

      //输出
      System.out.println("输入的BigDecimal值为:" + bigDecimal);
      }
      }

      输出结果,运行查看结果

【练习】

  1. 练习应用实例内容,完成代码编写

实战和作业

  1. 【扩展】编写程序,要求如下

    1. 定义一个产品类(Product),要求如下
      1. 具备产品编号(每个产品对象的产品编号都不相同)、产品名称、产品单价、库存数量等属性,属性都使用包装类
      2. 具有入库方法,形参为入库数量,能够增加产品数量,返回是否入库成功
      3. 具有出库方法,形参为出库数量,能够出库不超过库存数量的产品,返回是否出库成功
    2. 定义一个仓库类(StoreHouse),要求如下
      1. 具备仓库编号、仓库名称、产品列表属性,即Product[]
      2. 具备指定产品入库方法,形参为产品编号、入库数量,能够针对该产品编号的产品进行库存数量增加,返回入库结果:0表示成功、1表示产品编号不存在、2表示入库数量不能小于0
      3. 具备指定产品出库方法,形参为产品编号、出库数量,能够针对该产品编号的产品进行库存数量减少,返回出库结果:0表示成功、1表示产品编号不存在、2表示库存数量不足
    3. 定义一个仓库测试类(StoreHouseTest),进行业务测试,要求如下
      1. 定义一个仓库类对象,并初始化产品列表
      2. 测试仓库类入库方法,根据产品编号、入库数量,进行入库
      3. 测试仓库类出库方法,根据产品编号、出库数量,进行出库
      4. 汇总库存总数、库存总价值

    注意:合理使用包装类、基本数据类型