Java

String

2018-11-24  本文已影响68人  huangzhuyun

背景

为了验证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

  1. 说明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的含义。

  1. 空白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()。

上一篇 下一篇

猜你喜欢

热点阅读