JavaSE 第二次学习随笔(String的坑 + ==)

String 类是一个final类, 其内部是使用的 private final char value[]; 来存储内容, 其既可以当作一个基本类型来使用也可以当作一个类来使用;
final 类(String): 被final修饰的类将不能被继承
final char value[]: 不能修改String类型的对象的内容, 但是我可以修改他的引用指向啊~

"==" 作用 : 判断引用是否指向堆内存的同一块地址

equals()的作用: 比较是否值相等(先比较两个String的地址然后用instanceof比较类型, 然后遍历两个String的value[] 挨个值比较)

hashcode()的作用: 根据String中的value[]计算出的散列值, 和它的地址一丁点毛关系都没有-_-|

 

equals();

/**
     * 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;
    }

 

hashCode();

/**
     * Returns a hash code for this string. The hash code for a
     * {@code String} object is computed as
     * <blockquote><pre>
     * s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
     * </pre></blockquote>
     * using {@code int} arithmetic, where {@code s[i]} is the
     * <i>i</i>th character of the string, {@code n} is the length of
     * the string, and {@code ^} indicates exponentiation.
     * (The hash value of the empty string is zero.)
     *
     * @return  a hash code value for this object.
     */
    public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

  

 

针对String作为一个基本类型来使用:
 
1。如果String作为一个基本类型来使用,那么我们视此String对象是String缓冲池所拥有的。
2。如果String作为一个基本类型来使用,并且此时String缓冲池内不存在与其指定值相同的String对象,那么此时虚拟机将为此创建新的String对象,并存放在String缓冲池内。
3。如果String作为一个基本类型来使用,并且此时String缓冲池内存在与其指定值相同的String对象,那么此时虚拟机将不为此创建新的String对象,而直接返回已存在的String对象的引用。
 
针对String作为一个对象来使用:
 
1。如果String作为一个对象来使用,那么虚拟机将为此创建一个新的String对象,即为此对象分配一块新的内存堆,并且它并不是String缓冲池所拥有的,即它是独立的。

关于String的各种==判定相等问题:

new 的对象一定是两块内存() 

final String s1 = "123";这时 s1 已经可以当作一个常量(等同于 ["123"] ) 使用jvm对String的常量优化对它起作用(后边有例子)

* 对于Integer来说, 数字范围在在一个字节范围内  
* 后边再定义它,直接使用前边的        (Integer i = new Integer(100); Integer j = new Integer(100); i==j;-->>true )
* 超过一个字节其会新建对象          (Integer i = new Integer(129); Integer j = new Integer(129); i==j;-->>true )

Character ch1 = '1';			
Character ch2 = '1';
System.out.println("'1' == '1' " + (ch1 == ch2) );				//true
ch1 = new Character('1');
System.out.println( "new Character('1') == '1' " + (ch1 == ch2));		//false
System.out.println("new Character('1').equals('1') " + ch1.equals(ch2));     //true
		
Character ch3 = '我';
Character ch4 = '我';
System.out.println("'我' == '我' " + (ch3 == ch4));				//true
ch3 = new Character('我');
System.out.println("new Character('我') == '我' " + (ch3 == ch4));		//true
System.out.println("new Character('我').equals('我') "+ch3.equals(ch4));	     //true


 

//这里由于Character是char的包装类, char是整数类型表示的所以在128前的 '1' 在常量池里 ch1 ch2 指向一个(常量池里的)对象, 而 '我' 则不在常量池里, ch3 ch4分别指向不同的对象
//关于常量池, 下面有详细的解释

 

String s1 = “abc”; 
final String s2 = “a”; 
final String s3 = “bc”; 
String s4 = s2 + s3; 
System.out.println(s1 == s4); 
输出true,因为final变量在编译后会直接替换成对应的值,所以实际上等于s4=”a”+”bc”,而这种情况下,编译器会直接合并为s4=”abc”,所以最终s1==s4。

