Skip to content

day13_String_StringBuilder

java
课前回顾:
  1.异常:Exception
    a.编译时期异常:语法没问题,但是调用了某个方法,底层给咱们抛了一个编译时期异常,导致咱们一调用就爆红
                 Exception以及子类(除了RuntimeException以及子类之外)
    b.运行时期异常:语法没问题,运行的时候就会出现问题
                 RuntimeException以及子类
  2.创建异常对象:
    throw new 异常
  3.异常的处理:
    a.throws 异常1,异常2...
    b.try...catch
  4.finally:不管异常有么有捕获到,都会执行的代码,都是配合try...catch使用
    使用场景:关闭资源
  5.三个输出异常信息的方法:
    a.toString()输出异常类型+原因
    b.getMessage()输出异常类型
    c.printStackTrace()输出最详细的异常信息
  6.Object:所有类的父类,所有的类都会直接或者间接继承Object类
    a.toString():
      没有重写,直接输出对象名会默认调用Object中的toString,会输出地址值
      重写了,直接输出对象名会默认调用重写的toString方法,输出的是对象内容
    b.equals():
      没有重写,直接回比较对象的地址值
      重写了,会比较对象的内容

      ==:
         针对于基本类型,比较的是值
         针对于引用类型,比较的是地址值


今日重点:
  all

第一章.Object 类

1.toString 方法

java
1.Object类中的toString:返回该对象的字符串表示
  public String toString() {
      return getClass().getName() + "@" + Integer.toHexString(hashCode());
  }
2.结论:
  a.直接输出对对象名,默认调用toString方法,如果没有重写Object中的toString,那么就会默认调用Object中的toString方法,输出的是地址值
  b.如果重写了Object中的toString方法,默认就会调用重写的toString方法,重写了toString方法,应该返回对象的内容
java
public class Person {
    private String name;
    private int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

/*    public String toString(){
        return name+","+age;
    }*/

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
java
public class Demo01Object {
    public static void main(String[] args) {
        Person p1 = new Person("张三",10);
        System.out.println(p1);//地址值
        System.out.println(p1.toString());//地址值

        System.out.println("=======================");
        ArrayList<String> list = new ArrayList<>();
        list.add("张三");
        list.add("李四");
        list.add("王五");
        System.out.println(list);
    }
}

小结:如果直接输出对象名,不想输出地址值,就重写 toString 方法

2.equals 方法

java
1.Object中的equals方法:指示其他某个对象是否与此对象“相等”。
  public boolean equals(Object obj) {
        return (this == obj);
  }

  针对于基本类型:==比较的是值
  针对于引用类型:==比较的是地址值

2.结论:
  a.如果没有重写Object中的equals方法,比较的是地址值
  b.如果重写了Object中的equals方法,应该比较对象的内容
1743215246624
java
public class Demo02Object {
    public static void main(String[] args) {
        Person p1 = new Person("涛哥", 12);
        Person p2 = new Person("涛哥", 12);
        System.out.println(p1.equals(p2));
        ArrayList<String> list = new ArrayList<>();
        System.out.println(p1.equals(p1));

        System.out.println("============================");

        String s1 = new String("abc");
        String s2 = new String("abc");
        System.out.println(s1==s2);
        System.out.println(s1.equals(s2));
    }
}
java
public class Person {
    private String name;
    private int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

/*    public String toString(){
        return name+","+age;
    }*/

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    /**
     * 问题1:我们用obj调用name和age操作是为啥?
     *      name和age是Person中的属性,不是Object中的属性,用Object类型调用name和age
     *      由于多态,父类不能直接调用子类特有成员,所以需要向下转型
     *
     * 问题2:如果传递的不是Person类型,那么强转之后,就会出现类型转换异常
     *      所以我们需要先判断一下类型再强转
     *
     * 问题3:如果传递null,我们直接返回false即可,不用做类型判断
     *
     * 问题4:如果传递自己呢?我们直接返回true即可,没必要做类型判断,强转等操作,直接返回true
     * @param obj
     * @return
     */

/*    public boolean equals(Object obj){
        if (this==obj){
            return true;
        }

        if (obj==null){
            return false;
        }

        if (obj instanceof Person){
            Person p = (Person) obj;
            return this.name.equals(p.name) && this.age == p.age;
        }

        return false;

    }*/

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age && Objects.equals(name, person.name);
    }

}

