String
String对象是不可变的,当我们要修改String的值的时候,实际上我们是新建了一个String的对象,而开始的对象则并没有发生改变.我们也可以简单的认为String只是具有可读性。
public static String upCase(String s){
return s.toUpperCase();//将小写字母转化为大写
}
public static void main(String[] args) throws Exception {
String q = "baishao";
System.out.println(q);
String p = upcase(q);
System.out.println(p);
System.out.println(q);
}
//输出结果
baishao
BAISHAO
baishao
该代码中,当q传值入upCase()时,实际上传递的引用是一个拷贝,每当把String对象作为方法的参数的时,都会复制一份引用,而该引用所指向的对象会一直保留再原先的物理位置上,从未动过。
回到我们的upCase()方法里,当引入传入时候就有了名字s,而只有当upCase()方法正在运行的时候,局部引用s才存在.一旦upCase()运行结束,s就消失了.当然,upCase()的返回值,其实只是最终结果的引用,而且该引用已经指向了一个新的对象,而原本的q还在原地。
不可变性会带来一定的效率问题。其中String对象中的重载“+”操作符就是一个很好的例子,重载的意思是,一个操作符应用在特定的类时,被赋予了特殊的意义。我们通过一个例子来看观察。
/*
*这是一个用“+”连接String的例子
*/
public class Demo1 {
public static void main(String[] args) {
String s1 = "baishao";
String s2 = s1 + "abc" + "hello" + 46;
System.out.println(s2);
}
}
我们可以大胆猜想一下,有没有可能String中有一个append的方法,它会生成一个新的String对象,用来包含s1与“adc”拼接后的字符串,然后该对象又与“hello”相连,再次形成了一个新的String对象,但是如果是这样的话,这样的话不就会产生一大堆需要垃圾回收的对象吗?而这频繁的触发GC势必会引起程序执行的效率下降。为了一探究竟,我们可以试着反编译该代码
依次执行以下命令:
//生成对应的字节码文件
javac Demo1.java
//对字节码文件进行反编译
javap -c Demo1
执行该命令的字节码如下:
public class Demo.Demo1 {
public Demo.Demo1();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: ldc #7 // String baishao
2: astore_1
3: aload_1
4: invokedynamic #9, 0 // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
9: astore_2
10: getstatic #13 // Field java/lang/System.out:Ljava/io/PrintStream;
13: aload_2
14: invokevirtual #19 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
17: return
}
在JDK5-JDK8,Javac会把字符串连接语句转译成StringBuilder调用,而在JDK9之后中变成了动态方法调用。那么这个#0:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;是什么呢?
动态指令invokedynamic指令会调用makeConcatWithConstants
方法进行字符串的连接。
该方法位于java.lang.invoke.StringConcatFactory类中。