Java基础6:自动拆装箱

一、前言

  本文内容参考《深入理解Java核心技术:写给Java工程师的干货笔记(基础篇)》一书,2022年出版,作者 张洪亮(@Hollis),阿里巴巴技术专家,著有《Java工程师成神之路》系列文章,《Java工程师成神之路》电子书已开源,可在 阿里云开发者社区 免费下载。书籍内容比电子书内容要丰富,内容有修改,有需要的读者可以购买正版书籍。
 
  【如何成神:先搬砖,再砌砖,后造砖!】这句话的意思是,先学习别人的好文章中涉及到的技术知识,再尝试自己也写好相关技术文章,最后参与开源社区中的开源组件代码维护。
 
  本文由 @大白有点菜 原创,请勿盗用,转载请说明出处!如果觉得文章还不错,请点点赞,加关注,谢谢!
 
深入理解Java核心技术:写给Java工程师的干货笔记(基础篇) 

二、第7章 自动拆装箱

1、自动拆箱

 
  为了解决Java中的基本数据类型不面向对象的问题,Java官方设计了与基本数据类型对应的包装类(Wrapper Class)。
 

1.1 包装类

 
  包装类在 java.lang 包中,基本数据类型和包装类对应关系如下。注意,int 和 char 对应的包装类是不一样!
 

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

 
  为什么需要包装类呢?因为Java面向对象的特点,很多地方要使用对象而不是基本数据类型。比如,在集合类中,int 和 double 等类型是放不进去的,因为集合的容器要求元素是 Object 类型。
 
  包装类使基本数据类型具有对象的性质,里面包含属性和方法,丰富了基本数据类型的操作。
 

1.2 拆箱与装箱

 
  什么是拆箱与装箱呢?

  • 装箱(boxing)将 基本数据类型 转换成 包装类
  • 拆箱(unboxing)将 包装类 转换成 基本数据类型

 
  在 Java SE5 之前,可以通过以下代码进行装箱:

Integer num = new Integer(6);

 

1.3 自动拆箱与自动装箱

 
  什么又是自动拆箱与自动装箱呢?

  • 自动装箱将 基本数据类型 自动转换成 包装类
  • 自动拆箱将 包装类 自动转换成 基本数据类型
// 自动装箱
Integer num1 = 6;
// 自动拆箱
int num2 = num1;

 
  Integer i1 = 9 等同于 new Integer(9) 。但 new Integer(int value) 是老式(Java SE5)的写法,Java提供了自动装箱的功能,不需要开发者手动地去“new”一个 Integer 对象。
 

1.4 自动装箱与自动拆箱的实现原理

 
【自动装箱代码样例】

/**
 * Java基础6:自动拆装箱
 * @author 大白有点菜
 */
public class AutomaticBoxingUnboxingApp {
    public static void main(String[] args) {
        // 自动装箱
        Integer num1 = 6;
        // 自动拆箱
        int num2 = num1;
    }
}

 
  可以使用 IDEA 或者 Windows里面的CMD工具对编译出来的 .class 文件进行反编译, 正常编译的 .class 文件在 target 目录下。例如,笔者测试生成的 .class 文件是 AutomaticBoxingUnboxingApp.class 。使用 javap -c 命令进行反编译。

javap -c AutomaticBoxingUnboxingApp.class

 
IDEA中对class文件进行反编译 
Windows的CMD工具对class文件进行反编译 
  整理出反编译后的代码:

public static void main(String[] args) {
    Integer num1 = Integer.valueOf(6);
    int num2 = num1.intValue();
}

 
  从反编译的代码可以看出,int 的自动装箱都是调用 Integer.valueOf() 方法实现的,而 Integer 的自动拆箱都是调用 Integer.intValue() 实现的。发现规律如下:

自动装箱都通过包装类的 valueOf 方法实现,自动拆箱都通过包装类的 xxxValue 方法实现。

 