小结:如果调用 equals 想比较对象的内容,那么就重写 equals 方法

总结:

1.直接输出对象名不想输出地址值,而是想输出对象的内容(属性值)就重写 toString 方法

2.如果比较对象时不想比较地址值,而是比较对象的内容(属性值),就重写 equals 方法

第二章.String

1.String 介绍

java
1.概述:String 类代表字符串,属于字符串的类型
2.特点:
  a.Java 程序中的所有字符串字面值(如 "abc" )都作为此类的实例(对象)实现
    凡是带双引号的都是String的对象
    比如: String s = "abc"  -> String是数据类型;s是对象名;"abc"是对象

  b.字符串是常量(底层是被final修饰的数组);它们的值在创建之后不能更改

  c.因为 String 对象是不可变的,所以可以共享
17432169680921743217298365

2.String 的实现原理

java
1.底层就是一个被final修饰的数组
  a.jdk8: 被final修饰的一个char数组
  b.jdk8之后: 被final修饰的一个byte数组

  private final byte[] value;
java
byte数组被final修饰了,所以数组的地址值不能改变,直接定死

3.String 的创建

java
1.String()  利用空参构造创建String对象
2.String(String s) 利用字符串创建String对象
3.String(char[] chars) 根据char数组创建String对象
4.String(byte[] bytes)通过使用平台的默认字符集解码指定的 byte 数组,构造一个新的 String。

5.简化:  String 变量名 = ""
java
public class Demo01String {
    public static void main(String[] args) {
        //1.String()  利用空参构造创建String对象
        String s1 = new String();
        System.out.println(s1);
        //2.String(String s) 利用字符串创建String对象
        String s2 = new String("abc");
        System.out.println(s2);
        //3.String(char[] chars) 根据char数组创建String对象
        char[] chars = {'a', 'b', 'c'};
        String s3 = new String(chars);
        System.out.println(s3);

        /*
          4.String(byte[] bytes)通过使用平台的默认字符集解码指定的 byte 数组,构造一个新的 String。
            a.如果字节是正数,会直接转成对应的字符
            b.如果是负数,就涉及到中文了,涉及到中文了,就涉及到编码问题了
              中文对应的字节都是负数

              GBK:一个汉字占2个字节
              UTF-8:一个汉字占3个字节

            c.注意:
              平台:操作系统
              咱们得操作系统默认字符集:GBK

              但是我们的代码是在idea中写的,idea在代码启动的时候,会给代码添加
              一个启动参数:-Dfile.encoding=UTF-8
              所以我们认为idea就是按照UTF-8来编码解码

         */
        byte[] bytes = {97,98,99};
        String s4 = new String(bytes);
        System.out.println(s4);

        byte[] bytes1 = {-27,-101,-67};
        String s5 = new String(bytes1);
        System.out.println(s5);
    }
}
java
扩展构造:
  1.String(char[] chars,int offset,int length)->将字符数组的一部分转成String
           chars:代表的是被转的数组
           offset:代表的是从数组的哪个索引开始转
           length:转多少个
  2.String(byte[] bytes,int offset,int count) -> 将字节数组的一部分转成String
           bytes:被转的数组
           offset:从数组的哪个索引开始转
           count:转多少个
java
public class Demo02String {
    public static void main(String[] args) {
        /*1.String(char[] chars,int offset,int length)->将字符数组的一部分转成String
        chars:代表的是被转的数组
        offset:代表的是从数组的哪个索引开始转
        length:转多少个*/
        char[] chars = {'a','b','c','d','e','f'};
        String s = new String(chars, 2, 3);
        System.out.println("s = " + s);

        System.out.println("=======================");
        /*2.String(byte[] bytes,init offset,int count) -> 将字节数组的一部分转成String
        bytes:被转的数组
        offset:从数组的哪个索引开始转
        count:转多少个*/
        byte[] bytes = {97,98,99,100,101,102};
        String s1 = new String(bytes, 2, 3);
        System.out.println("s1 = " + s1);
    }
}

