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); }