1.5 Java会在哪些场景自动拆装箱

 
(1)场景1:将基本数据类型放入集合类
 
  【将基本数据类型放入集合类 - 代码】

package cn.zhuangyt.javabase.automatic_boxing_unboxing;

import java.util.ArrayList;
import java.util.List;

/**
 * Java基础6:自动拆装箱
 * @author 大白有点菜
 */
public class AutomaticBoxingUnboxingApp {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            list.add(i);
        }
        System.out.println("list 数组集合:" + list);
    }
}

 
  【将基本数据类型放入集合类 - 代码运行结果】

list 数组集合:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

  反编译后代码如下:

List<Integer> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
    list.add(Integer.valueOf(i));
}

  由此可以得出结论:把基本数据类型放入集合类时,会进行自动装箱。
 
(2)场景2:包装类和基本类型的大小比较
 
  【包装类和基本类型的大小比较 - 代码】

package cn.zhuangyt.javabase.automatic_boxing_unboxing;

/**
 * Java基础6:自动拆装箱
 * @author 大白有点菜
 */
public class AutomaticBoxingUnboxingApp {
    public static void main(String[] args) {
        Integer num3 = 1;
        System.out.println("num3 == 1 比较结果:" + (num3 == 1 ? "等于" : "不等于"));
        Boolean bool = false;
        System.out.println("bool:" + (bool ? "真" : "假"));
    }
}

 
  【包装类和基本类型的大小比较 - 代码运行结果】

num3 == 1 比较结果:等于
bool:假

  反编译后代码如下:

Integer num3 = 1;
System.out.println("num3 == 1 比较结果:" + (num3.intValue() == 1 ? "等于" : "不等于"));
Boolean bool = false;
System.out.println("bool:" + (bool.booleanValue() ? "真" : "假"));

  可以看出,包装类与基本数据类型进行比较运算,是先将包装类拆箱成基本数据类型,然后进行比较。
 
(3)场景3:包装类的运算
 
  如何对Integer对象进行四则运算呢?
 
  【包装类的运算 - 代码】

package cn.zhuangyt.javabase.automatic_boxing_unboxing;

/**
 * Java基础6:自动拆装箱
 * @author 大白有点菜
 */
public class AutomaticBoxingUnboxingApp {
    public static void main(String[] args) {
        Integer num4 = 10;
        Integer num5 = 20;
        System.out.println("num4 + num5 = " + (num4 + num5));
    }
}

 
  【包装类的运算 - 代码运行结果】

num4 + num5 = 30

  反编译后代码如下:

Integer num4 = Integer.valueOf(10);
Integer num5 = Integer.valueOf(20);
System.out.println("num4 + num5 = " + (num4.intValue() + num5.intValue()));

  发现两个包装类会被自动拆箱成基本类型进行运算。
 
(4)场景4:三目运算符的使用(使用不当会产生NPE异常
 
  【三目运算符的使用 - 代码】

package cn.zhuangyt.javabase.automatic_boxing_unboxing;

/**
 * Java基础6:自动拆装箱
 * @author 大白有点菜
 */
public class AutomaticBoxingUnboxingApp {
    public static void main(String[] args) {
        boolean flag = true;
        Integer num6 = null;
        int num7 = 1;
        int num8 = flag ? num6 : num7;
        System.out.println("num8的值:" + num8);
    }
}

 
  【三目运算符的使用 - 代码运行结果】

Exception in thread "main" java.lang.NullPointerException
	at cn.zhuangyt.javabase.automatic_boxing_unboxing.AutomaticBoxingUnboxingApp.main(AutomaticBoxingUnboxingApp.java:38)

  反编译后代码如下:

boolean flag = true;
Integer num6 = null;
int num7 = 1;
int num8 = flag ? num6.intValue() : num7;
System.out.println("num8的值:" + num8);

  为什么会产生NPE(NullPointerException,空指针异常)呢?其实是三目运算符的语法规范。当第二、第三位操作数分别为基本类型和对象时,其中的对象就被拆箱为基本类型进行操作。在“int num8 = flag ? num6 : num7;”中,num6 是一个包装类 Integer 的对象,而 num7 是一个基本类型,所以会对 num6 进行自动拆箱num6.intValue()),此时 num6 为 null ,所以就产生了自动拆箱导致空指针异常。
 
  在《阿里巴巴Java开发手册》中,就强制要求使用三目运算符时要注意表达式的类型对齐,不然可能抛出因自动拆箱导致的NPE(NullPointerException)异常。阿里云开发者社区已开源相关电子书,电子书名为《Java开发手册(嵩山版)》,读者可自行去下载。
 
