==和equals的区别

==对于基本数据类型来说,是用来比较"值"是否相等。对于引用类型来说是用来比较引用地址是否相同。

对于equals来说该方法属于Object类,其内部实现就是==。但是String重写了equals方法,所以String中的equals是比较两个值是否相等。

public boolean equals(Object anObject) {
    // 对象引用相同直接返回true
    if (this == anObject) {
        return true;
    }
    // 如果比较的值是String类型,则进行值的比较
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            // 把两个字符串转成cahr数据对比
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            // 循环对比两个字符串的每一个字符
            while (n-- != 0) {
                // 如果其中有一个字符不相等就返回false
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            // 所有字符都相等返回true
            return true;
        }
    }
    // 否则直接返回false
    return false;
}

为什么String类使用final修饰

使用final修饰之后String就不能被继承,内部的字符数组被final修饰使得String不可变。好处:安全和高效。

  • 在传参时不用担心被修改结果,如果是可变类那么可能需要重新拷贝一个新值出来进行传参性能有一定的损失

  • 当字符串不可变时,可以实现字符串常量池,缓存字符串,提高运行效率。如下所示,可知a和b都是指向常量池中,可变时修改了a指向的"hello",那么b一会跟着改变。所以在真实中当a改变时,"hello"并没有改变,在时指向了常量池中的另外的字符串。

String a = "hello";

String b = "hello";

String、StringBuilder和StringBuffer的区别

因为String是不可变的,如果在进行字符串拼接是使用String的话性能会很低。StringBuilder和StringBuffer提供了append和insert方法可用于字符串拼接。StringBuffer中的方法使用了synchronized修饰而StringBuilder则没有,在并发环境下StringBuffer性能比StringBuilder差,但是功能确实一样的。

对于String的创建有直接赋值和new:

String s1 = "hello";

String s2 = new String("hello");

上面两种方式虽然都可以创建出一个字符串,但是却内存存放上却不同。在JDK1.8中:

  • 直接复制的字符串会去常量池中查找是否已经存在,存在则返回常量池中的内存地址,没有则创建出来然会返回内存地址
  • new的方式则是会在堆上创建出来

所以是s1指向的常量池,s2指向的是堆。当调用了intern()方法后,会把hello保存到常量池中

String s1 = new String("hello");
String intern = s1.intern();
String s2 = "hello";
System.out.println(s2==s1); // false
System.out.println(s2==intern); // true

JVM对字符串的优化

对于字符串的拼接优化:

String s1 = "he"+"llo";
String s2 = "hello";
System.out.println(s2==s1); // true

通过反编译可以看到:两个ldc的操作都是返回hello

ldc #2 <hello>
astore_1
ldc #2 <hello>
astore_2
getstatic #3 <java/lang/System.out : Ljava/io/PrintStream;>
aload_2
aload_1
if_acmpne 18 (+7)
iconst_1
goto 19 (+4)
iconst_0
invokevirtual #4 <java/io/PrintStream.println : (Z)V>
return