(摘自 https://blog.csdn.net/soonfly/article/details/70147205 后面多点理解String.intern()是摘抄于此)

 

String s1 = new String("1234");
String s2 = new String("1234");
System.out.println(s1 == s2);  //false
String s3 = "1234";
String s4 = "1234";
System.out.println(s3 == s4); //true
String s1 = new String("1234");
String s2 = new String("12345");
s1+="5";
System.out.println(s1 == s2);//false
String s1 = "12345";
String s2 = "1234" + "5";
String s3 = "1234";
s3+=5;
System.out.println(s1 == s2);//true
System.out.println(s1 == s3);//false
String s1 = new String("12345");
String s2 = new String("1234") + "5";
System.out.println(s1 == s2);//false
String s2 = "1234" ;
String s3 = s2 + "5";
System.out.println(s1 == s3);//false

String.intern();作用: (jdk1.7后)

  一、new String都是在上创建字符串对象。当调用 intern() 方法时,编译器会将字符串添加到常量池中(stringTable维护),并返回指向该常量的引用

  二、通过字面量赋值创建字符串(如:String str=”twm”)时,会先在常量池中查找是否存在相同的字符串,若存在,则将栈中的引用直接指向该字符串;若不存在,则在常量池中生成一个字符串,再将栈中的引用指向该字符串

  三、常量字符串的“+”操作,编译阶段直接会合成为一个字符串。如string str=”JA”+”VA”,在编译阶段会直接合并成语句String str=”JAVA”,于是会去常量池中查找是否存在”JAVA”,从而进行创建或引用

  四、对于final字段,编译期直接进行了常量替换(而对于非final字段则是在运行期进行赋值处理的)

  五、常量字符串和变量拼接时(如:String str3=baseStr + “01”;)会调用stringBuilder.append()在堆上创建新的对象

  六、JDK 1.7后,intern方法还是会先去查询常量池中是否有已经存在,如果存在,则返回常量池中的引用,这一点与之前没有区别,区别在于,如果在常量池找不到对应的字符串,则不会再将字符串拷贝到常量池,而只是在常量池中生成一个对原字符串的引用。简单的说,就是往常量池放的东西变了:原来在常量池中找不到时,复制一个副本放到常量池,1.7后则是将在堆上的地址引用复制到常量池.  ( https://blog.csdn.net/soonfly/article/details/70147205 )

  为了加深我自己的印象(万一大佬找上门来, 怂的不行ing), 大佬的图我就不复制了 下边是我自己画哒, 画图真累...

 

 

举例说明(不错这是大佬写的, 人家写的非常好我就复制过来了还是上边那个地址)

 

String str2 = new String("str")+new String("01");
str2.intern();
String str1 = "str01";
System.out.println(str2==str1);

在JDK 1.7下,当执行str2.intern();时,因为常量池中没有“str01”这个字符串,所以会在常量池中生成一个对堆中的“str01”的引用(注意这里是引用 ,就是这个区别于JDK 1.6的地方。在JDK1.6下是生成原字符串的拷贝),而在进行String str1 = “str01”;字面量赋值的时候,常量池中已经存在一个引用,所以直接返回了该引用,因此str1和str2都指向堆中的同一个字符串,返回true。

 

String str2 = new String("str")+new String("01");
String str1 = "str01";
str2.intern();
System.out.println(str2==str1);

  将中间两行调换位置以后,因为在进行字面量赋值(String str1 = “str01″)的时候,常量池中不存在,所以str1指向的常量池中的位置,而str2指向的是堆中的对象,再进行intern方法时,对str1和str2已经没有影响了,所以返回false。

 

有了对以上的知识的了解,我们现在再来看常见的面试或笔试题就很简单了: 
Q:下列程序的输出结果: 
String s1 = “abc”; 
String s2 = “abc”; 
System.out.println(s1 == s2); 
A:true,均指向常量池中对象。

Q:下列程序的输出结果: 
String s1 = new String(“abc”); 
String s2 = new String(“abc”); 
System.out.println(s1 == s2); 
A:false,两个引用指向堆中的不同对象。

Q:下列程序的输出结果: 
String s1 = “abc”; 
String s2 = “a”; 
String s3 = “bc”; 
String s4 = s2 + s3; 
System.out.println(s1 == s4); 
A:false,因为s2+s3实际上是使用StringBuilder.append来完成,会生成不同的对象。

Q:下列程序的输出结果: 
String s1 = “abc”; 
final String s2 = “a”; 
final String s3 = “bc”; 
String s4 = s2 + s3; 
System.out.println(s1 == s4); 
A:true,因为final变量在编译后会直接替换成对应的值,所以实际上等于s4=”a”+”bc”,而这种情况下,编译器会直接合并为s4=”abc”,所以最终s1==s4。

Q:下列程序的输出结果: 
String s = new String(“abc”); 
String s1 = “abc”; 
String s2 = new String(“abc”); 
System.out.println(s == s1.intern()); 
System.out.println(s == s2.intern()); 
System.out.println(s1 == s2.intern()); 
A:false,false,true。

  

 

 

由于它其实是由char[] 实现的所以我们来看看它的构造方法以便了解它的char[] 到底指向了哪里

这是其中的三个构造方法,

1. 我们可以看到 当输入的是一个String类型的时候, 他的value和hashcode 是直接赋值过来的 所以毫无疑问这时 两个new String("123") 的char[]是一个对象,
2. 我们还看到单纯的new String()就是个"",
3. 当new String(char[]); 时,String类会new 一个char[] 然后把你的char[] 复制过来(Arrays.copyOf(value, value.length)会返回一个new char[]),所以这里应该是两个char[] 对象 那么两个String对象肯定不同了.
4.String 类对于它内部的char[]的一个原则就是坚决不让你得到它的引用, 任何你能接触到的关于它的char方法返回的要么是一个copy的char[] 要么就是一个char;这么做主要是为了保证String 的 final性 以保证它在多线程情况下程序的稳定(我猜的, 嘿嘿).

/**
     * Initializes a newly created {@code String} object so that it represents
     * an empty character sequence.  Note that use of this constructor is
     * unnecessary since Strings are immutable.
     */
    public String() {
        this.value = "".value;
    }

    /**
     * Initializes a newly created {@code String} object so that it represents
     * the same sequence of characters as the argument; in other words, the
     * newly created string is a copy of the argument string. Unless an
     * explicit copy of {@code original} is needed, use of this constructor is
     * unnecessary since Strings are immutable.
     *
     * @param  original
     *         A {@code String}
     */
    public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }

    /**
     * Allocates a new {@code String} so that it represents the sequence of
     * characters currently contained in the character array argument. The
     * contents of the character array are copied; subsequent modification of
     * the character array does not affect the newly created string.
     *
     * @param  value
     *         The initial value of the string
     */
    public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }

 

posted @ 2018-08-04 11:43  一根咸鱼干  阅读(351)  评论(0编辑  收藏  举报