阿里巴巴的《Java开发手册(嵩山版)》电子书
 
阿里巴巴的《Java开发手册(嵩山版)》中三目运算符的自动拆箱抛出NPE
 
  【Java开发手册中关于三目运算符自动拆箱的样例 - 代码】

package cn.zhuangyt.javabase.automatic_boxing_unboxing;

/**
 * Java基础6:自动拆装箱
 * @author 大白有点菜
 */
public class AutomaticBoxingUnboxingApp {
    public static void main(String[] args) {
        Integer a = 1;
        Integer b = 2;
        Integer c = null;
        Boolean flag = false;
        Integer result = (flag ? a*b : c);
    }
}

 
  【Java开发手册中关于三目运算符自动拆箱的样例 - 代码运行结果】

Exception in thread "main" java.lang.NullPointerException
	at cn.zhuangyt.javabase.automatic_boxing_unboxing.AutomaticBoxingUnboxingApp.main(AutomaticBoxingUnboxingApp.java:38)

  a*b 的结果是 int 类型,那么 c 会强制拆箱成 int 类型,抛出 NPE 异常。
 
(5)场景5:函数参数与返回值
 
  【函数参数与返回值 - 代码】

package cn.zhuangyt.javabase.automatic_boxing_unboxing;

/**
 * Java基础6:自动拆装箱
 * @author 大白有点菜
 */
public class AutomaticBoxingUnboxingApp {
    public static void main(String[] args) {
        getNum1(6);
        getNum2(9);
    }

    /**
     * 自动拆箱
     * @param num
     * @return
     */
    public static int getNum1(Integer num) {
        return num;
    }

    /**
     * 自动装箱
     * @param num
     * @return
     */
    public static Integer getNum2(int num) {
        return num;
    }
}

 

1.6 自动装拆箱与缓存

 
  先来做一道经典的面试题提提神,你所认为的结果是正确的,但事实却是错误的,你可能会很难理解,为什么会这样子!
 
  【经典面试题1 - 代码】

package cn.zhuangyt.javabase.automatic_boxing_unboxing;

/**
 * Java基础6:自动拆装箱
 * @author 大白有点菜
 */
public class AutomaticBoxingUnboxingApp {
    public static void main(String[] args) {
        Integer i1 = 100;
        Integer i2 = 100;

        Integer i3 = 200;
        Integer i4 = 200;

        System.out.println("变量i1(" + i1 + ") == 变量i2(" + i2 + ")吗:" + (i1 == i2));
        System.out.println("变量i3(" + i3 + ") == 变量i4(" + i4 + ")吗:" + (i3 == i4));
    }
}

 
  读者们,你们觉得会输出什么结果呢,i1 和 i2 相等吗,i3 和 i4 相等吗?有人就说了,答案很简单,两者输出结果都为 false,因为变量都是包装类Integer对象,两者对象的引用不一样,所以使用“==”比较结果是 false !但是,事实是否如此呢?我们来运行看看结果,验证一番。
 
  【经典面试题1 - 代码运行结果】

