背景
为了验证String,StringBuffer,StringBuilder的区别?String为什么是不可变的。
首先,找到String类,源码如此:
/** The value is used for character storage. */
private final char value[];
/**
* 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);
}
结论:String是使用了final修饰的char[]数组来存放字符串,所以不可变。
继续来看final的用法
final修饰成员变量用处:
1.一个永不改变的编译时常量
2.一个在运行时被初始化的值,而你不希望他被改变
对于编译期常量这种情况,编译器可以将常量值代入任何可能用到它的计算式中,也就是说,可以在编译时执行计算式,这减轻了一些运行时的负担。在Java中,这类常量必须是基本数据类型,并且以final关键字表示。在对这个常量进行定义的时候,必须对其进行赋值。
插播:java基本数据类型都有哪些:
在程序设计中经常用到一系列类型,他们需要特殊对待。可以把它们想象成“基本”类型。之所以特殊对待,是因为new将对象存储在“堆”里,故用new创建一个对象——特别是小的,简单的变量,往往不是很有效。一般,对于这些类型,Java采取与C和C++相同的方法。也就是说,不用new来创建对象,而是创建一个并非是引用的“自动”变量。这个变量直接存储“值”,并置于堆栈中,因此更加高效。
boolean,char(16),byte(8),short(16),int(32),long(64),float(32),double(64),void
- 说明static final和final的区别
public class Temp1 {
private static Random rand = new Random(18);
private final int v1 = rand.nextInt(16);
private static final int v2 = rand.nextInt(16);
public String toString() {
return "v1: "+v1+";v2: "+v2;
}
public static void main(String[] args) {
Temp1 t1 = new Temp1();
Temp1 t2 = new Temp1();
Temp1 t3 = new Temp1();
System.out.println(t1);
System.out.println(t2);
System.out.println(t3);
}
}
打印结果:
v1: 15;v2: 11
v1: 3;v2: 11
v1: 2;v2: 11
结果:可以发现,final的值每次都会改变,但是static final每次都只有一份,可见static的含义。
- 空白final
可以出现空白的final,但是必须在类的构造器中对其进行初始化。
final修饰方法:
- final修饰方法用处是防止其他类重写方法。
final修饰类:
- 永远不希望别人继承的类。
final的知识点复习到这里也就结束了,下面我们来继续看背景中的剩余问题
StringBuffer和StringBuilder同样是继承AbstractStringBuilder,但是一个是线程安全,一个不是。
@Override
public synchronized StringBuffer append(Object obj) {
toStringCache = null;
super.append(String.valueOf(obj));
return this;
}
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
@Override
public StringBuilder append(Object obj) {
return append(String.valueOf(obj));
}
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
下面通过分析实际代码,来看看如何写好toString()方法
step1:
C:\Users\***>javap -c C:\Users\***\Desktop\MyStringTest.class
Compiled from "MyStringTest.java"
public class cn.com.sjzc.MyStringTest {
public cn.com.sjzc.MyStringTest();
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 #2 // String
2: astore_1
3: iconst_0
4: istore_2
5: iload_2
6: iconst_5
7: if_icmpge 35
10: new #3 // class java/lang/StringBuilder
13: dup
14: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
17: aload_1
18: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
21: iload_2
22: invokevirtual #6 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
25: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
28: astore_1
29: iinc 2, 1
32: goto 5
35: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;
38: aload_1
39: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
42: return
}
step2 :通过javap -c 反编译,可以看到操作符 “+” 在对String使用的时候,是构建了一个StringBuilder,并且每次通过append()方法来修改字符串。我们可以继续设想,如果是把“+”操作放在循环体又会如何?
C:\Users\***>javap -c C:\Users\***\Desktop\circle.class
Compiled from "MyStringTest.java"
public class cn.com.sjzc.MyStringTest {
public cn.com.sjzc.MyStringTest();
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 #2 // String
2: astore_1
3: iconst_0
4: istore_2
5: iload_2
6: iconst_5
7: if_icmpge 35
10: new #3 // class java/lang/StringBuilder
13: dup
14: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
17: aload_1
18: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
21: iload_2
22: invokevirtual #6 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
25: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
28: astore_1
29: iinc 2, 1
32: goto 5
35: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;
38: aload_1
39: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
42: return
}
step3 :这里说一下汇编知识:dup 和 invokevirtual 构成循环体。
所有在StringBuilder在循环体中每次都要被创建,执行了append()后,又被销毁,那我们需要想出一个优化的方法:
//如果这样在运行时会不会好一点
public static void main(String[] args) {
StringBuilder sb = new StringBuilder("abc");
for(int i = 0; i < 5; i++){
sb.append(i);
}
System.out.println(sb.toString());
}
public static void main(java.lang.String[]);
Code:
0: new #2 // class java/lang/StringBuilder
3: dup
4: ldc #3 // String abc
6: invokespecial #4 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
9: astore_1
10: iconst_0
11: istore_2
12: iload_2
13: iconst_5
14: if_icmpge 29
17: aload_1
18: iload_2
19: invokevirtual #5 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
22: pop
23: iinc 2, 1
26: goto 12
29: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
32: aload_1
33: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
36: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
39: return
}
这次StringBuilder只初始化了一次。
小总结:循环写这种字符串“+”操作,最好直接使用StringBuilder的append;可以使用javap -c命令来反编译代码,查看程序的合理性
为了打印内存地址引出的异常
背景:有时我们想在打印数组的时,可以打印对象的内存地址
public class MyStringTest {
public String toString() {
return "MyStringTest" + this + "\n";
}
public static void main(String[] args) {
List<MyStringTest> list = new ArrayList<>();
for(int i = 0; i < 5; i++) {
list.add(new MyStringTest());
}
System.out.println(list);
}
}
到这就报错了。原因是递归调用,“+”操作符尝试把this转化为String,然后就调用了它的toString()方法。想要不报错,改为super.toString()。