Java学习笔记--String

String特点

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

下面开始上题

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出来的不同对象的地址,图形理解如下

而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没有被改变的原因。

你可能感兴趣的:(java,String,对象,内存,存储)