变量i1(100) == 变量i2(100)吗:true
变量i3(200) == 变量i4(200)吗:false

  What?i1 == i2 的结果是 true ,为什么呀? 因为,官方在Integer 包装类中设计了一个缓存机制,当需要进行自动装箱时,如果数字在 -128~127 之间,就会直接使用缓存中的对象,而不是重新创建一个对象。在第二部分“缓存”一节中再来详细介绍,并剖析源码,看看官方是如何玩转 Integer 缓存机制的。
 
  额外补充点知识,前面例子中,Integer对象使用“==”比较是不允许的,在《阿里巴巴Java开发手册》中强制要求所有整型包装类对象之间的值比较,全部使用 equals 方法比较
 
阿里巴巴的《Java开发手册(嵩山版)》中所有整型包装类对象的值比较使用equals方法
 

1.7 自动拆箱带来的问题

 
  自动拆箱会引入一些问题。如下:

  • 比较包装对象的数值不能简单地使用“==” ,虽然 -128~127 之间的数字可以使用其来比较,但这个范围之外的数字还需要使用 equals 比较。
  • 有些场景下会进行自动拆箱,如果包装类对象为 null ,那么自动拆箱时就可能抛出 NPE(NullPointerException),即空指针异常。
  • 如果一个 for 循环中有大量拆装箱操作,则会浪费很多资源。

 

2、缓存

 
  Java 5中,在 Integer 的操作中引入了一个新功能来节省内存和提供性能——整型对象通过使用相同的对象引用实现了缓存和重用。

  • 适用于 -128 至 127 区间的整数值。
  • 只适用于自动装箱。使用构造函数创建的对象不适用。

  Integer的自动装箱就是调用 Integer.valueOf() 方法实现的。笔者的JDK版本是 jdk1.8_281。源码中一共有三个重载的 valueOf() 方法:
 
  【入参为 String 和 int 类型的 valueOf() 方法1】

/**
 * Returns an {@code Integer} object holding the value
 * extracted from the specified {@code String} when parsed
 * with the radix given by the second argument. The first argument
 * is interpreted as representing a signed integer in the radix
 * specified by the second argument, exactly as if the arguments
 * were given to the {@link #parseInt(java.lang.String, int)}
 * method. The result is an {@code Integer} object that
 * represents the integer value specified by the string.
 *
 * <p>In other words, this method returns an {@code Integer}
 * object equal to the value of:
 *
 * <blockquote>
 *  {@code new Integer(Integer.parseInt(s, radix))}
 * </blockquote>
 *
 * @param      s   the string to be parsed.
 * @param      radix the radix to be used in interpreting {@code s}
 * @return     an {@code Integer} object holding the value
 *             represented by the string argument in the specified
 *             radix.
 * @exception NumberFormatException if the {@code String}
 *            does not contain a parsable {@code int}.
 */
public static Integer valueOf(String s, int radix) throws NumberFormatException {
    return Integer.valueOf(parseInt(s,radix));
}

  【入参为 String 和 int 类型的 valueOf() 方法1 - 注解谷歌翻译如下】

  返回一个 Integer 对象,该对象包含在使用第二个参数给定的 radix(基数) 进行解析时从指定 String 中提取的值。 第一个参数被解释为表示第二个参数指定的 radix(基数) 中的有符号整数,就像将参数提供给 parseInt(String, int) 方法一样。 结果是一个 Integer 对象,表示由字符串指定的整数值。
  换句话说,此方法返回一个等于以下值的 Integer 对象:new Integer(Integer.parseInt(s,radix))
  参数:
    s – 要解析的字符串。
    radix – 用于解释 s 的基数。
  返回:一个 Integer 对象,它保存由指定基数中的字符串参数表示的值。
  抛出异常:NumberFormatException – 如果 String 不包含可解析的 int。

 
  【入参为 String 类型的 valueOf() 方法2】

/**
 * Returns an {@code Integer} object holding the
 * value of the specified {@code String}. The argument is
 * interpreted as representing a signed decimal integer, exactly
 * as if the argument were given to the {@link
 * #parseInt(java.lang.String)} method. The result is an
 * {@code Integer} object that represents the integer value
 * specified by the string.
 *
 * <p>In other words, this method returns an {@code Integer}
 * object equal to the value of:
 *
 * <blockquote>
 *  {@code new Integer(Integer.parseInt(s))}
 * </blockquote>
 *
 * @param      s   the string to be parsed.
 * @return     an {@code Integer} object holding the value
 *             represented by the string argument.
 * @exception  NumberFormatException  if the string cannot be parsed
 *             as an integer.
 */
