Java字符串:“ String s = new String(“ silly”);”


85

我是学习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这样,使上面的语句可以正常运行(带有和不带有extend String)?字符串到底有什么用,它能够像这样传递文字就可以了吗?据我了解,Java中没有“复制构造函数”的概念吗?


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

Answers:


110

String是该语言的特殊内置类。它是为String只有在你应该避免说

String s = new String("Polish");

因为文字"Polish"已经是类型String,并且您正在创建一个多余的对象。对于其他班级,

CaseInsensitiveString cis = new CaseInsensitiveString("Polish");

是正确的(并且仅在这种情况下)要做的事情。


8
第二点是,编译器可以使用带有字符串文字的internig / propagating-stuff,这对于像“ String(literal)”这样的怪异函数调用不一定是可能的
Tetha

4
由于永远不要调用new String("foo"),您可能会问自己为什么构造函数new String(String)存在。答案是有时可以很好地利用它: stackoverflow.com/a/390854/1442870
连线2012年

仅供参考,泰莎(Tetha)在上面的评论中,单词“ interning”的拼写错误,就像字符串interning一样
罗勒·布尔克

56

我相信使用文字形式(即“ foo”而不是新的String(“ foo”))的主要好处是,所有String文字均由VM“插入”。换句话说,它被添加到池中,以便创建相同字符串的任何其他代码将使用池化的String而不是创建新实例。

为了说明这一点,以下代码将在第一行中显示true,但在第二行中显示false:

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

14
同样,这就是为什么FindBugs告诉您将“ new Integer(N)”替换为“ Integer.valueOf(N)”的原因-因为存在这种中介。
Paul Tomblin

6
您还应该添加“ foo” == new String(“ foo”)。intern()
James Schek

4
更正:字符串文字被编译器而不是VM指向相同的引用。VM可能会在运行时实习String对象,因此第二行可能返回true或false!
Craig P. Motlin

1
@Motlin:我不确定这是正确的。String类的javadoc要求“插入所有文字字符串和字符串值常量表达式”。因此,我们可以依赖被截断的文字,这意味着“ foo” ==“ foo”应该始终返回true。

3
@Leigh是的,字面量可以插入,但由编译器而不是VM插入。Motlin得出的结论是,VM可能会附加内部字符串,因此,new String(“ bar”)== new String(“ bar”)-> false是否取决于实现。
亚伦·曼帕

30

字符串在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(因为字符串将此行为添加到equals)。

你想写的东西

CaseInsensitiveString cis = "Polish";

之所以不起作用,是因为您认为引号是对象的某种短路构造函数,而实际上这仅适用于普通的旧java.lang.Strings。


+1表示不变性,对我而言,这是Java编写strA = strB而不是的真正原因strA = new String(strB)。实际上,它与字符串实习没有太大关系。
kritzikratzi

它们不通过引用计数进行处理。JLS需要字符串池。
罗恩侯爵,

20
String s1="foo";

文字将进入池,而s1将引用。

String s2="foo";

这次它将检查String池中是否已经有“ foo”文字,因为它现在已经存在,因此s2将引用相同的文字。

String s3=new String("foo");

首先在StringPool中创建“ foo”文字,然后通过字符串arg构造函数创建String Object,即由于通过新运算符创建对象而在堆中创建“ 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在堆中的不同位置创建)


1
不要对未引用的文本使用引号格式,而对代码使用代码格式。
罗恩侯爵,

12

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

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

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


7

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
  • a1并且a2是对象引用
  • 当明确声明字符串时,将创建新实例,并且它们的引用将不同。

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

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

由于要组成类,因此可以使比较执行所需的操作-比较文本值。


编译器创建字符串池,而不是JVM。变量不包含对象,它们引用它们。永远不会回收字符串文字的字符串池空间。
罗恩侯爵,

6

你不能 Java中双引号中的内容被编译器专门识别为Strings,不幸的是,您不能覆盖它(或扩展java.lang.String-它已声明final)。


最后是这里的红鲱鱼。即使String不是最终的,在这种情况下,扩展String也无济于事。
达伦(Darron)

1
我认为您的意思是您不能覆盖此。:)
Craig P. Motlin

@Motlin:哈!你可能是正确的。我想我读过某个地方的Java旨在通过故意排除类似这样的有趣事情来阻止任何人做任何愚蠢的事
丹·文顿

6

