浅谈Java中的自动包装机制

不可变对象

引入概念

在Java中,除了基本类型以外,其他都是以对象的形式存在。而基本类型也有其对应的类,这些类被称为包装器(Wrapper)。例如

基本类型 对应的类
int Integer
double Double
以下 同理

这些类就属于不可变对象

解释

通常我们创建一个类时,会设置相应的get和set方法来对成员进行修改。但是对于不可变对象而言,内部成员的值是不能被修改到的,所以被称为不可变

对于用户自定义的类而言,若想设计成不可变对象,可以借助private或者final关键字防止使用者来修改对象内部的值。

对于Integer这些对象包装器来说,他们不能够被继承,也不能修改内部的值。(因为他们其实就是final类)例如,调用构造函数: new Integer(1) 来实例化一个内部保存了一个int=1的Integer对象,这个对象内部的值再也不能被修改了。这样做的好处是,将Java里的所有基本类型都统一为了对象,而且可以将一些工具类放入包装器中。所以当我们想定义一个整数数组列表时,因为<>尖括号中是不允许是基本类型的,这时候就可以使用Integer对象包装器类。但是在数组列表中,调用list.add()方法时,可以直接传入一个整数类型int,这是因为Java中存在有自动包装机制。

自动包装机制

拆包和自动装箱

借着上面的那个例子,例如调用如下代码

ArrayList<Integer> list = new ArrayList<Integer>();
list.add(2);

实际上第二句话相当于

list.add(new Integer(2));

这里就发生了一个自动装箱过程(Autoboxing)

由于Integer是不可变对象,那么如果我们调用如下语句的时候,会发生什么呢?

Integer n = 3;
Integer m = 2;
n++;
m+n;

第一句话,等号右边是个int基本类型,所以这里先进行自动装箱,将3变成一个Integer类。
但是在第二句话中,由于Integer不能参与运算,这里会发生拆箱动作,将Integer类变成基本类型int参与运算,执行自增动作,在运算结束之后,再自动装箱
拆箱动作会发生在Integer类型被当作int使用时(例如:打印操作)。
接下来在第三句中,两个Integer类型要发生运算关系,所以两个Integer都会先拆箱变为基本类型相加,然后结果再打包。

关于自动装箱还有几点需要说明

  1. 由于包装器类的引用可以为null,所以在拆箱的时候会抛出NullPointerException
  2. 在一个条件表达式中混合使用Integer和Double类型,Integer值会拆箱,提升为double,再装箱为Double

常量池

Java在栈里设计了一个静态常量池,里面已经准备好了一些会经常用到的数字(0~127)或者字符串(比如"abc"),所以当实例化对象的值在常量池里有的时候,Java不会去堆中再开辟新的空间,而是直接指向栈内的那个值。

包装类的创建方式

==常量化赋值==

Integer a = 10;

放在栈内存储,将被常量化,相当于在栈内开辟一个对象给a,但是由于a是10,是被常量化的 直接指向栈里那个10,以后是凡10的也都指同一个地址。但是如果是128那些就要自己重新在栈里开辟了,因为他们是常量池之外的。

==new对象创建==

Integer c = new Integer(10);

放在堆内,不会被常量化, 就算是10,也不能指到栈里那个10去。因为是再堆中重新开辟的。

代码

下面是一段测试代码,涉及到了常量池和自动装箱机制:

public class BoxClassTest {

    public static void main(String[] args) {
        
        int i1 = 10;
        Integer i2 = 10;                //自动装箱     
        
        System.out.println(i1 == i2);  //true
        //i2会被自动拆箱比大小
        //Ps. 
        //在两边都是对象的时候 == 是比较地址
        //两边都是基本类型的时候 == 是比较值
        //当有有一边是对象,有一边是基本类型时,会自动拆箱比较。

        Integer i3 = new Integer(10);   //在堆中开辟一块空间
        System.out.println(i1 == i3);   //true
        //虽然i3在堆里 但是直接拆箱比大小
        
        System.out.println(i2 == i3);  //false
        //两个都是Integer,一个的地址在栈里一个的地址在堆里
        
        Integer i4 = new Integer(5);
        Integer i5 = new Integer(5);
        System.out.println(i1 == (i4+i5));  //true
        System.out.println(i2 == (i4+i5));  //true
        System.out.println(i3 == (i4+i5));  //true
        //i4+i5的操作会暴力拆箱 括号那一团就变成基本类型了 
        //第三个:对象比基本类型,自动给对象拆箱

        Integer i6 = i4 + i5;   //i4+i5会自动拆箱 也就是 i6 = 10,然后再自动装箱
        System.out.println(i1 == i6);  //true  //拆箱操作
        System.out.println(i2 == i6);  //true  //一样的
        System.out.println(i3 == i6);  //false //一个在栈里一个在堆里 
    }
}

String类

String类的自动包装区别

语言描述有点难,直接看代码和注解吧:

public class StringConstantTest {

    public static void main(String[] args) {
        
        String s1 = "abc";
        String s2 = "abc";
        String s3 = "ab"+"c";
        String s4 = "a"+"b"+"c";
        System.out.println(s1==s2);   //只要后面都是常数 就直接被编译器当成abc
        System.out.println(s1==s3);   //所以这里三个全是true
        System.out.println(s1==s4);   //true
        //对于String来说,如果你是给的一堆常量,Java会自动把他们包装起来
        //所以上面的全都是abc
        
        String s5 = "c";
        String s6 = "ab" + s6;
        System.out.println(s1==s6);   //false
        //但是比如你相加的东西里面有个变量,那么编译器就无法识别出来了
        //他就不能帮你直接组合好了
        //所以他会在代入s3的值之后在堆内重新开辟一块地址
        //所以这里的abc就不是和常量池里的是一个地址了   
    }
}

String的缺陷

以这段代码为例:

public class StringTest {
    public static void main(String[] args) {
        String str = "a";
        str = "ab";
    }
}

由于String也是一个不可变对象,所以每次改变String引用变量的时候,我们并没有去修改实际字符串的内容,而是重新指向了新的一个对象。


在这里插入图片描述

显然,单是为了增加一个b字母,我们又重新创建了一个新的对象,而且原先的a还被闲置了(虽然会被回收),这样的效率太低了,所以,Java里提供了另外两个操作字符串的可变对象类。

StringBuffer和StringBuilder

StringBuffer:线程安全,但是速度没用StringBuilder快。
StringBuilder:线程没那么安全,但是快些。

使用方法如下:

    StringBuffer str = "a";
    for(int i = 0;i<3;i++)
    str.append("b");    
    System.out.println(str);

这时结果输出的str就是abbb了,而且没用创建新的对象。同理StringBuilder。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,002评论 6 509
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,777评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,341评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,085评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,110评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,868评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,528评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,422评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,938评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,067评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,199评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,877评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,540评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,079评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,192评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,514评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,190评论 2 357

推荐阅读更多精彩内容