public static Integer valueOf(String s) throws NumberFormatException {
    return Integer.valueOf(parseInt(s, 10));
}

  【入参为 String 类型的 valueOf() 方法2 - 注解谷歌翻译如下】

  返回一个 Integer 对象,其中包含指定 String 的值。 该参数被解释为表示一个带符号的十进制整数,就像该参数被提供给 parseInt(String) 方法一样。 结果是一个 Integer 对象,表示由字符串指定的整数值。
  换句话说,此方法返回一个等于以下值的 Integer 对象:new Integer(Integer.parseInt(s))
  参数:
    s – 要解析的字符串。
  返回:一个 Integer 对象,包含由字符串参数表示的值。
  抛出异常:NumberFormatException – 如果无法将字符串解析为整数。

 
  【入参为 int 类型的 valueOf() 方法3】

/**
 * Returns an {@code Integer} instance representing the specified
 * {@code int} value.  If a new {@code Integer} instance is not
 * required, this method should generally be used in preference to
 * the constructor {@link #Integer(int)}, as this method is likely
 * to yield significantly better space and time performance by
 * caching frequently requested values.
 *
 * This method will always cache values in the range -128 to 127,
 * inclusive, and may cache other values outside of this range.
 *
 * @param  i an {@code int} value.
 * @return an {@code Integer} instance representing {@code i}.
 * @since  1.5
 */
public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

  【入参为 int 类型的 valueOf() 方法3 - 注解谷歌翻译如下】

  返回表示指定 int 值的 Integer 实例。 如果不需要新的 Integer 实例,通常应优先使用此方法而不是构造函数 Integer(int),因为此方法可能通过缓存频繁请求的值来产生明显更好的空间和时间性能。 此方法将始终缓存 -128 到 127(含) 范围内的值,并可能缓存此范围之外的其他值。
  参数:
    i – 一个整数值。
  返回:代表 i 的 Integer 实例。
  版本开始:1.5。

 
  我们要关注的就是入参为 int 类型的 valueOf(int i) 方法。在 valueOf() 执行过程中,传入的整数会尝试从缓存(IntegerCache 类)中读取对应的数字,IntegerCache.low 代表最小值 -128IntegerCache.high 代表最大值 127 ,如果读取不到才会使用 new Integer(i) 新建一个对象。
 

2.1 IntegerCache

 
  IntegerCache 是 Integer 类中定义的一个内部类。定义如下:

/**
 * Cache to support the object identity semantics of autoboxing for values between
 * -128 and 127 (inclusive) as required by JLS.
 *
 * The cache is initialized on first usage.  The size of the cache
 * may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.
 * During VM initialization, java.lang.Integer.IntegerCache.high property
 * may be set and saved in the private system properties in the
 * sun.misc.VM class.
 */

private static class IntegerCache {
    static final int low = -128;
    static final int high;
    static final Integer cache[];

    static {
        // high value may be configured by property
        int h = 127;
        String integerCacheHighPropValue =
            sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
        if (integerCacheHighPropValue != null) {
            try {
                int i = parseInt(integerCacheHighPropValue);
                i = Math.max(i, 127);
                // Maximum array size is Integer.MAX_VALUE
                h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
            } catch( NumberFormatException nfe) {
                // If the property cannot be parsed into an int, ignore it.
            }
        }
        high = h;

        cache = new Integer[(high - low) + 1];
        int j = low;
        for(int k = 0; k < cache.length; k++)
            cache[k] = new Integer(j++);

        // range [-128, 127] must be interned (JLS7 5.1.7)
        assert IntegerCache.high >= 127;
    }

