【问题标题】:Java Strings: "String s = new String("silly");"Java 字符串:“String s = new String("silly");”
【发布时间】:2010-09-24 23:20:50
【问题描述】:

我是一名学习 Java 的 C++ 人。我正在阅读有效的 Java,有些东西让我很困惑。它说永远不要写这样的代码:

String s = new String("silly");

因为它会创建不必要的 String 对象。但它应该这样写:

String s = "No longer silly";

到目前为止还不错......但是,鉴于这个类:

public final class CaseInsensitiveString {
    private String s;
    public CaseInsensitiveString(String s) {
        if (s == null) {
            throw new NullPointerException();
        }
        this.s = s;
    }
    :
    :
}

CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
String s = "polish";
  1. 为什么第一个语句没问题?不应该吗

    CaseInsensitiveString cis = "Polish";

  2. 如何使CaseInsensitiveString 表现得像String 这样上述语句就可以(有和没有扩展String)?是什么让 String 能够像这样传递文字是可以的?据我了解,Java 中没有“复制构造函数”的概念?

【问题讨论】:

  • 字符串 str1 = "foo";字符串 str2 = "foo"; str1 和 str2 都属于同一个字符串对象,“foo”,b'coz for Java 在 StringPool 中管理字符串,所以一个新变量引用同一个字符串,它不会创建另一个变量,而是分配存在于字符串池。但是当我们这样做时: String str1 = new String("foo");字符串 str2 = new String("foo");这里str1和str2都属于不同的Object,b'coz new String()强行创建了一个新的String Object。

标签: java string


【解决方案1】:

String 是语言的一个特殊的内置类。它仅适用于String,您应该避免在其中说

String s = new String("Polish");

因为文字 "Polish" 已经是 String 类型,并且您正在创建一个额外的不必要的对象。对于任何其他类,说

CaseInsensitiveString cis = new CaseInsensitiveString("Polish");

是正确的(在这种情况下也是唯一的)要做的事情。

【讨论】:

  • 第二点是编译器可以用字符串文字来想象内部/传播的东西,这对于像“String(literal)”这样的奇怪函数调用不一定是可能的
  • 既然你永远不应该调用new String("foo"),你可能会问自己为什么构造函数new String(String)存在。答案是它有时会有很好的用途:stackoverflow.com/a/390854/1442870
  • 仅供参考,Tetha 上面的评论拼错了“interning”这个词,如string interning
【解决方案2】:

我相信使用字面量形式(即“foo”而不是 new String(“foo”))的主要好处是所有字符串字面量都被 VM 'interned'。换句话说,它被添加到池中,这样创建相同字符串的任何其他代码都将使用池化字符串,而不是创建新实例。

为了说明,下面的代码将打印第一行为真,第二行为假:

System.out.println("foo" == "foo");
System.out.println(new String("bar") == new String("bar"));

【讨论】:

  • 同样,这就是 FindBugs 告诉你用“Integer.valueOf(N)”替换“new Integer(N)”的原因——因为那个实习。
  • 你还应该加上 "foo" == new String("foo").intern()
  • 更正:字符串文字被编译器指向同一个引用,而不是虚拟机。 VM 可能会在运行时实习 String 对象,因此第二行可能返回 true 或 false!
  • @Motlin:我不确定这是否正确。 String 类的 javadoc 要求“所有文字字符串和字符串值的常量表达式都是内部的”。所以我们可以依赖被实习的文字,这意味着 "foo" == "foo" 应该总是返回 true。
  • @Leigh 是的,文字是实习的,但由编译器而不是虚拟机。 Motlin 的意思是,VM 可以另外实习字符串,因此,无论 new String("bar") == new String("bar") --> false 是否依赖于实现。
【解决方案3】:

字符串在 java 中被特殊对待,它们是不可变的,因此通过引用计数来处理它们是安全的。

如果你写

String s = "Polish";
String t = "Polish";

那么 s 和 t 实际上指的是同一个对象,并且 s==t 将返回 true,因为“==”表示对象读取“是同一个对象”(或者可以,无论如何,我不确定这是否是实际语言规范的一部分,或者只是编译器实现的一个细节——所以依赖这个可能是不安全的)。

如果你写

String s = new String("Polish");
String t = new String("Polish");

然后 s!=t (因为您明确地创建了一个新字符串)虽然 s.equals(t) 将返回 true (因为 string 将此行为添加到 equals )。