4.String 面试题

java
public class Demo03String {
    public static void main(String[] args) {
        String s1 = "abc";
        String s2 = "abc";
        String s3 = new String("abc");
        System.out.println(s1==s2);//true
        System.out.println(s1==s3);//false
    }
}
1743220521124
java
问题1: String s = new String("abc") 共有几个对象  -> 2个

问题2: String s = new String("abc") 共创建了几个对象  -> 1个或2个
1743228869207

5.字符串常见问题

Java
public class Demo04String {
    public static void main(String[] args) {
        String s1 = "hello";
        String s2 = "world";
        String s3 = "helloworld";
        String s4 = "hello"+"world";
        String s5 = s1+s2;
        String s6 = s1+"world";
        System.out.println(s3==s4);//true
        System.out.println(s3==s5);//false
        System.out.println(s5==s6);//false
    }
}
1743229284887

如果拼接字符串时,等号右边没有变量参与,都是字符串字面值,那么不会随意产生新对象(假如之前有这个拼接好的串儿)

如果等号右边有变量参与拼接 ,即使之前有这个字符串内容,也会产生新对象

第三章.String 的方法

1.判断方法

java
1.boolean equals(Object obj) ->比较字符串内容
2.boolean equalsIgnoreCase(String s) -> 比较字符串内容,忽略大小写  -> 常见于验证码对比使用
java
public class Demo05String {
    public static void main(String[] args) {
        String s1 = "abc";
        String s2 = new String("abc");
        System.out.println(s1.equals(s2));

        String s3 = "ABC";
        System.out.println(s1.equalsIgnoreCase(s3));

        //String s4 = "1234";
        //System.out.println("一二三四".equalsIgnoreCase(s4));
        //System.out.println("壹贰叁肆".equalsIgnoreCase("一二三四"));
    }
}

2.练习 1

java
已知用户名和密码,请用程序实现模拟用户登录。总共给三次机会,登录成功与否,给出相应的提示
java
public class Demo06String {
    public static void main(String[] args) {
        //1.定义两个字符串,代表已经注册过的用户名和密码
        String username = "admin";
        String password = "123456";

        //2.创建Scanner对象
        Scanner sc = new Scanner(System.in);

        //3.循环录入用户名和密码,给3次机会
        for (int i = 0; i < 3; i++) {
            System.out.println("请您输入用户名:");
            String name = sc.next();
            System.out.println("请您输入密码:");
            String pwd = sc.next();
            if (name.equals(username) && pwd.equals(password)){
                System.out.println("登录成功!");
                break;
            }else{
                System.out.println("用户名或密码错误,您还有"+(2-i)+"次机会");
            }
        }
    }
}
java
public class Demo07String {
    public static void main(String[] args) {
        String s1 = "abc";
        method(s1);
    }

    private static void method(String s1) {
        /*if (s1.equals("abc")){
            System.out.println("是abc");
        }else{
            System.out.println("不是abc");
        }*/

        if ("abc".equals(s1)){
            System.out.println("是abc");
        }else{
            System.out.println("不是abc");
        }

        /*
           工具类: Objects
         */
       /* boolean result = Objects.equals(s1, "abc");
        System.out.println(result);*/
    }
}

3.获取功能

java
1.String concat(String str)  拼接字符串,返回一个新串
2.char charAt(int index) 根据索引获取对应的字符
3.int indexOf(String str) 根据指定的字符串获取对应的第一次出现的索引
4.String substring(int beginIndex) 从指定的索引位置开始截取字符串到最后,返回一个新串儿
5.String substring(int beginIndex,int endIndex)从beginIndex截取到endIndex,返回一个新串儿
                                               含头不含尾