回答问题的最佳方法是使您熟悉“字符串常量池”。在Java中,字符串对象是不可变的(即,一旦初始化它们的值就无法更改),因此,在编辑字符串对象时,您最终会创建一个新的已编辑字符串对象,其中旧对象只是在称为“字符串”的特殊内存区域中浮动。恒定池”。通过创建一个新的字符串对象

String s = "Hello";

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

String s = new String("Hello");

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


4

-如何使CaseInsensitiveString表现得像String,所以上面的语句还可以(带有和不带有扩展String)?字符串到底有什么用,它能够像这样传递文字就可以了吗?据我了解,Java中没有“复制构造函数”的概念,对吗?

从第一点讲已经足够了。“波兰语”是字符串文字,不能分配给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

当非常频繁地使用相似的对象并且创建成本很高时,这很有用。

字符串类文档指出该类使用内部池

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

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

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

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

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


3

在第一个示例中,您将创建一个字符串“ silly”,然后将其作为参数传递给另一个String的副本构造函数,这将使第二个String与第一个String相同。由于Java字符串是不可变的(经常使习惯于C字符串的人受到伤害),这是不必要的资源浪费。您应该改用第二个示例,因为它跳过了一些不必要的步骤。

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

同样,您的CaseInsensitiveString应该实现CharSequence接口以及可能的Serializable和Comparable接口。当然,如果实现Comparable,则还应该覆盖equals()和hashCode()。


3

仅仅因为您String在课堂上有这个词,并不意味着您获得了内置String课程的所有特殊功能。


3

CaseInsensitiveString不是,String尽管其中包含String。甲String文字例如“例如”只能分配给一个String


2

CaseInsensitiveString和String是不同的对象。您不能:

CaseInsensitiveString cis = "Polish";

因为“波兰语”是一个字符串,而不是CaseInsensitiveString。如果String扩展了CaseInsensitiveString String,那么您可以,但显然不行。

并且不用担心这里的构造,您将不会制作不必要的对象。如果您看一下构造函数的代码,那么它所做的就是存储对传入的字符串的引用。没有任何额外的创建。

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


即使您扩展String也不行。您需要使用String来扩展CaseInsensitiveString。
达伦(Darron)

无论哪种情况,它都是不可能的,要么是因为内置了字符串,要么是因为声明了final
luke

2

当他们说写

String s = "Silly";

代替

String s = new String("Silly");

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

但是当你写

CaseInsensitiveString cis = new CaseInsensitiveString("Polish");

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


1

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

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

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

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

同样,如果解释器看到“”中的内容,则将其视为默认类型的对象,即字符串。这是一些在后台运行的本机代码。

感谢麻省理工学院的视频讲座课程6.00,在那里我得到了这个答案的提示。


0

在Java中,语法“文本”创建类java.lang.String的实例。分配:

String foo = "text";

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

MyString bar = "text";

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



0

我只是补充说Java具有Copy构造函数...

好吧,这是一个普通构造函数,其对象类型与参数相同。


2
这是一种设计模式,而不是语言构造。在Java中,很少有使用复制构造函数的用途,因为所有内容始终都是“按引用”的,并且每个对象只有一个副本。实际上,制作副本确实会引起很多问题。
比尔K

0

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

String s = new String(“ silly”);

字符串s =“不再傻”;

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

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

我在这里看不到区别。但是,对于您自己的类(不是不变的),此行为无关紧要,您必须调用构造函数。

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


0

字符串是特殊的类之一,您可以在其中创建新类而不需要新的Sring部分

和...一样

int x = y;

要么

字符c;


0

Java中的字符串不可变且区分大小写是一条基本定律。


0
 String str1 = "foo"; 
 String str2 = "foo"; 

str1和str2都属于同一个String对象,即“ foo”,b'coz Java在StringPool中管理字符串,因此,如果新变量引用相同的String,则它不会创建另一个变量,而是分配StringPool中存在的相同名称。

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

在这里,str1和str2都属于不同的对象,所以b'coz new String()会强制创建一个新的String对象。


-1

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

字符串是像原始数据一样“起作用”的复杂数据。字符串文字实际上是对象,即使我们假设它们是原始文字(如6, 6.0, 'c',等等)也是如此。因此,字符串“ literal”"text"将返回一个带有value的新String对象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


每当使用“”时,它就是对常量池中相同字符串的引用。
罗恩侯爵,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.