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

IT小君   2021-09-15T00:07:25

我是一个学习 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)?String 是什么让它可以像这样传递一个文字?根据我的理解,Java 中没有“复制构造函数”的概念?

点击广告,支持我们为你提供更好的服务
评论(23)
IT小君

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

String s = new String("Polish");

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

CaseInsensitiveString cis = new CaseInsensitiveString("Polish");

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

2021-09-15T00:07:27   回复
IT小君

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

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

System.out.println("foo" == "foo");
System.out.println(new String("bar") == new String("bar"));
2021-09-15T00:07:27   回复
IT小君

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

如果你写

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

然后 s 和 t 实际上指的是同一个对象,而 s==t 将返回真,因为“==”对于读取的对象“是同一个对象”(或者可以,无论如何,我不确定这是否是实际的语言规范或只是编译器实现的一个细节 - 所以依赖这个可能不安全)。

如果你写

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

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

你想写的东西,

CaseInsensitiveString cis = "Polish";

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

2021-09-15T00:07:28   回复
IT小君
String s1="foo";

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

String s2="foo";

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

String s3=new String("foo");

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

2021-09-15T00:07:28   回复
IT小君

Strings 在 Java 中很特殊——它们是不可变的,并且字符串常量会自动变成String对象。

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

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

2021-09-15T00:07:28   回复
IT小君

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是对象引用
  • 显式声明 string 时,将创建新实例,并且它们的引用将不同。

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

IE

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

由于您正在编班,因此您可以根据需要进行比较 - 比较文本值。

2021-09-15T00:07:28   回复
IT小君

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

String s = "Hello";

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

String s = new String("Hello");

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

2021-09-15T00:07:29   回复
IT小君

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

2021-09-15T00:07:29   回复
IT小君

- 我如何让 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

当非常频繁地使用类似对象并且创建成本昂贵时,这很有用。

字符串类文档说明该类使用内部池

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

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

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

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

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

2021-09-15T00:07:29   回复
IT小君

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

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

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

2021-09-15T00:07:29   回复
IT小君

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

2021-09-15T00:07:29   回复
IT小君

CaseInsensitiveString不是 aString虽然它包含String. String文字例如“例如”只能分配给一个String

2021-09-15T00:07:30   回复
IT小君

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

CaseInsensitiveString cis = "Polish";

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

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

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

2021-09-15T00:07:30   回复
IT小君

当他们说要写

String s = "Silly";

代替

String s = new String("Silly");

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

但是当你写

CaseInsensitiveString cis = new CaseInsensitiveString("Polish");

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

2021-09-15T00:07:30   回复
IT小君

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

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

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

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

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

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

2021-09-15T00:07:30   回复
IT小君

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

String foo = "text";

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

MyString bar = "text";

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

2021-09-15T00:07:31   回复
IT小君

首先,你不能创建一个从 String 扩展的类,因为 String 是一个 final 类。并且 java 以不同于其他类的方式管理字符串,因此您只能使用 String

String s = "Polish";

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

2021-09-15T00:07:31   回复
IT小君

我只想补充一点,Java 有复制构造函数......

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

2021-09-15T00:07:31   回复
IT小君

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

String s = new String("傻");

String s = "不再傻了";

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

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

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

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

2021-09-15T00:07:31   回复
IT小君

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

它和

整数 x = y;

或者

字符 c;

2021-09-15T00:07:31   回复
IT小君

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

2021-09-15T00:07:32   回复
IT小君
 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 对象。

2021-09-15T00:07:32   回复
IT小君

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

字符串是复杂的数据,就像原始数据一样“表现”。字符串文字实际上是对象,即使我们假装它们是原始文字,例如6, 6.0, 'c',等等。因此字符串“文字”"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

2021-09-15T00:07:32   回复