我是一名学习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";
为什么第一个陈述好吗?不应该是
CaseInsensitiveString cis = "Polish";
如何使CaseInsensitiveString
的行为与String
相同,以便上述声明正常(有无扩展String
)?它是什么让它能够传递像这样的文字?根据我的理解,Java中没有“复制构造函数”概念吗?
答案 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";
字符串比较取决于您所比较的内容
String a1 = new String("A");
String a2 = new String("A");
a1
不等于a2
a1
和a2
是对象引用我认为你试图使用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
,但它包含String
。 String
字面数字,例如“示例”只能分配给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