这可能是有史以来最愚蠢的问题,但我认为对于Java新手来说这很令人困惑。
- 有人可以澄清什么是不变的吗?
- 为什么是
String
一成不变的? - 不可变对象的优缺点是什么?
- 为什么诸如
StringBuilder
String之类的可变对象优先于String,反之亦然?
一个很好的例子(在Java中)将不胜感激。
这可能是有史以来最愚蠢的问题,但我认为对于Java新手来说这很令人困惑。
String
一成不变的?StringBuilder
String之类的可变对象优先于String,反之亦然?一个很好的例子(在Java中)将不胜感激。
Answers:
不可变是指一旦对象的构造函数完成执行,该实例将无法更改。
这很有用,因为这意味着您可以传递对对象的引用,而不必担心别人会更改其内容。尤其是在处理并发时,永远不变的对象没有锁定问题
例如
class Foo
{
private final String myvar;
public Foo(final String initialValue)
{
this.myvar = initialValue;
}
public String getValue()
{
return this.myvar;
}
}
Foo
不必担心调用方getValue()
可能会更改字符串中的文本。
如果您想象与相似的类Foo
,但是成员带有StringBuilder
而不是String
,则可以看到的调用者getValue()
将能够更改实例的StringBuilder
属性Foo
。
还要注意您可能会发现的各种不变性:埃里克·利珀特(Eric Lippert)撰写了一篇关于此的博客文章。基本上,您可以拥有其接口是不可变的对象,但是在幕后是实际的可变私有状态(因此不能在线程之间安全地共享)。
不变的对象是不能更改内部字段(或至少影响其外部行为的所有内部字段)的对象。
不可变的字符串有很多优点:
性能:请执行以下操作:
String substring = fullstring.substring(x,y);
substring()方法的基础C可能是这样的:
// Assume string is stored like this:
struct String { char* characters; unsigned int length; };
// Passing pointers because Java is pass-by-reference
struct String* substring(struct String* in, unsigned int begin, unsigned int end)
{
struct String* out = malloc(sizeof(struct String));
out->characters = in->characters + begin;
out->length = end - begin;
return out;
}
请注意,无需复制任何字符! 如果String对象是可变的(字符以后可能会更改),则您必须复制所有字符,否则子字符串中字符的更改将在以后的其他字符串中反映出来。
并发性:如果不可变对象的内部结构有效,则它将始终有效。不同的线程不可能在该对象内创建无效状态。因此,不可变对象是Thread Safe。
垃圾收集:垃圾收集器就不可变对象做出逻辑决策要容易得多。
但是,不变性也有缺点:
性能:等一下,我以为您说性能是一成不变的!好吧,有时候是这样,但并非总是如此。采取以下代码:
foo = foo.substring(0,4) + "a" + foo.substring(5); // foo is a String
bar.replace(4,5,"a"); // bar is a StringBuilder
这两行都用字母“ a”代替第四个字符。第二段代码不仅可读性更高,而且速度更快。看一下您将如何为foo做底层代码。子字符串很容易,但是现在因为在空格5处已经有一个字符,并且其他内容可能引用了foo,所以不能仅仅更改它;您必须复制整个字符串(当然,其中某些功能被抽象为真正的基础C语言中的函数,但此处的要点是显示可以在一处执行的代码)。
struct String* concatenate(struct String* first, struct String* second)
{
struct String* new = malloc(sizeof(struct String));
new->length = first->length + second->length;
new->characters = malloc(new->length);
int i;
for(i = 0; i < first->length; i++)
new->characters[i] = first->characters[i];
for(; i - first->length < second->length; i++)
new->characters[i] = second->characters[i - first->length];
return new;
}
// The code that executes
struct String* astring;
char a = 'a';
astring->characters = &a;
astring->length = 1;
foo = concatenate(concatenate(slice(foo,0,4),astring),slice(foo,5,foo->length));
注意,连接被调用了两次,这意味着整个字符串必须循环通过!将此与C代码进行比较bar
:
bar->characters[4] = 'a';
可变的字符串操作显然要快得多。
结论:在大多数情况下,您需要一个不变的字符串。但是,如果您需要在字符串中进行大量附加和插入操作,则需要提高速度的可变性。如果您希望它具有并发安全性和垃圾回收的好处,那么关键是将可变对象保持在方法本地:
// This will have awful performance if you don't use mutable strings
String join(String[] strings, String separator)
{
StringBuilder mutable;
boolean first = true;
for(int i = 0; i < strings.length; i++)
{
if(!first) first = false;
else mutable.append(separator);
mutable.append(strings[i]);
}
return mutable.toString();
}
由于该mutable
对象是本地引用,因此您不必担心并发安全性(只有一个线程接触过它)。而且由于没有在其他任何地方引用它,所以它仅在堆栈中分配,因此一旦函数调用完成就将其释放(您不必担心垃圾回收)。您将获得可变性和不变性的所有性能优势。
Passing pointers because Java is pass-by-reference
Java不是“按值传递”吗?
实际上,如果您使用上面建议的Wikipedia定义,则String并非一成不变的。
String的状态确实会改变后期构造。看一下hashcode()方法。字符串将哈希码值缓存在本地字段中,但是直到第一次调用hashcode()时才计算它。哈希码的这种惰性计算将String置于状态发生变化的不可变对象的有趣位置,但如果不使用反射,就无法观察到它已发生变化。
因此,也许不变的定义应该是一个不能被观察到已经改变的对象。
如果状态在创建了不可变对象后发生了变化,但是没有人可以看到(没有反射),该对象是否仍然是不可变的?
不可变的对象是无法通过编程方式更改的对象。它们特别适用于多线程环境或其他环境,其中多个进程可以更改(变异)对象中的值。
但是,为了澄清起见,StringBuilder实际上是一个可变对象,而不是一个不变的对象。常规的Java字符串是不可变的(这意味着一旦创建它,就不能在不更改对象的情况下更改基础字符串)。
例如,假设我有一个名为ColoredString的类,该类具有String值和String颜色:
public class ColoredString {
private String color;
private String string;
public ColoredString(String color, String string) {
this.color = color;
this.string = string;
}
public String getColor() { return this.color; }
public String getString() { return this.string; }
public void setColor(String newColor) {
this.color = newColor;
}
}
在此示例中,ColoredString被认为是可变的,因为您可以更改(变异)其关键属性之一而无需创建新的ColoredString类。例如,假设您有一个GUI应用程序具有多个线程,并且您正在使用ColoredStrings将数据打印到窗口中,那么这可能是不好的原因。如果您有一个ColoredString实例,其创建为
new ColoredString("Blue", "This is a blue string!");
然后,您将期望该字符串始终为“ Blue”。但是,如果另一个线程知道该实例并调用
blueString.setColor("Red");
当您想要一个“蓝色”字符串时,您会突然有了一个“红色”字符串,并且可能出乎意料。因此,在传递对象实例时,几乎总是首选不可变对象。如果确实需要可变对象,那么通常只通过从特定控制区域传递副本来保护对象。
概括地说,在Java中,java.lang.String是一个不可变的对象(创建后就不能更改),而java.lang.StringBuilder是一个可变的对象,因为可以在不创建新实例的情况下对其进行更改。
字符串s1 =“旧字符串”;
//s1 variable, refers to string in memory
reference | MEMORY |
variables | |
[s1] --------------->| "Old String" |
字符串s2 = s1;
//s2 refers to same string as s1
| |
[s1] --------------->| "Old String" |
[s2] ------------------------^
s1 =“新字符串”;
//s1 deletes reference to old string and points to the newly created one
[s1] -----|--------->| "New String" |
| | |
|~~~~~~~~~X| "Old String" |
[s2] ------------------------^
“内存中”的原始字符串没有更改,但是引用变量已更改,因此它引用了新字符串。如果我们没有s2,则“ Old String”仍将存在于内存中,但我们将无法访问它。
“不变”表示您无法更改值。如果您有String类的实例,则您调用的任何似乎修改值的方法实际上都会创建另一个String。
String foo = "Hello";
foo.substring(3);
<-- foo here still has the same value "Hello"
要保留更改,您应该执行以下操作foo = foo.sustring(3);
当您使用集合时,不可变与可变可能会很有趣。考虑如果将可变对象用作map的键,然后更改值(提示:equals
和,请考虑hashCode
)会发生什么。
可能有点晚了,但是为了了解什么是不可变对象,请考虑以下来自新Java 8 Date and Time API(java.time)的示例。您可能知道Java 8中的所有日期对象都是不可变的,因此在以下示例中
LocalDate date = LocalDate.of(2014, 3, 18);
date.plusYears(2);
System.out.println(date);
输出:
2014-03-18
这将打印与初始日期相同的年份,因为plusYears(2)
返回一个新对象,因此旧日期仍然是不变的,因为它是一个不变的对象。创建后,您将无法再对其进行修改,并且date变量仍指向它。
因此,该代码示例应捕获并使用通过调用实例化并返回的新对象plusYears
。
LocalDate date = LocalDate.of(2014, 3, 18);
LocalDate dateAfterTwoYears = date.plusYears(2);
date.toString()…2014-03-18
dateAfterTwoYears.toString()…2016-03-18
我真的很喜欢SCJP Sun Java 5学习认证程序员指南中的解释。
为了使Java的内存使用效率更高,JVM预留了一个特殊的内存区域,称为“字符串常量池”。当编译器遇到String文字时,它将检查池以查看是否已经存在相同的String。如果找到匹配项,则对新文字的引用将指向现有的String,并且不会创建新的String文字对象。
不可变的对象在创建后不能更改其状态。
尽可能使用不可变对象的主要原因有三个,所有这些都将有助于减少在代码中引入的错误的数量:
当您知道对象的状态是不可变的时,还可以在代码中进行其他一些优化,例如,对计算出的哈希值进行缓存,但是这些优化并不十分有趣。
String s1="Hi";
String s2=s1;
s1="Bye";
System.out.println(s2); //Hi (if String was mutable output would be: Bye)
System.out.println(s1); //Bye
s1="Hi"
:s1
创建的对象中带有“ Hi”值。
s2=s1
:s2
参考s1对象创建一个对象。
s1="Bye"
注意:上一个s1
对象的值不会更改,因为它s1
具有String类型,而String类型是不可变类型,而是编译器创建一个带有“ Bye”值的新String对象并对其进行s1
引用。在这里,当我们打印s2
值时,结果将是“ Hi”而不是“ Bye”,因为s2
引用的是s1
具有“ Hi”值的先前对象。
不变是指不变或不可修改。创建字符串对象后,其数据或状态将无法更改
考虑下面的例子,
class Testimmutablestring{
public static void main(String args[]){
String s="Future";
s.concat(" World");//concat() method appends the string at the end
System.out.println(s);//will print Future because strings are immutable objects
}
}
让我们考虑一下波纹管图,
在此图中,您可以看到创建为“未来世界”的新对象。但不能更改“未来”。Because String is immutable
。s
,仍请参阅“未来”。如果您需要调用“未来世界”,
String s="Future";
s=s.concat(" World");
System.out.println(s);//print Future World
为什么字符串对象在Java中是不可变的?
因为Java使用字符串文字的概念。假设有5个参考变量,所有参考变量都引用一个对象“ Future”。如果一个参考变量更改了该对象的值,它将影响所有参考变量。这就是为什么字符串对象在Java中是不可变的。
不变的对象
如果对象的状态在构造后无法更改,则认为该对象是不可变的。对不可变对象的最大依赖已被广泛接受为创建简单,可靠代码的合理策略。
不可变对象在并发应用程序中特别有用。由于它们不能更改状态,因此它们不能被线程干扰破坏或在不一致的状态下观察到。
程序员通常不愿使用不可变的对象,因为他们担心创建新对象而不是就地更新对象的成本。对象创建的影响常常被高估,并且可以被与不变对象相关联的某些效率所抵消。其中包括由于垃圾收集而减少的开销,以及消除了保护可变对象免受损坏所需的代码。
以下小节将介绍一个其实例是可变的类,并从中派生具有不变实例的类。通过这样做,他们给出了这种转换的一般规则,并展示了不可变对象的一些优点。
由于接受的答案无法回答所有问题。11年零6个月后,我被迫给出答案。
有人可以澄清什么是不变的吗?
希望您的意思是固定对象(因为我们可以考虑不可变引用)。
一个对象是不可变的:一旦创建,它们始终代表相同的值(没有任何更改该值的方法)。
为什么是
String
一成不变的?
遵守上面的定义,可以通过查看Sting.java源代码来进行检查。
不可变对象的优缺点是什么?不变类型是:
更安全地避免错误。
更容易理解。
并且为改变做好了准备。
为什么像StringBuilder这样的可变对象要比String更好,反之亦然?
缩小问题的范围为什么我们在编程中需要可变的StringBuilder? 它的常见用法是将大量字符串连接在一起,如下所示:
String s = "";
for (int i = 0; i < n; ++i) {
s = s + n;
}
使用不可变的字符串,会产生许多临时副本-在构建最终字符串的过程中,实际上将字符串的第一个数字(“ 0”)复制了n次,将第二个数字复制了n-1次,依此类推上。即使我们仅串联n个元素,实际上仅花费O(n2)时间即可完成所有复制操作。
StringBuilder旨在最大程度地减少这种复制。当您使用toString()调用最终的String时,它使用简单但聪明的内部数据结构来避免进行任何复制直到最后。
StringBuilder sb = new StringBuilder();
for (int i = 0; i < n; ++i) {
sb.append(String.valueOf(n));
}
String s = sb.toString();
获得良好的性能是我们使用可变对象的原因之一。另一个是方便共享:程序的两个部分可以通过共享通用的可变数据结构来更方便地进行通信。
可以在这里找到更多信息:https : //web.mit.edu/6.005/www/fa15/classes/09-immutability/#useful_immutable_types
不可变对象是创建后无法修改的对象。一个典型的例子是字符串文字。
AD编程语言变得越来越流行,它通过“不变”关键字具有“不变性”的概念。勾选此Dr.Dobb的关于它的文章- http://dobbscodetalk.com/index.php?option=com_myblog&show=Invariant-Strings.html&Itemid=29。它完美地说明了问题。