    private IntegerCache() {}
}

 
  【IntegerCache类注解 - 谷歌翻译】

  缓存以支持 JLS 要求的 -128 和 127(含)之间的值自动装箱的对象标识语义。
 
  缓存在第一次使用时初始化。 缓存的大小可以由 -XX:AutoBoxCacheMax=<size> 选项控制。 在 VM 初始化期间,java.lang.Integer.IntegerCache.high 属性可以设置并保存在 sun.misc.VM 类的私有系统属性中。

 
  javadoc 详细地说明了缓存支持 [-128,127 ] 的自动装箱过程。最大值 127 可以通过 -XX:AutoBoxCacheMax=size 修改。
 
  缓存通过一个 for 循环实现。从高到低创建尽可能多的整数并存储在一个整数数组中。这个缓存会在第一次使用 Integer 类时被初始化出来,,后面就可以在自动装箱的情况下使用缓存中包含的实例对象,而不是创建一个新的实例。
 
  实际在Java 5 中引入时,数值范围是固定的 -128 至 127 ,后来在 Java 6中,可以通过 java.lang.Integer.IntegerCache.high 设置最大值。为什么要选择[-128,127]这个范围的数值呢?因为这范围内的数字被使用最广泛。在程序中,第一次使用 Integer 时也需要一定的额外时间来初始化这个缓存。
 

2.2 其它缓存对象

 
  这种缓存机制适用于所有整数类型的类

  • ByteCache:用于 Byte 对象
  • ShortCache:用于 Short 对象
  • LongCache:用于 Long 对象
  • CharacterCache:用于 Character 对象

 
  Byte、Short、Long 对象都有固定的数值范围:[-128,127]。Character 对象的数值范围:[0,127]。除了 Integer 对象,这个数值范围都不能改变注意】:包装类中只有整型包装类才有缓存机制,浮点数对应的包装类是没有缓存机制的,例如 Float 类和 Double 类
 
  【验证 Float 和 Double 是否存在缓存机制 - 代码】

package cn.zhuangyt.javabase.automatic_boxing_unboxing;

/**
 * Java基础6:自动拆装箱
 * @author 大白有点菜
 */
public class AutomaticBoxingUnboxingApp {
    public static void main(String[] args) {
        Float f1 = 100.0f;
        Float f2 = 100.0f;
        Float f3 = 200.0f;
        Float f4 = 200.0f;

        Double d1 = 100.00d;
        Double d2 = 100.00d;
        Double d3 = 200.00d;
        Double d4 = 200.00d;

        System.out.println("变量f1(" + f1 + ") == 变量f2(" + f2 + ")吗:" + (f1 == f2));
        System.out.println("变量f3(" + f3 + ") == 变量f4(" + f4 + ")吗:" + (f3 == f4));
        System.out.println("------------------分割线------------------");
        System.out.println("变量d1(" + d1 + ") == 变量d2(" + d2 + ")吗:" + (d1 == d2));
        System.out.println("变量d3(" + d3 + ") == 变量d4(" + d4 + ")吗:" + (d3 == d4));
    }
}

 
  【验证 Float 和 Double 是否存在缓存机制 - 代码运行结果】

变量f1(100.0) == 变量f2(100.0)吗:false
变量f3(200.0) == 变量f4(200.0)吗:false
------------------分割线------------------
变量d1(100.0) == 变量d2(100.0)吗:false
变量d3(200.0) == 变量d4(200.0)吗:false

 
  从运行结果可以看到,浮点数包装类(Float和Double)是不存在缓存机制的。
 
  额外补充点知识,Float包装类和Double包装类的对象使用“==”比较是不允许的,在《阿里巴巴Java开发手册》中强制要求浮点数之间的等值判断,基本数据类型不能用 == 来比较,包装数据类型不能用 equals 来判断
 
阿里巴巴的《Java开发手册(嵩山版)》中浮点数之间的等值判断,基本数据类型不能用==来比较,包装数据类型不能用equals来判断 