6.int length() 获取字符串长度
java
public class Demo08String {
    public static void main(String[] args) {
        String s = "abcdefg";
        //1.String concat(String str)  拼接字符串,返回一个新串
        String str1 = s.concat("哈哈哈哈");
        System.out.println("str1 = " + str1);
        //2.char charAt(int index) 根据索引获取对应的字符
        System.out.println(s.charAt(0));
        //3.int indexOf(String str) 根据指定的字符串获取对应的第一次出现的索引
        System.out.println(s.indexOf("c"));
        //4.String substring(int beginIndex) 从指定的索引位置开始截取字符串到最后,返回一个新串儿
        System.out.println(s.substring(2));
        //5.String substring(int beginIndex,int endIndex)从beginIndex截取到endIndex,返回一个新串儿,含头不含尾
        System.out.println(s.substring(2,4));
        //6.int length() 获取字符串长度
        System.out.println(s.length());
    }
}

4.练习

java
遍历字符串
java
public class Demo09String {
    public static void main(String[] args) {
        String s = "abcdefg";
        for (int i = 0;i<s.length();i++){
            System.out.println(s.charAt(i));
        }
    }
}

5.转换功能

java
1.char[] toCharArray() -> 将字符串转成char数组
2.byte[] getBytes() -> 将字符串转成byte数组
3.String replace(CharSequence target, CharSequence replacement)->将target指定的字符串替换成replacement指定的串儿
                 String类是CharSequence接口的实现类

4.byte[] getBytes(String charsetName)->将字符串按照指定的编码规则去转成byte数组
                  charsetName:不区分大小写
java
public class Demo10String {
    public static void main(String[] args) throws UnsupportedEncodingException {
        String s = "aabcdefg";
        ///1.char[] toCharArray() -> 将字符串转成char数组
        char[] chars = s.toCharArray();
        System.out.println(chars);
        ///2.byte[] getBytes() -> 将字符串转成byte数组
        byte[] bytes = s.getBytes();
        System.out.println(Arrays.toString(bytes));
        /*
        3.String replace(CharSequence target, CharSequence replacement)->将target指定的字符串替换成replacement指定的串儿
        String类是CharSequence接口的实现类
        */
        String str = s.replace("a", "z");
        System.out.println("str = " + str);

       /* 4.byte[] getBytes(String charsetName)->将字符串按照指定的编码规则去转成byte数组
            charsetName:不区分大小写
        */
        byte[] bytes1 = "你".getBytes("UTF-8");
        System.out.println(Arrays.toString(bytes1));
    }
}

7.练习 4

java
键盘录入一个字符串,统计该字符串中大写字母字符,小写字母字符,数字字符出现的次数(不考虑其他字符)

1.创建Scanner对象,录入一个字符串-> data
2.定义三个变量,分别统计大写字母字符,小写字母字符,数字字符的个数
  int big = 0;
  int small = 0;
  int number = 0;
3.遍历data字符串,将每一个字符都获取出来
4.在遍历的过程中是否是大写字母,小写字母,数字,分别让三个变量++
  a.大写字母范围65-90 -> B 66 ->66在65-90之间,证明是大写字母
  b.小写字母范围97-122 -> b 98 -> 98在97-122之间,证明是小写字母
  c.数字范围48-57 -> 1 49 -> 49在48-57之间,证明是数字
5.输出三个变量
java
public class Demo11String {
    public static void main(String[] args) {
        //1.创建Scanner对象,录入一个字符串-> data
        Scanner scanner = new Scanner(System.in);
        System.out.println("请您输入一个字符串:");
        String data = scanner.next();
        /*2.定义三个变量,分别统计大写字母字符,小写字母字符,数字字符的个数
        int big = 0;
        int small = 0;
        int number = 0;*/
        int big = 0;
        int small = 0;
        int number = 0;
        //3.遍历data字符串,将每一个字符都获取出来
        char[] chars = data.toCharArray();
        //4.在遍历的过程中是否是大写字母,小写字母,数字,分别让三个变量++
        for (int i = 0; i < chars.length; i++) {
            char key = chars[i];
        //a.大写字母范围65-90 -> B 66 ->66在65-90之间,证明是大写字母
            if (key >= 'A' && key <= 'Z') {
                big++;
        //b.小写字母范围97-122 -> b 98 -> 98在97-122之间,证明是小写字母
            } else if (key >= 'a' && key <= 'z') {
                small++;
        //c.数字范围48-57 -> 1 49 -> 49在48-57之间,证明是数字
            } else if (key >= '0' && key <= '9') {
                number++;
            }
        }
        //5.输出三个变量
        System.out.println("大写字母个数:" + big);
        System.out.println("小写字母个数:" + small);
        System.out.println("数字个数:" + number);
    }
}