你想写的东西,

CaseInsensitiveString cis = "Polish";

无法工作,因为您认为引号是对象的某种短路构造函数,而实际上这只适用于普通的旧 java.lang.Strings。

【讨论】:

  • +1 用于提及不变性,对我而言,这是在 java 中写strA = strB 而不是strA = new String(strB) 的真正原因。它确实与字符串实习无关。
  • 它们不被引用计数处理。 JLS 需要字符串池。
【解决方案4】:
String s1="foo";

literal 将进入池中,s1 将引用。

String s2="foo";

这一次它将检查 StringPool 中是否已经存在“foo”字面量,因为它现在存在,所以 s2 将引用相同的字面量。

String s3=new String("foo");

“foo”字面量将首先在 StringPool 中创建,然后通过字符串 arg 构造函数创建 String 对象,即由于通过 new 运算符创建对象而在堆中创建“foo”,然后 s3 将引用它。

String s4=new String("foo");

和s3一样

所以System.out.println(s1==s2);// **true** due to literal comparison.

System.out.println(s3==s4);// **false** due to object

比较(s3和s4在堆的不同位置创建)

【讨论】:

  • 不要对未引用的文本使用引号格式,对代码使用代码格式。
【解决方案5】:

Strings 在 Java 中是特殊的 - 它们是不可变的,字符串常量会自动转换为 String 对象。

您的SomeStringClass cis = "value" 示例无法应用于任何其他类。

你也不能扩展String,因为它被声明为final,这意味着不允许子类化。

