关于后端:Java-不要在问String为什么是不可变的

43次阅读

共计 2553 个字符,预计需要花费 7 分钟才能阅读完成。

前言

String 是一个非凡的对象,属于援用类型, 一经创立初始化后不能更改,因为 String 对象的不可变,所以能够共享。

定义

从概念上讲,Java 字符串就是 Unicode 字符序列。在规范 Java 类库中提供了一个预约义类 String。String 就是用双引号引起来的几个字符,每个用双引号括起来的字符串都是 String 类的一个实例:

String s = "abc";

String 对象是不可变的。查看 JDK 文档你就会发现,String 类中每一个看起来会批改 String 值的办法,实际上都是创立了一个全新的 String 对象,以蕴含批改后的字符串内容。而最后的 String 对象则丝毫未动。看看上面的代码:

public class User {public static String upcase(String s){return s.toUpperCase();
    }

    public static void main(String[] args) {
        String name = "manoninsight";
        String name1 = upcase(name);
        System.out.println(name1);
        System.out.println(name);
    }
}

输入:

MANONINSIGHT
manoninsight

当把 name 传递给 upcase() 办法时,理论传递的是援用的一个拷贝。其实,每当把 String 对象作为办法的参数时,都会复制一份援用,而该援用所指向的对象其实始终待在繁多的物理地位上,从未动过。

回到 upcase() 的定义,传入其中的援用有了名字 s,只有 upcase() 运行的时候,部分援用 s 才存在。一旦 upcase() 运行完结,s 就隐没了。当然了,upcase() 的返回值,其实是最终后果的援用。这足以阐明,upcase() 返回的援用曾经指向了一个新的对象,而 name 依然在原来的地位。

对于一个办法而言,参数是为该办法提供信息的,而不是想让该办法扭转本人的。在浏览这段代码时,读者天然会有这样的感觉。这一点很重要,正是有了这种保障,才使得代码易于编写和浏览。

  1. 小细节:
    咱们晓得 String 是一个对象,然而咱们后面说过对象的创立要通过 new 关键字创立,而咱们在创立 String 时却写成:

    String s = "abc";

    而非

    String s = new String(“abc”);
    

String s=new String(“abc”); 创立了两个对象

1,在字符串池中创立一个对象(此对象是不能反复的)

2,new 出一个对象。Java 运行环境有一个字符串池,由 String 类保护。执行语句 String s=”abc” 时,首先查看字符串池中是否存在字符串 ”abc”,如果存在则间接将 ”abc” 赋给 s,如果不存在则先在字符串池中新建一个字符串 ”abc”,而后再将其赋给 s。执行语句 String s=new String(“abc”) 时,不论字符串池中是否存在字符串 ”abc”,间接新建一个字符串 ”abc”(留神:新建的字符串 ”abc” 不是在字符串池中),而后将其付给 s。

重载

String 不可变性会带来肯定的效率问题。为 String 对象重载的 + 操作符就是一个例子。重载的意思是,一个操作符在用于特定的类时,被赋予了非凡的意义。操作符 + 能够用来连贯 String:

    public static void main(String[] args) { 
        String name = "码农洞见"; 
        String age = 30;
        String s = "namge:" + name + "age:" + age; 
        System.out.println(s);
    } 

能够设想一下,这段代码是这样工作的:String 可能有一个 append() 办法,它会生成一个新的 String 对象,以蕴含“namge:”与 name 连贯后的字符串。该对象会再创立另一个新的 String 对象,而后与“age:”相连,生成另一个新的对象,依此类推。

这种形式当然是可行的,然而为了生成最终的 String 对象,会产生一大堆须要垃圾回收的两头对象。我猜测,Java 设计者一开始就是这么做的(这也是软件设计中的一个教训:除非你用代码将零碎实现,并让它运行起来,否则你无奈真正理解它会有什么问题),而后他们发现其性能相当蹩脚。

为了解决这个问题,在 JDK5.0 中引入 StringBuilder 类。在这之前用的是 StringBuffer。后者是线程平安的,因而开销也会大些。应用 StringBuilder 进行字符串操作更快一点。如果须要用许多小段的字符串构建一个字符串,那么应该依照下列步骤进行。首先,构建一个空的字符串构建器:

 StringBuilder builder = new StringBuilder(); 

当每次须要增加一部分内容时,就调用 append 办法。

builder.append("abc");
builder.append("123");
...

在须要构建字符串时就调用 toString 办法,将能够失去一个 String 对象,其中蕴含了构建器中的字符序列。

String result = builder.toString();

StringBuilder 提供了丰盛而全面的办法,包含 insert()、replace()、substring(),甚至还有 reverse(),然而最罕用的还是 append() 和 toString()。还有 delete()。

总结

String 是只读字符串,它是一个对象。每次 + 操作隐式在堆上 new 了一个跟原来字符串雷同的 StringBuilder 对象,在调用 append 办法拼接 + 前面的字符串。在应用过程中在细节上要留神效率问题,例如失当地应用 StringBuilder 等。

咱们都晓得 Java 源自于 C ++,Java 设计者认为 C ++ 容许编程人员任意重载操作符是一个很简单的过程,所以没有纳入到 Java 中。然而就像当初看到的 Python 和 C#,它们都有垃圾回收机制,操作符重载也简略易懂。所以说在 Java 中应用操作符重载也并非设想中那么简单。这也是软件设计中的一个教训:除非你用代码将零碎实现,并让它运行起来,否则你无奈真正理解它会有什么问题。

最初的最初

为初学者提供学习指南,为从业者提供参考价值。我深信码农也具备产生洞见的能力。扫描下图二维码关注,学习和交换!

正文完
 0