8.分割功能

java
 String[] split(String regex)  -> 按照指定的规则切割字符串
java
public class Demo12String {
    public static void main(String[] args) {
        String s = "abc,java,test";
        String[] arr = s.split(",");
        System.out.println(Arrays.toString(arr));

        System.out.println("==================");

        String s1 = "abc.java";
        String[] arr1 = s1.split("\\.");
        System.out.println(Arrays.toString(arr1));
    }
}

第四章.其他方法

java
boolean contains(CharSequence s)  -> 判断字符串中是否包含指定的串儿
boolean endsWith(String suffix)  -> 判断字符串是否以指定的串儿结尾
boolean startsWith(String prefix)  -> 判断字符串是否以指定的串儿开头
String toLowerCase()  -> 将大写字母转成小写
String toUpperCase() -> 将小写字母转成大写
String trim() -> 去掉字符串两端空格
java
public class Demo13String {
    public static void main(String[] args) {
        //boolean contains(CharSequence s)  -> 判断字符串中是否包含指定的串儿
        System.out.println("abcdefg".contains("abc"));
        //boolean endsWith(String suffix)  -> 判断字符串是否以指定的串儿结尾
        System.out.println("abcdefg".endsWith("g"));
        //boolean startsWith(String prefix)  -> 判断字符串是否以指定的串儿开头
        System.out.println("abcdefg".startsWith("a"));
        //String toLowerCase()  -> 将大写字母转成小写
        System.out.println("ABCdefg".toLowerCase());
        //String toUpperCase() -> 将小写字母转成大写
        System.out.println("abcdefg".toUpperCase());
        //String trim() -> 去掉字符串两端空格
        System.out.println("   abc  defg   ".trim());

        String str = "   adfa dsfads   dsfasd   ".replace(" ", "");
        System.out.println(str);
    }
}

第五章. String 新特性_文本块

预览的新特性文本块在 Java 15 中被最终确定下来,Java 15 之后我们就可以放心使用该文本块了。

JDK 12 引入了 Raw String Literals 特性,但在其发布之前就放弃了这个特性。这个 JEP 与引入多行字符串文字(文本块)在意义上是类似的。Java 13 中引入了文本块(预览特性),这个新特性跟 Kotlin 中的文本块是类似的。

现实问题

在 Java 中,通常需要使用 String 类型表达 HTML,XML,SQL 或 JSON 等格式的字符串,在进行字符串赋值时需要进行转义和连接操作,然后才能编译该代码,这种表达方式难以阅读并且难以维护。

文本块就是指多行字符串,例如一段格式化后的 XML、JSON 等。而有了文本块以后,用户不需要转义,Java 能自动搞定。因此,文本块将提高 Java 程序的可读性和可写性。

目标

  • 简化跨越多行的字符串,避免对换行等特殊字符进行转义,简化编写 Java 程序。
  • 增强 Java 程序中字符串的可读性。

举例

会被自动转义,如有一段以下字符串:

html
<html>
  <body>
    <p>Hello, 尚硅谷</p>
  </body>
</html>

将其复制到 Java 的字符串中,会展示成以下内容:

java
"<html>\n" +
"    <body>\n" +
"        <p>Hello, 尚硅谷</p>\n" +
"    </body>\n" +
"</html>\n";

即被自动进行了转义,这样的字符串看起来不是很直观,在 JDK 13 中,就可以使用以下语法了:

java
"""
<html>
  <body>
      <p>Hello, world</p>
  </body>
</html>
""";

使用“”“作为文本块的开始符和结束符,在其中就可以放置多行的字符串,不需要进行任何转义。看起来就十分清爽了。

文本块是 Java 中的一种新形式,它可以用来表示任何字符串,并且提供更多的表现力和更少的复杂性。

