Java学习笔记--String

String特点

String是不可变的常量,每当我们创建一个字符串对象的时候,如果堆区的常量池里不存在这个字符串,就会创建一个存储在常量池里(String存的地方叫String pool),如果存在了,就直接把变量的地址指向常量池里,比如:String b = “abc”; 这句话的内存表示如下。
String内存信息

下面开始上题

1、以下代码的输出结果是什么呢?

String s1 = new String("abc");
String s2 = new String("abc");
System.out.println(s1 == s2);
System.out.println(s1.equals(s2));

String ss1 = "aaa";
String ss2 = "aaa";
System.out.println(ss1 == ss2);
System.out.println(ss1.equals(ss2));

从上面的图也大概知道了JVM里面有栈区、堆区。栈区里面主要存放的是局部变量,堆区里存放的是new出来的对象。而对于对象类型比较的是地址。s1和s2是分别引用了堆里面new出来的不同对象的地址,图形理解如下
String内存信息

而ss1和ss2则因为字符串内容一样,所以指向相同的地址。
答案很明显:

false
true
true
true

2、以下代码的输出结果是什么呢?

String s1 = "abc";
StringBuffer s2 = new StringBuffer(s1); 
System.out.println(s1.equals(s2));

这是true,还是false呢?答案是false。

首先s1变量引用了字符串”abc”,然后StringBuffer s2 = new StringBuffer(s1),新建了一个StringBuffer对象调用append()方法返回自身。调用String的equals方法。重点就是这个equals方法里有个instance of,必须是同一类型的才进行比较否则直接返回false。
来看一下源码:

/**
 * Compares this string to the specified object.  The result is {@code
 * true} if and only if the argument is not {@code null} and is a {@code
 * String} object that represents the same sequence of characters as this
 * object.
 *
 * @param  anObject
 *         The object to compare this {@code String} against
 *
 * @return  {@code true} if the given object represents a {@code String}
 *          equivalent to this string, {@code false} otherwise
 *
 * @see  #compareTo(String)
 * @see  #equalsIgnoreCase(String)
 */
public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    //关键点就在这里了
    if (anObject instanceof String) {
        String anotherString = (String) anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                        return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

3、下面的代码在内存会产生几个对象呢?

String s1 = new String("abc"); 
String s2 = new String("abc");

答案:3个
有了上面的分析,相信大家都明白了,new了两个对象,加上string pool里的一个”abc”。

4、下面的代码输出结果是啥?

String s1 = "abc";
String s2 = new String("abc");
s2.intern();
System.out.println(s1 ==s2);

我们可能对intern()这个方法不太熟悉,先来看看注释:

/**
 * Returns a canonical representation for the string object.
 * <p>
 * A pool of strings, initially empty, is maintained privately by the
 * class <code>String</code>.
 * <p>
 * When the intern method is invoked, if the pool already contains a
 * string equal to this <code>String</code> object as determined by
 * the {@link #equals(Object)} method, then the string from the pool is
 * returned. Otherwise, this <code>String</code> object is added to the
 * pool and a reference to this <code>String</code> object is returned.
 * <p>
 * It follows that for any two strings <code>s</code> and <code>t</code>,
 * <code>s.intern()&nbsp;==&nbsp;t.intern()</code> is <code>true</code>
 * if and only if <code>s.equals(t)</code> is <code>true</code>.
 * <p>
 * All literal strings and string-valued constant expressions are
 * interned. String literals are defined in section 3.10.5 of the
 * <cite>The Java&trade; Language Specification</cite>.
 *
 * @return  a string that has the same contents as this string, but is
 *          guaranteed to be from a pool of unique strings.
 */
public native String intern();

注释很多,关键的是这句:When the intern method is invoked, if the pool already contains a string equal to this String object as determined by the {@link #equals(Object)} method, then the string from the pool is returned. Otherwise, this String object is added to the pool and a reference to this String object is returned.
大致就是说,如果常量池里不存在这个字符串,就创建一个并且返回地址,否则的话直接返回地址。

上面的代码第二行String s2 = new String(“abc”); s2其实是引用到了new的对象,虽然在第三行调用了intern方法,但是没有赋值给s2,所以s2的引用还是没有变。所以返回false。
如果第三行代码改成s2 = s2.intern()就会返回true了。

String s1 = "abc";
String s2 = new String("abc");
s2 = s2.intern();
System.out.println(s1==s2);

5、由上所知Java中的String对象是不可变的,但我们来看下面这段代码:

String s1 = "Hello World";  
String s2 = "Hello World";  
String s3 = s1.substring(6);  
System.out.println(s1); // Hello World  
System.out.println(s2); // Hello World  
System.out.println(s3); // World  

Field field = String.class.getDeclaredField("value");  
field.setAccessible(true);  
char[] value = (char[])field.get(s1);  
value[6] = 'J';  
value[7] = 'a';  
value[8] = 'v';  
value[9] = 'a';  
value[10] = '!';  

System.out.println(s1); // Hello Java!  
System.out.println(s2); // Hello Java!  
System.out.println(s3); // World

为什么这段代码会是这样的运行结果?为什么s1和s2的值被改变了,但是s3的值却没有?

String对象是不可变的,但这仅意味着你无法通过调用它的公有方法来改变它的值。

上面的代码通过反射机制绕过了正常的API。采用这种方式,你还可以改变枚举的值,甚至可以改变Integer类型自动装箱时使用的查找表。

在这里,由于s1和s2指向同一个内部的字符串对象,因此他们的值都被改变了。正如其他回答所述,这是由编译器实现的。

s3没有被改变的原因确实令我诧异,我过去一直认为s3和s1共享同一个value数组(在Java 7u6之前的版本中的确是这样)。然而,通过查看String类的源码,我们可以看出子串对象的value数组是从原字符串对象中拷贝得到的(通过使用Arrays.copyOfRange(..)方法)。这是s3没有被改变的原因。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值