《Java核心技术》第10版读书笔记之Chap5(4)——基本数据类型的Wrapper类、自动拆箱与装箱及过程中的坑

Java语言中虽然提供了byte、short、int、long、char、boolean、float、double这些基本数据类型,但由于他们并不是对象,因此有些操作会受到限制,比如不能将其存入容器中等等。为此Java在java.lang包中对于上述每一种基本类型都提供了一个对应的封装类(Wrapper),这些包装类中有一个不可变的对应基本类型的成员变量(也意味着一旦设定就没法更改了)并提供了若干方法。

这里写图片描述

仍然以Integer为例,看看Wrapper类型都提供了哪些功能:

1.字符串与基本数据类型的互转:XX.parseXX方法与XX.toString(XX)静态方法

这里写图片描述

需要注意的是,待转换的字符串必须符合对应类型的规定,否则在运行期会抛出解析错误的异常:

    public static void main(String[] args) {
        int n1 = Integer.parseInt("666666");
        int n2 = Integer.parseInt("-666666");
        int n3 = Integer.parseInt("  2333333");
        System.out.println(n1);
        System.out.println(n2);
    }

在这段代码中,n1和n2能被正常解析出来,但在执行Integer.parseInt(” 2333333”)时,由于输入的字符串中包含空格,其不能解释为数字,所以发生异常:

Exception in thread "main" java.lang.NumberFormatException: For input string: "  2333333"
    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
    at java.lang.Integer.parseInt(Integer.java:569)
    at java.lang.Integer.parseInt(Integer.java:615)
    at CMain.main(CMain.java:11)

此外,Integer类还有一个重载的parseInt方法,输入的是字符串和进制数,字符串里的内容会被按照对应的进制数解析,如下列代码的运行结果就为16:

    public static void main(String[] args) {
        int n1 = Integer.parseInt("1100", 2);
        System.out.println(n1);
    }

另外,将基本数据类型转换为字符串的方法之前的文章已经提到过了,直接用”” + 的方式即可,即用空串连接上一个基本数据类型即可。当然,也可以选择使用Integer.toString(int)方法,该方法为静态方法,会返回对应基本类型的字符串表示。

    public static void main(String[] args) {
        String s1 = Integer.toString(23);
        String s2 = Integer.toString(23, 2);
        String s3 = "" + 233;

        System.out.println(s1);
        System.out.println(s2);
        System.out.println(s3);
    }
//对应的结果分别是:
//23
//10111
//233

对于Integer类型,还有如下静态方法可供调用:

  • 进制转换toBinaryString(int)方法
  • toHexString(int)
  • toOctalString(int)

2.构造方法

以int类型对应的Integer封装类为例,其源码中有如下片段:

    public final class Integer extends Number implements Comparable<Integer> {   
       //......
       private final int value;

       public Integer(int value) {
           this.value = value;
       }
       //......
       public Integer(String s) throws NumberFormatException {
           this.value = parseInt(s, 10);
       }
   }

从中可以看出如下几点:

  1. Wrapper类为final类型
  2. 其中封装的基本类型也标明为final,意味着对象一旦创建,其表征的值就不能再被更改了
  3. 可以用基本数据类型或字符串的方式构建一个对应的Wrapper对象

3.intValue方法:返回封装的基本数据类型。

   public static void main(String[] args) {
           Integer n1 = new Integer("23333");
           int n = n1.intValue();
           System.out.println(n);
   }

4.MAX_VALUE与MIN_VALUE静态成员变量:最大值与最小值

这一点没有太多要说的,直接看Integer类的源码就知道了:

@Native public static final int   MIN_VALUE = 0x80000000;
@Native public static final int   MAX_VALUE = 0x7fffffff;

5.自动装箱与自动拆箱——带着坑的语法糖

  • 自动装箱:将基本数据类型赋值给Wrapper类,下面两条语句其实是等价的。
  • 自动拆箱:将Wrapper类中的数据返回给基本数据类型

       public static void main(String[] args) {
           Integer n1 = Integer.valueOf(333);
           Integer n2 = 333;
       }

正是因为有了javac编译器的自动装拆箱,才使得我们可能写出这样的代码:

ArrayList arrayInteger = new ArrayList<Integer>;
arrayInteger.add(13);
Integer n1 = 32;
n1 = n1 + 233;

这里的13在加入arrayInteger中时会自动装箱,成为Integer类型。同理,n1也经过了自动装箱,成为了Integer类型。而当执行n1 + 233操作时,有需要对n1进行自动拆箱,加法运算结束后再通过自动装箱重新变回了Integer类型。

但是也需要注意,这种自动装拆箱机制是有弊端的:

  1. 每一次装箱都伴随着对象的创建,会耗费时空资源

  2. 对null的Wrapper类对象执行自动拆箱,会引发空引用异常。所以,尽量不要把Wrapper类对象引用设为null,宁可让其值为0。

    Integer n3 = null;
    n3 = n3 + 233;
    /*上述代码执行时将引发如下异常:
    Exception in thread "main" java.lang.NullPointerException
    at CMain.main(CMain.java:12)*/
  3. Wrapper类型对象的值缓存机制

    Integer类的valueOf静态成员方法源码如下:

    public static Integer valueOf(int i) {
           if (i >= IntegerCache.low && i <= IntegerCache.high)
               return IntegerCache.cache[i + (-IntegerCache.low)];
           return new Integer(i);
       }

    而其中的IntegerCache是Integer类的内部类。

    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() {}
       }

    可以看出,在该类中确实有缓存,且该缓存至少覆盖了[-128, 127]这个区间范围。所以,当自动装箱机制使用valueOf()方法时,在该区间范围内的数变会使用缓存里的实例,返回的也是这些缓存实例的引用。但是要注意,如果不使用自动装箱机制,而是直接new Integer(2)之类的方法创建对象,则不会有任何的缓存机制。

    这点我印象十分深刻:曾经在做一个项目的时候,没有看清楚其Bean类中用的是Integer而非int,结果单测时用的数据是小于127的,没有发现问题。上线后,使用一段时间后用户反馈出现问题,检查后分析就是栽在了这个所谓的缓存机制上。

      public static void main(String[] args) {
           Integer n1 = 127;
           Integer n2 = 127;
           System.out.println(n1 == n2);
           System.out.println(n1.equals(n2));
    
           System.out.println("---------------------------------");
    
           Integer n3 = 128;
           Integer n4 = 128;
           System.out.println(n3 == n4);
           System.out.println(n3.equals(n4));
    
           System.out.println("---------------------------------");
    
           Integer n5 = new Integer(127);
           Integer n6 = new Integer(127);
           System.out.println(n5 == n6);
           System.out.println(n5.equals(n6));
       }

    三组测试的结果分别是:

    true
    true
    
    false
    true
    
    false
    true

    所以,在Java中,对127以内的Byte、Short、Integer、Long对应的基本数据类型进行自动装箱,会从内建的缓存数组中引用同一个对象,而从128开始则不再使用这种缓存机制。另外这种缓存机制仅限于自动装箱的情况,对于显示new出的对象则无此机制。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值