Java源码分析——String、AbstractStringBuilder、StringBuffer、StringBuilder类(一)——String类、intern方法

    在Java中,与C++一样,也是亲自封装了字符串,命名为String类,用来对字符串的常用操作,其中Java也仿照String类设计出了其它的几个字符串类,用来处理String类不能处理的场景,分别是StringBuffer类与StringBuilder类,下面将通过源码来详细的讲解它们之间的相同与不同处。

String类

    String类是一个不可被继承的类,也是一个不可变类,当其内容改变的时候,都会在静态常量池中创建一个对象。它实现了Serializable、Comparable、CharSequence三个接口,用来分别实现序列化、字符串的比较以及字符序列的。在Java中,所有的像如下定义的字符串都被默认为是String对象:

String a="asdf";
StringBuffer b="asdf" 2

    在定义二式后,编译器会自动提醒如下错误:

Required: java.lang.StringBuffer  Found: java.lang.String

    这也从侧面证明了静态的字符串是String对象。而且String类分为两种情况,一种是在jvm中存贮在常量池中,实现对象的共享机制,也就是上面描述的;而另外一种是存贮在堆上的,就是新创建一个对象,下面来看看这个例子:

public class Test {
    public static void main(String args[]) {
       String a="hello";
       String b="hello";
       String c=new String("hello");
       System.out.println(a==b);  //true
       System.out.println(b==c);  //false
    }
}

    如上面的代码结果所示,静态定义的字符串如果首次出现会将其放入常量池中,后出现的静态字符串如果值相同,则不会重新新建一个对象,而是新建一个引用,来指向常量池中与其相同内容的地址,也就是说两者是指向同一个地址,同一个内容。而采用构造器来创建String对象时,也会检查是否常量池中有没有,如果有则直接创建堆空间,并将引用指向它,如果没有则会将其放入常量池,再创建堆空间。另外注意的是,’+'号在拼接String对象时也有着两种策略,如下代码所示:

public class Test {
    public static void main(String args[]) {
       String a="hello";
       String b="he"+"llo";
       String c="he"+new String();
       System.out.println(a==b);  //true 1式
       System.out.println(b==c);  //false 2式
    }
}

    jvm在处理1式的时候,会自动的把"he"+“llo"直接为"hello”,所以1式的结果是true,而针对"he"+new String(),jvm并不知道将其转化为什么类型的值,则就直接新创建一个String对象,用来存贮该值,所以为false。而String类也提供了两种方法用来实现StringBuffer与StringBuilder类转化为String类,其中转化StringBuffer是线程安全的,加了同步方法:

public String(StringBuffer buffer) {
        synchronized(buffer) {
            this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
        }
    }

public String(StringBuilder builder) {
        this.value = Arrays.copyOf(builder.getValue(), builder.length());
    }

    另外一个String的equals方法则是先判断是否地址相同,然后在判断是否是String类的对象,在比较其中的每个字符。我们需要注意的是String类里面的唯一一个本地方法,intern方法:

public native String intern();

    在jdk 1.6以前,该方法返回一个常量池中的String对象的引用,假如该常量池中没有与实例相同值的String对象,则将该String对象实例值复制一遍并新建String类对象放入常量池,如果有,则返回常量池中对应的String对象的引用。而在jdk 1.7以后,当常量池中没有对应类的String对象时,jvm则不会复制实例,而是在常量池中记录第一次实例出现的地址,并返回该地址的引用,如果有,则返回对应常量池中String类的引用。我们用深入理解Java虚拟器的例子来讲解(基于jdk 1.8):

public class Test {
    public static void main(String args[]) {
        String a = "123";
        System.out.println(a.intern() == a); //true

        String s = new String("ddd");
        System.out.println(s.intern() == s);//false

        String s1 = new String(new StringBuilder("计算机").append("软件"));//true
        System.out.println(s1.intern() == s1);


        String s2 = new StringBuilder("ja").append("va").toString();
        System.out.println(s2.intern() == s2);//false

        String s3 = new String(new StringBuilder("计算机软件"));//true
        System.out.println(s1.intern() == s1);

    }
}

    对于静态字符串a来说,在定义的时候就会被放到静态的常量池中,所以它的地址与常量池中的地址是相同的,所以是true;对于s字符串而言,是一个考验仔细阅读的能力的题,很多人会用jdk 1.7以后的解释来解释这个情况,但得出的答案是true,但其结果却是false,那是因为没有注意到:

String s = new String("ddd");

    中的"ddd","ddd"这本身就是一个静态的字符串,也就是上文说采用new创建String类会先检查需要被创建的值有无在常量池中出现过。第一次出现会将"ddd"的地址放入静态常量区,而String方法的构造器只是另外的复制了其值,并开辟了另外的一块空间把值放进去,这里是第二次出现,而不是第一次,静态常量区中保存的是"ddd"的地址,所以是false。对于s1来说,因为是append操作,所以返回的是一个全新的String类对象,所以是true,对比s3,可验证这个正确性。而对于s2来说,"java"这个字符串本身就是静态常量池中的对象,所以是false。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值