• String的小笔记


    String类的对象是不可变的!

    在使用String类的时候要始终记着这个观念。一旦创建了String对象,它就不会改变。

    String类中也有可以改变String中字符串的方法,但只要是涉及改变的方法,都是通过创造并返回一个全新的String对象来实现的。而原先那个String对象是没有被改动过的。

    String对操作符“+”的重载

    String对象是不可变的,而这个不可变性往往会带来性能上、效率上的问题。

    Java中,为String类重载的“+”操作符就是一个例子。  操作符的重载的意思是,一个操作符作用于一个特定的类的时候,被赋予了特殊的意义。用于String的“+”和“+=”是Java中仅有的l两个重载过的操作符,而且Java并不允许程序员重载任何操作符。

    我们知道操作符“+”可以用来连接String,并且当操作符的另一边不是String的时候,会尝试把它变成String。

    public class Concatenation {
        public static void main(String[] args) {
        String mango = "mango";
        String s = "abc" + mango + "def" + 47;
        System.out.println(s);
    }
     /* Output:
    abcmangodef47

    这个我们可能会想象,它是这样工作的:

    String中应该有个append()方法,一开始String“abc”会调用这个append方法,然后创建一个新的String对象,连接abc和Stringmango,然后再创建新的String对象,以此类推。

    因为String对象是不能改变的嘛。意味着这几个String的+运算,会产生好几个String对象,这样的猜想确实没错。

    这个猜想确实行得通,但这样的话,垃圾回收器就要回收一堆中间的String对象。很浪费资源噢。

    为了看Java底层真正是怎么实现这个Java的Stirng加法的,我们用到了jdk自带的反编译工具——Javap工具。

    输入命令行:

    javap -c Concatenation

    -c代表着生成JVMz字节码,下面是删减后的g关键字节码:

    public static void main(java.lang.String[]);
        Code:
        Stack=2, Locals=3, Args_size=1
        0: ldc #2; //String mango
        2: astore_1
        3: new #3; //class StringBuilder
        6: dup
        7: invokespecial #4; //StringBuilder."<init>":()
        10: ldc #5; // String abc
        12 invokevirtual #6; //StringBuilder.append:(String)
        15 aload_1
        16 invokevirtual #6; //StringBuilder.append:(String)
        19 ldc #7; //String def
        21 invokevirtual #6; //StringBuilder.append:(String)
        24 bipush 47
        26 invokevirtual #8; //StringBuilder.append:(I)
        29 invokevirtual #9; //StringBuilder.toString:()
        32 astore_2
        33 getstatic #10; //Field System.out:PrintStream;
        36 aload_2
        37 invokevirtual #11; // PrintStream.println:(String)
        40 return

    这上面是我们的JVM的汇编语言。

    我们可以看到,虽然我们在自己写的的源代码中完全没有提到StringBuilder类,但编译器却自作主张自己引入了!!——因为它大大地提高了效率!

    在这个例子中,编译器创建了一个StringBuilderd对象来实现这个s的String的加法。调用了StringBuilder的append()方法四次,然后最后调用了StringBuilder对象的toString()方法来产生一个String对象并存在s中返回。

    可见Java编译器对String的加法操作是会自动优化的!

    现在我们可以随意地使用String了,但有时候编译器的优化工作做的不是很好,需要我们手动地优化,看个例子

    //: strings/WhitherStringBuilder.java
    public class WhitherStringBuilder {
        public String implicit(String[] fields) {
            String result = "";
            for(int i = 0; i < fields.length; i++)
                result += fields[i];
            return result;
        }
    
        public String explicit(String[] fields) {
            StringBuilder result = new StringBuilder();
            for(int i = 0; i < fields.length; i++)
                result.append(fields[i]);
            return result.toString();
        }
    } ///:~

    这两个方法的目的都一样,把一个String数组中的元素都加起来,最后返回一个String对象。

    然后我们用上面一样的方法来看看它的执行过程:

    implicit方法的执行汇编字节码:

    public java.lang.String implicit(java.lang.String[]);
        Code:
        0: ldc #2; //String
        2: astore_2
        3: iconst_0
        4: istore_3
        5: iload_3
        6: aload_1
        7: arraylength
        8: if_icmpge 38
        11: new #3; //class StringBuilder
        14: dup
        15: invokespecial #4; // StringBuilder.”<init>”:()
        18: aload_2
        19: invokevirtual #5; // StringBuilder.append:()
        22: aload_1
        23 iload_3
        24 aaload
        25: invokevirtual #5; // StringBuilder.append:()
        28: invokevirtual #6; // StringBuiIder.toString:()
        31: astore_2
        32: iinc 3, 1
        35: goto 5
        38: aload_2
        39 areturn

    书上的小解析:

    大概意思就是第八行到第35行构成了循环:

     然后我们要关注的重点是:

    在循环中,一个StringBuilder的构造器被调用了,说明你每次进入循环都会创建一个新的StringBuilder对象。

    然后是explicit的执行字节码:

    public java.lang.String explicit(java.lang.String[]);
        Code:
        0: new #3; //class StringBuilder
        3: dup
        4: invokespecial #4; // StringBuilder.”<init>”:()
        7: astore_2
        8: iconst_0
        9: istore_3
        10: iload_3
        11: aload_1
        12: arraylength
        13: if_icmpge 30
        16: aload_2
        17: aload_1
        18: iload_3
        19: aaload
        20 invokevirtual #5; // StringBuilder.append:()
        23 pop
        24: iinc 3,1
        27: goto 10
        30: aload_2
        31: invokevirtual #6; // StringBuiIder.toString:()
        34: areturn

    可以看到:

    这里只创建了一个StringBuilder对象,显然效率更好!
    如果你知道字符串的长度,还可以体现分配好size,这样还避免了多次重新分配缓存。

    所以说,如果你要重写一个类的toString方法,如果字符串操作不是很复杂,就可以依赖编译器本身的优化;如果很复杂的话,建议自己创建一个StringBuilder类。

    StringBuilder是jdk5后引入的。之前用的是StringBuffer,是线程安全的,所以开销也更大,显然StringBuilder效率更高。

    无意识的递归

    下面例子是基于这样的一个想法:
    你想在某个类的toString中打印出这个对象的地址,然后你想到了关键字this于是就写出了这样的代码:

    但是,当你尝试去打印这个对象的话,你会看到很长的一个异常报错。

    为什么会这样呢?因为发生了递归调用。
    当编译器看到String后面跟了个加号“+”,会尝试将后面的this变成String类型,这就变成调用toString方法,然后就递归了……

    正确的做法应该是调用根类Object的toString()方法,也就是super.toString()。

  • 相关阅读:
    C 语言 静态库和动态库的创建和应用
    C++ 中英文术语对照
    下午
    [转]内核 do_fork 函数源代码浅析
    关于C#反射机制,自己写的
    获取字符串中数字
    关于C#反射机制,来源于网络
    关于 Nhinernate 的one to one(转载)
    鼠标坐标的记录
    关于C#中hibernate.cfg.xml动态加载问题
  • 原文地址:https://www.cnblogs.com/wangshen31/p/10340210.html
Copyright © 2020-2023  润新知