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

来源:互联网 发布:大数据开发工程师简历 编辑:程序博客网 时间:2024/06/05 05:36

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.NullPointerExceptionat 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));   }

    三组测试的结果分别是:

    truetruefalsetruefalsetrue

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

阅读全文
0 0
原创粉丝点击