【讨论】:

    【解决方案6】:

    Java 字符串很有趣。看起来回复已经涵盖了一些有趣的观点。这是我的两分钱。

    字符串是不可变的(您永远无法更改它们)

    String x = "x";
    x = "Y"; 
    
    • 第一行将创建一个变量 x,其中包含字符串值“x”。 JVM会在它的字符串值池中查看“x”是否存在,如果存在则将变量x指向它,如果不存在则创建它然后进行赋值
    • 第二行将删除对“x”的引用,并查看字符串值池中是否存在“Y”。如果它确实存在,它将分配它,如果它不存在,它将首先创建它然后分配。无论是否使用字符串值,都会回收字符串值池中的内存空间。

    字符串比较取决于您要比较的内容

    String a1 = new String("A");
    
    String a2 = new String("A");
    
    • a1 不等于 a2
    • a1a2 是对象引用
    • 显式声明字符串时,会创建新实例,并且它们的引用将不同。

    我认为您在尝试使用不区分大小写的类时走错了路。别管弦了。您真正关心的是如何显示或比较这些值。使用另一个类来格式化字符串或进行比较。

    TextUtility.compare(string 1, string 2) 
    TextUtility.compareIgnoreCase(string 1, string 2)
    TextUtility.camelHump(string 1)
    

    由于您正在编写课程,因此您可以让比较做您想做的事情 - 比较文本值。

    【讨论】:

    • 编译器创建字符串池,而不是JVM。变量不包含对象,它们引用它们。字符串字面量的字符串池空间永远不会被回收。
    【解决方案7】:

    回答您的问题的最佳方式是让您熟悉“字符串常量池”。在java中,字符串对象是不可变的(即它们的值一旦被初始化就不能改变),所以当编辑一个字符串对象时,你最终会创建一个新的编辑字符串对象,而旧对象只是漂浮在一个特殊的内存中,称为“字符串”恒定池”。通过

    创建一个新的字符串对象
    String s = "Hello";
    

    只会在池中创建一个字符串对象,引用s会引用它,但是通过使用

    String s = new String("Hello");
    

    您创建了两个字符串对象:一个在池中,另一个在堆中。引用将引用堆中的对象。

    【讨论】:

      【解决方案8】:

      你不能。 Java中双引号中的东西被编译器特别识别为字符串,不幸的是你不能覆盖它(或扩展java.lang.String - 它被声明为final)。

      【讨论】:

      • final 在这里是一个红鲱鱼。即使 String 不是最终的,在这种情况下扩展 String 也无济于事。
      • 我认为你的意思是幸运的是你不能覆盖这个。 :)
      • @Motlin:哈!你可能是对的。我想我在某处读到 Java 旨在阻止任何人做任何愚蠢的事情,故意排除任何有趣的事情......
      【解决方案9】:

      - 我如何使 CaseInsensitiveString 表现得像 String 一样,所以上面的语句是可以的(有和没有扩展 String)?是什么让 String 可以像这样传递文字?据我了解,Java 中没有“复制构造函数”的概念吗?

      从第一点就说得够多了。 "Polish" 是字符串文字,不能分配给 CaseInsentiviveString 类。

      关于第二点

      虽然您不能创建新的文字,但您可以按照该书的第一项“类似”方法,因此以下陈述是正确的:

          // Lets test the insensitiveness
          CaseInsensitiveString cis5 = CaseInsensitiveString.valueOf("sOmEtHiNg");
          CaseInsensitiveString cis6 = CaseInsensitiveString.valueOf("SoMeThInG");
      
          assert cis5 == cis6;
          assert cis5.equals(cis6);
      

      这是代码。

      C:\oreyes\samples\java\insensitive>type CaseInsensitiveString.java
      import java.util.Map;
      import java.util.HashMap;
      
      public final class CaseInsensitiveString  {
      
      
          private static final Map<String,CaseInsensitiveString> innerPool 
                                      = new HashMap<String,CaseInsensitiveString>();
      
          private final String s;
      
      
          // Effective Java Item 1: Consider providing static factory methods instead of constructors
          public static CaseInsensitiveString valueOf( String s ) {
      
              if ( s == null ) {
                  return null;
              }
              String value = s.toLowerCase();
      
              if ( !CaseInsensitiveString.innerPool.containsKey( value ) ) {
                   CaseInsensitiveString.innerPool.put( value , new CaseInsensitiveString( value ) );
               }
      
               return CaseInsensitiveString.innerPool.get( value );   
          }
      
          // Class constructor: This creates a new instance each time it is invoked.
          public CaseInsensitiveString(String s){
              if (s == null) {
                  throw new NullPointerException();
               }         
               this.s = s.toLowerCase();
          }
      
          public boolean equals( Object other ) {
               if ( other instanceof CaseInsensitiveString ) {
                    CaseInsensitiveString otherInstance = ( CaseInsensitiveString ) other;
                   return this.s.equals( otherInstance.s );
               }
      
               return false;
          }
      
      
          public int hashCode(){
               return this.s.hashCode();
          }
      

      // 使用“assert”关键字测试类

          public static void main( String [] args ) {
      
              // Creating two different objects as in new String("Polish") == new String("Polish") is false
              CaseInsensitiveString cis1 = new CaseInsensitiveString("Polish");
              CaseInsensitiveString cis2 = new CaseInsensitiveString("Polish");
      
              // references cis1 and cis2 points to differents objects.
              // so the following is true
              assert cis1 !=  cis2;      // Yes they're different
              assert cis1.equals(cis2);  // Yes they're equals thanks to the equals method
      
              // Now let's try the valueOf idiom
              CaseInsensitiveString cis3 = CaseInsensitiveString.valueOf("Polish");
              CaseInsensitiveString cis4 = CaseInsensitiveString.valueOf("Polish");
      
              // References cis3 and cis4 points to same  object.
              // so the following is true
              assert cis3 == cis4;      // Yes they point to the same object
              assert cis3.equals(cis4); // and still equals.
      
              // Lets test the insensitiveness
              CaseInsensitiveString cis5 = CaseInsensitiveString.valueOf("sOmEtHiNg");
              CaseInsensitiveString cis6 = CaseInsensitiveString.valueOf("SoMeThInG");
      
              assert cis5 == cis6;
              assert cis5.equals(cis6);
      
              // Futhermore
              CaseInsensitiveString cis7 = CaseInsensitiveString.valueOf("SomethinG");
              CaseInsensitiveString cis8 = CaseInsensitiveString.valueOf("someThing");
      
              assert cis8 == cis5 && cis7 == cis6;
              assert cis7.equals(cis5) && cis6.equals(cis8);
          }
      
      }
      
      C:\oreyes\samples\java\insensitive>javac CaseInsensitiveString.java
      
      
      C:\oreyes\samples\java\insensitive>java -ea CaseInsensitiveString
      
      C:\oreyes\samples\java\insensitive>
      

      也就是说,创建一个 CaseInsensitiveString 对象的内部池,并从那里返回相应的实例。

      这样,“==”运算符为代表相同值的两个对象引用返回true

      当相似的对象被频繁使用并且创建成本昂贵时,这很有用。

      字符串类文档指出该类使用an internal pool

      类不完整,当我们尝试在实现 CharSequence 接口时遍历对象的内容时会出现一些有趣的问题,但是这段代码足以说明如何应用 Book 中的项目。

      重要的是要注意,通过使用 internalPool 对象,引用不会被释放,因此不会被垃圾回收,如果创建了很多对象,这可能会成为一个问题。

      它适用于 String 类,因为它被密集使用并且池仅由“interned”对象构成。

      它也适用于布尔类,因为只有两个可能的值。

      最后,这也是 Integer 类中的 valueOf(int) 限制为 -128 到 127 个 int 值的原因。

      【讨论】:

        【解决方案10】:

        在您的第一个示例中,您正在创建一个“愚蠢”的字符串,然后将其作为参数传递给另一个字符串的复制构造函数,这会生成与第一个字符串相同的第二个字符串。由于 Java 字符串是不可变的(这经常会刺痛习惯于 C 字符串的人),这是对资源的不必要浪费。您应该改用第二个示例,因为它跳过了几个不必要的步骤。

        但是,字符串文字不是 CaseInsensitiveString,因此您无法在上一个示例中执行您想要的操作。此外,没有办法像在 C++ 中那样重载强制转换运算符,因此实际上没有办法做你想做的事。您必须改为将其作为参数传递给您的类的构造函数。当然,我可能只是使用 String.toLowerCase() 并完成它。

        此外,您的 CaseInsensitiveString 应该实现 CharSequence 接口以及可能的 Serializable 和 Comparable 接口。当然,如果你实现了 Comparable,你也应该重写 equals() 和 hashCode()。

        【讨论】:

          【解决方案11】:

          CaseInsensitiveString 不是String,尽管它包含StringString 文字,例如“示例”只能分配给 String

          【讨论】:

            【解决方案12】:

            CaseInsensitiveString 和 String 是不同的对象。你不能这样做:

            CaseInsensitiveString cis = "Polish";
            

            因为“Polish”是一个字符串,而不是不区分大小写的字符串。如果 String 扩展了 CaseInsensitiveString String 那么你就可以了,但显然不是。

            不用担心这里的构造,您不会制作不必要的对象。如果您查看构造函数的代码,它所做的只是存储对您传入的字符串的引用。没有创建任何额外内容。

            在 String s = new String("foobar") 的情况下,它正在做一些不同的事情。您首先创建文字字符串“foobar”,然后通过从中构造一个新字符串来创建它的副本。无需创建该副本。

            【讨论】:

            • 即使你扩展了 String 这也行不通。您需要 String 来扩展 CaseInsensitiveString。
            • 在任何一种情况下都是不可能的,要么是因为 string 是内置的,要么是因为它声明为 final
            【解决方案13】:

            仅仅因为您的班级中有String 这个词,并不意味着您获得了内置String 班级的所有特殊功能。

            【讨论】:

              【解决方案14】:

              当他们说要写时

              String s = "Silly";
              

              而不是

              String s = new String("Silly");
              

              它们在创建 String 对象时是这样的,因为上述两个语句都创建了一个 String 对象,但新的 String() 版本创建了两个 String 对象:一个在堆中,另一个在字符串常量池中。因此使用更多的内存。

              但是当你写的时候

              CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
              

              您不是在创建字符串,而是在创建 CaseInsensitiveString 类的对象。因此,您需要使用 new 运算符。

              【讨论】:

                【解决方案15】:

                如果我理解正确,您的问题意味着为什么我们不能通过直接为其分配值来创建对象,我们不要将其限制为 java 中的 String 类的 Wrapper。

                回答这个问题,我只想说,纯粹的面向对象编程语言有一些结构,它说,单独编写的所有文字都可以直接转换为给定类型的对象。

                这恰恰意味着,如果解释器看到 3,它将被转换为 Integer 对象,因为 integer 是为此类文字定义的类型。

                如果解释器在单引号中看到任何东西,例如“a”,它将直接创建一个字符类型的对象,您不需要指定它,因为语言为它定义了默认的字符类型对象。

                同样,如果解释器在 "" 中看到某些内容,它将被视为其默认类型的对象,即字符串。这是一些在后台运行的原生代码。

                感谢 MIT 视频讲座课程 6.00,我得到了这个答案的提示。

                【讨论】:

                  【解决方案16】:

                  在 Java 中,语法“text”创建类 java.lang.String 的实例。作业:

                  String foo = "text";
                  

                  是一个简单的赋值,不需要复制构造函数。

                  MyString bar = "text";
                  

                  无论你做什么都是非法的,因为 MyString 类既不是 java.lang.String 也不是 java.lang.String 的超类。

                  【讨论】:

                    【解决方案17】:

                    首先,您不能创建从 String 扩展的类,因为 String 是最终类。 Java 对字符串的管理方式与其他类不同,因此只有使用 String 才能做到

                    String s = "Polish";
                    

                    但是对于你的类,你必须调用构造函数。所以,那个代码没问题。

                    【讨论】:

                      【解决方案18】:

                      我只想补充一点,Java 有 Copy constructors...

                      嗯,这是一个普通的构造函数,具有与参数相同类型的对象。

                      【讨论】:

                      • 这是一种设计模式,而不是一种语言结构。在 Java 中,复制构造函数很少有有趣的用途,因为一切总是“通过引用”,并且每个对象只有一个副本。事实上,复制确实会引起很多问题。
                      【解决方案19】:

                      在 JDK 的大多数版本中,这两个版本是相同的:

                      String s = new String("silly");

                      String s = "不再傻";

                      因为字符串是不可变的,编译器会维护一个字符串常量列表,如果你尝试创建一个新的常量,将首先检查字符串是否已经定义。如果是,则返回对现有不可变字符串的引用。

                      澄清一下-当您说“String s =”时,您正在定义一个占用堆栈空间的新变量-那么无论您说“不再傻”还是新字符串(“傻”)都会发生完全相同的事情- 一个新的常量字符串被编译到您的应用程序中,并且引用指向它。

                      我没有看到这里的区别。然而对于你自己的类,它不是不可变的,这种行为是无关紧要的,你必须调用你的构造函数。

                      更新:我错了! 基于否决票和附加评论,我对此进行了测试并意识到我的理解是错误的 - new String("Silly") 确实创建了一个新字符串,而不是重用现有字符串。我不清楚为什么会这样(有什么好处?)但代码胜于雄辩!

                      【讨论】:

                        【解决方案20】:

                        String 是一种特殊的类,您可以在其中创建它们而无需新的 Sring 部分

                        和这个一样

                        int x = y;

                        字符 c;

                        【讨论】:

                          【解决方案21】:

                          java中的字符串是不可变的和区分大小写的,这是一个基本规律。

                          【讨论】:

                            【解决方案22】:
                             String str1 = "foo"; 
                             String str2 = "foo"; 
                            

                            str1 和 str2 都属于同一个字符串对象,"foo",b'coz Java 在 StringPool 中管理字符串,所以如果一个新变量引用同一个字符串,它不会创建另一个变量,而是分配同一个字符串存在于 StringPool 中。

                             String str1 = new String("foo"); 
                             String str2 = new String("foo");
                            

                            这里str1和str2都属于不同的Object,b'coz new String()强制创建一个新的String Object。

                            【讨论】:

                              【解决方案23】:

                              Java 为您在代码中使用的每个字符串文字创建一个 String 对象。任何时候使用"",都和调用new String()一样。

                              字符串是复杂的数据,就像原始数据一样“行动”。字符串字面量实际上是对象,即使我们假装它们是原始字面量,例如6, 6.0, 'c', 等。所以字符串“字面量”"text" 返回一个新的字符串对象,其值为char[] value = {'t','e','x','t}。因此,调用

                              new String("text"); 
                              

                              实际上类似于调用

                              new String(new String(new char[]{'t','e','x','t'}));
                              

                              希望从这里,您可以了解为什么您的教科书认为这是多余的。

                              供参考,这里是String的实现:http://www.docjar.com/html/api/java/lang/String.java.html

                              读起来很有趣,可能会激发一些见解。也非常适合初学者阅读和尝试理解,因为代码演示了非常专业且符合约定的代码。

                              另一个很好的参考是关于字符串的 Java 教程: http://docs.oracle.com/javase/tutorial/java/data/strings.html

                              【讨论】:

                              • 任何时候使用 "" 都是对常量池中 same 字符串的引用。
                              猜你喜欢
                              • 2012-05-26
                              • 1970-01-01
                              • 2011-01-29
                              • 2012-03-27
                              • 1970-01-01
                              • 2015-11-21
                              • 2013-01-23
                              • 1970-01-01
                              • 1970-01-01
                              相关资源
                              最近更新 更多