String

背景

为了验证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修饰类:

  • 永远不希望别人继承的类。

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()。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,367评论 6 512
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,959评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,750评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,226评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,252评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,975评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,592评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,497评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,027评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,147评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,274评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,953评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,623评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,143评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,260评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,607评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,271评论 2 358

推荐阅读更多精彩内容