2.3 基本类型和包装类怎么选

 
  在日常开发中,我们会经常要在类中定义布尔类型的变量,比如在给外部系统提供一个 RPC 接口的时候,我们一般会定义一个字段表示本次请求是否成功。
 
  关于这个“本次请求是否成功”的字段的定义,其实是有很多种讲究和“坑”的,稍有不慎就会掉入“坑”里。到底该如何定一个布尔类型的成员变量。
 
  一般情况下,使用以下2种方式来定义一个布尔类型的成员变量:

boolean success;
Boolean success;

  在定义一个成员变量时,到底是使用 Boolean 包装类更好,还是使用 boolean 基本数据类型更好呢?
 
  在《阿里巴巴Java开发手册》中,对于 POJO 中如何选择变量的类型也有一些规定。如下所示。
 
阿里巴巴的《Java开发手册(嵩山版)》中关于基本数据类型与包装数据类型的使用标准
 
  为什么开发手册中建议使用包装类?先来运行一段简单的代码。

package cn.zhuangyt.javabase.automatic_boxing_unboxing;

import java.util.StringJoiner;

/**
 * Boolean测试
 * @author 大白有点菜
 */
public class BooleanMainTest {
    public static void main(String[] args) {
        Model model = new Model();
        System.out.println("default model:" + model);
    }
}

class Model {
    /**
     * 定义一个 Boolean 类型的 success 成员变量
     */
    private Boolean success;
    /**
     * 定义一个 boolean 类型的 failure 成员变量
     */
    private boolean failure;

    /**
     * 覆盖 toString 方法,使用 Java 8 的 StringJoiner
     * @return
     */
    @Override
    public String toString() {
        return new StringJoiner(",", Model.class.getSimpleName() + "[", "]")
                .add("success=" + success)
                .add("failure=" + failure)
                .toString();
    }
}

 
  以上代码运行结果:

default model:Model[success=null, failure=false]

 
  由结果可以看到,当没有设置Model对象的字段的值时,Boolean 类型的变量会设置默认值为 null ,而 boolean 类型的变量会设置默认值为 false ,即对象的默认值为 null ,布尔基本数据类型的默认值为 false 。
 
  也就是说,包装类的默认值都是 null ,而基本数据类型的默认值是一个固定值。如 boolean 是 false ;byte、short、int、long 是 0 ;float 是 0.0f ;double 是 0.0d
 
  举一个扣费系统的例子,扣费时需要从外部的定价系统的接口中读取一个费率的值,该接口的返回值中会包含一个浮点型的费率字段,扣费结果计算:金额 x 费率 = 费用 。
 
  如果计费系统异常,则可能返回一个默认值。如果这个字段是 Double 类型,则默认值为 null ;如果字段是 double 类型,则默认值为 0.0 。
 
  如果扣费系统对该费率的返回值没有做特殊处理,则获取 null 进行计算时会直接报错,阻断程序。如果获取的是 0.0 ,则可能直接计算,得出结果为 0 后进行扣费了。在这种情况下,系统是无法被感知的。
 
  有人说,可以对 0.0 做特殊处理,如果是 0 ,那么一样可以阻断报错。但是,这时就会产生一个问题,如果是允许费率为 0 的场景,那么又怎么处理呢?我们就无法识别出这个 0 是正常返回的还是异常返回的。使用基本数据类型会存在很多“坑”。
 
  使用包装类定义变量的方式,通过异常来阻断程序,进而可以识别出线上问题。如果使用基本数据类型,则系统可能不会报错,进而认为无异常。
 
  额外补充点知识,在《阿里巴巴Java开发手册》中强制要求POJO 类中的任何布尔类型的变量,都不要加 is 前缀,否则部分框架解析会引起序列化错误
 
阿里巴巴的《Java开发手册(嵩山版)》中POJO 类中的任何布尔类型的变量,都不要加 is 前缀,否则部分框架解析会引起序列化错误
 

  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大白有点菜

你的鼓励决定文章的质量

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值