(1)文本块由零个或多个字符组成,由开始和结束分隔符括起来。

  • 开始分隔符由三个双引号字符表示,后面可以跟零个或多个空格,最终以行终止符结束。
  • 文本块内容以开始分隔符的行终止符后的第一个字符开始。
  • 结束分隔符也由三个双引号字符表示,文本块内容以结束分隔符的第一个双引号之前的最后一个字符结束。

以下示例代码是错误格式的文本块:

java
String err1 = """""";//开始分隔符后没有行终止符,六个双引号最中间必须换行

String err2 = """  """;//开始分隔符后没有行终止符,六个双引号最中间必须换行

如果要表示空字符串需要以下示例代码表示:

java
String emp1 = "";//推荐
String emp2 = """
   """;//第二种需要两行,更麻烦了

(2)允许开发人员使用“\n”“\f”和“\r”来进行字符串的垂直格式化,使用“\b”“\t”进行水平格式化。如以下示例代码就是合法的。

java
String html = """
    <html>\n
      <body>\n
        <p>Hello, world</p>\n
      </body>\n
    </html>\n
    """;

(3)在文本块中自由使用双引号是合法的。

java
String story = """
Elly said,"Maybe I was a bird in another life."

Noah said,"If you're a bird , I'm a bird."
 """;
java
public class Demo14String {
    public static void main(String[] args) {
        String s = "<!DOCTYPE html>\n" +
                "<html lang=\"en\">\n" +
                "<head>\n" +
                "    <meta charset=\"UTF-8\">\n" +
                "    <title>性感涛哥,在线发牌</title>\n" +
                "</head>\n" +
                "<body>\n" +
                "    哈哈哈\n" +
                "</body>\n" +
                "</html>";
        System.out.println(s);

        System.out.println("================================");

        String s1 = """
                <!DOCTYPE html>
                <html lang="en">
                <head>
                    <meta charset="UTF-8">
                    <title>性感涛哥,在线发牌</title>
                </head>
                <body>
                    哈哈哈
                </body>
                </html>
                """;
        System.out.println(s1);
        System.out.println("================================");
    }
}

第六章.StringBuilder 类

1.StringBuilder 的介绍

java
1.概述:一个可变的字符序列(底层数组没有被final修饰,数组地址值可以改变)。此类提供一个与 StringBuffer 兼容的 API(StringBuilder和StringBuffer使用一毛一样)

2.作用:主要是拼接字符串

3.问题:String就可以拼接字符串,那为啥还要学StringBuilder呢?
   a.String拼接字符串的时候,每拼接一次,都会产生一个新的字符串对象,如果拼接过多,会占用内存,而且拼接效率低
   b.StringBuilder底层自带缓冲区,我们所有拼接的字符串内容,都会保存到这个缓冲区中,而且不会随意创建新对象
     所以,用StringBuilder拼接字符串省内存,拼接效率高

4.StringBuilder的特点:
  a.底层自带缓冲区,拼接的过程中不会随意产生新对象,缓冲区默认是长度为16的数组
  b.拼接如果超出数组容量,会自动扩容(因为底层数组或者叫缓冲区,没有被final修饰,数组地址值可以改变)
  c.怎么扩容的:创建新数组,指明新数组容量,将老数组元素复制到新数组中去,然后将新数组地址值赋值给老数组
  d.默认扩容容量: 2倍+2
    如果存储的数据没有超过默认扩容容量,就按照2倍+2扩容
    如果存储的数据超过了默认扩容容量,那么就直接按照实际存的数据来扩容
1743238872886

2.StringBuilder 的使用

java
java
java
java
java
练习:键盘录入一个字符串,判断此字符串是否为"回文内容"
java

3.练习

java
定义一个数组,以[元素1, 元素2, 元素3..]的形式输出,用StringBuilder拼接
java
自己玩儿

String,StringBuilder,StringBuffer 区别:

1.相同点:都可以字符串拼接

2.不同点:

​ a.String:每拼接一次都会产生新的对象,占用内存,拼接效率低

​ b.StringBuilder:不会随意产生新对象,效率高,线程不安全

​ c.StringBuffer:不会随意产生新对象,效率低,线程安全

3.从拼接效率来说:

StringBuilder>StringBuffer>String