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

时间:2008-12-02 16:16:24

标签: java string

我是一名学习Java的C ++人。我正在阅读Effective 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)?它是什么让它能够传递像这样的文字?根据我的理解,Java中没有“复制构造函数”概念吗?

23 个答案:

答案 0 :(得分:108)

String是该语言的特殊内置类。仅适用于String ,您应该避免使用

String s = new String("Polish");

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

CaseInsensitiveString cis = new CaseInsensitiveString("Polish");

是正确的(在这种情况下,仅在此情况下)。

答案 1 :(得分:56)

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

为了说明,以下代码将为第一行打印为true,但对于第二行打印为false:

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

答案 2 :(得分: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(因为string将此行为添加到equals)。

你要写的东西,

CaseInsensitiveString cis = "Polish";

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

答案 3 :(得分:19)

String s1="foo";

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

String s2="foo";

这次它会检查“foo”文字是否已经在StringPool中可用,因为它现在存在,所以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在堆中的不同位置创建)

答案 4 :(得分:12)

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

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

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

答案 5 :(得分: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
  • a1a2是对象引用
  • 当显式声明字符串时,会创建新实例并且它们的引用将不相同。

我认为你试图使用casesensitive类是错误的。不管弦乐。您真正关心的是如何显示或比较值。使用另一个类来格式化字符串或进行比较。

即。

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

由于您正在编写课程,您可以根据需要进行比较 - 比较文本值。

答案 6 :(得分:6)

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

答案 7 :(得分:6)

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

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

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

String s = new String("Hello");

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

答案 8 :(得分:4)

  

- 如何让CaseInsensitiveString像String一样运行,所以上面的语句是正确的(使用和w / out扩展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值的原因。

答案 9 :(得分:3)

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

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

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

答案 10 :(得分:3)

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

答案 11 :(得分:3)

CaseInsensitiveString不是String,但它包含StringString字面数字,例如“示例”只能分配给String

答案 12 :(得分:2)

当他们说要写

String s = "Silly";

而不是

String s = new String("Silly");

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

但是当你写作

CaseInsensitiveString cis = new CaseInsensitiveString("Polish");

您没有创建String,而是创建了CaseInsensitiveString类的对象。因此,您需要使用new运算符。

答案 13 :(得分:2)

CaseInsensitiveString和String是不同的对象。你做不到:

CaseInsensitiveString cis = "Polish";

因为“Polish”是一个String,而不是CaseInsensitiveString。如果String扩展了CaseInsensitiveString String,那么你就可以了,但显然它没有。

不要担心这里的建筑,你不会做出不必要的物体。如果你看一下构造函数的代码,它所做的就是存储对你传入的字符串的引用。没有额外的东西被创建。

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

答案 14 :(得分:1)

如果我理解正确,你的问题意味着我们不能通过直接为它赋值来创建一个对象,不要将它限制为java中的String包装类。

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

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

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

类似地,如果解释器在“”中看到某些东西,它将被视为其默认类型的对象,即字符串。这是在后台工作的一些本机代码。

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

答案 15 :(得分:0)

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

String s = new String(“傻”);

String s =“不再傻”;

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

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

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

更新:我错了! 基于向下投票和评论,我测试了这一点并意识到我的理解是错误的 - 新的String(“傻瓜”)确实创建了一个新的字符串,而不是重用现有的字符串。我不清楚为什么会这样(有什么好处?)但是代码比言辞更响亮!

答案 16 :(得分:0)

我只想补充说Java有Copy constructors ...

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

答案 17 :(得分:0)

首先,你不能创建一个从String扩展的类,因为String是一个最终类。并且java管理字符串与其他类不同,所以只能用String做

String s = "Polish";

但是你的类必须调用构造函数。所以,那段代码很好。

答案 18 :(得分:0)

String是一个特殊的类,您可以在没有新的Sring部分的情况下创建它们

相同

int x = y;

char c;

答案 19 :(得分:0)

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

String foo = "text";

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

MyString bar = "text";

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

答案 20 :(得分:0)

java中的字符串是不可变且区分大小写的基本法则。

答案 21 :(得分:0)

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

str1和str2都属于同一个String对象,“foo”,b'coz Java管理StringPool中的字符串,所以如果一个新变量引用相同的String,它不会创建另一个而是指定相同的alerady出现在StringPool中。

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

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

答案 22 :(得分:-1)

Java为您在代码中使用的每个字符串文字创建一个String对象。任何时候使用void main() { new B(); } @proxy abstract class A { A() { a(); b(); c(); } } class B extends A { B() : super(); a() => print('a'); b() => print('b'); c() => print('c'); } ,都与调用""相同。

字符串是复杂的数据,只是像原始数据一样“行动”。字符串文字实际上是对象,即使我们假装它们是原始文字,如new String()等。因此字符串“literal”6, 6.0, 'c',返回一个值为"text"的新String对象。因此,请致电

char[] value = {'t','e','x','t}

实际上类似于调用

new String("text"); 

希望从这里开始,您可以看到为什么您的教科书认为这是多余的。

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

这是一个有趣的阅读,可能会激发一些洞察力。对于初学者来说,阅读和尝试理解它也很棒,因为代码演示了非常专业和符合惯例的代码。

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