这可能是有史以来最愚蠢的问题,但我认为对于 Java 新手来说,这很令人困惑。

  1. 有人可以澄清immutable是什么意思吗?
  2. 为什么String不可变的?
  3. 不可变对象的优点/缺点是什么?
  4. 为什么像StringBuilder这样的可变对象比 String 更受欢迎,反之亦然?

一个很好的例子(在 Java 中)将非常感激。

#1楼 票数:273 已采纳

不可变意味着一旦对象的构造函数完成执行,该实例就无法更改。

这很有用,因为这意味着您可以传递对对象的引用,而不必担心其他人会更改其内容。 特别是在处理并发时,永远不会改变的对象不存在锁定问题

例如

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()的调用者将能够更改Foo实例的StringBuilder属性。

还要注意您可能会发现的不同类型的不变性:Eric Lippert 写了一篇关于此的博客文章 基本上你可以拥有接口不可变但在幕后实际可变私有状态的对象(因此不能在线程之间安全地共享)。

#2楼 票数:81

不可变对象是内部字段(或至少影响其外部行为的所有内部字段)无法更改的对象。

不可变字符串有很多优点:

性能:采取如下操作:

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 对象是可变的(字符可以稍后更改),则您必须复制所有字符,否则子字符串中字符的更改将在稍后反映在另一个字符串中。

并发:如果一个不可变对象的内部结构是有效的,那么它将永远有效。 不同的线程不可能在该对象内创建无效状态。 因此,不可变对象是线程安全的

垃圾收集:垃圾收集器更容易对不可变对象做出逻辑决策。

然而,不变性也有缺点:

性能:等等,我以为你说性能是不变性的一个好处! 嗯,有时是,但并非总是如此。 取以下代码:

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));

请注意, concatenate 被调用两次,这意味着必须遍历整个字符串! 将此与bar操作的 C 代码进行比较:

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对象是本地引用,因此您不必担心并发安全性(只有一个线程接触它)。 而且由于它没有在其他任何地方被引用,它只在堆栈上分配,所以一旦函数调用完成它就会被释放(你不必担心垃圾收集)。 您可以获得可变性和不变性的所有性能优势。

#3楼 票数:32

如果您使用上面建议的维基百科定义,实际上 String 不是一成不变的。

字符串的状态确实会在构建后发生变化。 看看 hashcode() 方法。 String 将 hashcode 值缓存在本地字段中,但直到第一次调用 hashcode() 时才会计算它。 哈希码的这种懒惰评估将 String 作为一个不可变对象,其状态发生了变化,但如果不使用反射,则无法观察到它发生了变化。

所以也许 immutable 的定义应该是一个无法观察到改变的对象。

如果不可变对象在创建后状态发生变化但没有人可以看到它(没有反射),该对象是否仍然不可变?

#4楼 票数:24

不可变对象是不能以编程方式更改的对象。 它们特别适用于多线程环境或其他多个进程能够更改(变异)对象中的值的环境。

然而,澄清一下,StringBuilder 实际上是一个可变对象,而不是不可变对象。 一个普通的java String 是不可变的(意味着一旦它被创建,你就不能在不改变对象的情况下改变底层的字符串)。

例如,假设我有一个名为 ColoredString 的类,它有一个字符串值和一个字符串颜色:

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!");

那么你会期望字符串总是“蓝色”。 但是,如果另一个线程获得此实例并调用

blueString.setColor("Red");

当您想要一个“蓝色”字符串时,您会突然地,并且可能出乎意料地现在拥有一个“红色”字符串。 因此,在传递对象实例时,几乎总是首选不可变对象。 当您遇到确实需要可变对象的情况时,您通常会通过仅从您的特定控制领域传递副本来保护对象。

回顾一下,在 Java 中, java.lang.String 是一个不可变对象(一旦创建就无法更改),而 java.lang.StringBuilder 是一个可变对象,因为它可以在不创建新实例的情况下进行更改。

#5楼 票数:24

  1. 在大型应用程序中,字符串文字通常会占用大量内存。 因此,为了有效地处理内存,JVM 分配了一个称为“字符串常量池”的区域。( 请注意,在内存中,即使是未引用的字符串也会携带一个 char[]、一个表示其长度的 int、另一个表示其 hashCode。对于一个数字,相比之下,最多需要八个立即字节
  2. 当编译器遇到字符串文字时,它会检查池以查看是否已经存在相同的文字。 如果找到,则对新文字的引用将定向到现有字符串,并且不会创建新的“字符串文字对象”(现有字符串只是获得了一个额外的引用)。
  3. 因此:字符串可变性节省了内存......
  4. 但是当任何变量的值发生变化时,实际上 - 只有它们的引用发生了变化,而不是内存中的值(因此它不会影响引用它的其他变量),如下所示......

String 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]   ------------------------^

原始字符串 'in memory' 没有改变,但引用变量已更改,以便它引用新字符串。 如果我们没有 s2,“旧字符串”仍会在内存中,但我们将无法访问它......

#6楼 票数:16

“不可变”意味着你不能改变价值。 如果您有一个 String 类的实例,那么您调用的任何似乎修改该值的方法实际上都会创建另一个 String。

String foo = "Hello";
foo.substring(3);
<-- foo here still has the same value "Hello"

要保留更改,您应该执行以下操作 foo = foo.sustring(3);

当您使用集合时,不可变与可变可能会很有趣。 想想如果你使用可变对象作为 map 的键然后改变值会发生什么(提示:考虑equalshashCode )。

#7楼 票数:13

时间

可能有点晚了,但为了理解什么是不可变对象,请考虑以下来自新 Java 8 日期和时间 API ( java.time ) 的示例 您可能知道 Java 8 中的所有日期对象都是不可变的,因此在以下示例中

LocalDate date = LocalDate.of(2014, 3, 18); 
date.plusYears(2);
System.out.println(date);

输出:

2014-03-18

这将打印与初始日期相同的年份,因为plusYears(2)返回一个新对象,因此旧日期仍然保持不变,因为它是一个不可变对象。 一旦创建,您就无法进一步修改它,日期变量仍然指向它。

因此,该代码示例应该捕获并使用由对plusYears调用实例化和返回的新对象。

LocalDate date = LocalDate.of(2014, 3, 18); 
LocalDate dateAfterTwoYears = date.plusYears(2);

date.toString()... 2014-03-18

dateAfterTwoYears.toString()... 2016-03-18

#8楼 票数:8

我真的很喜欢SCJP Sun Certified Programmer for Java 5 Study Guide 的解释

为了提高 Java 的内存效率,JVM 留出了一个特殊的内存区域,称为“字符串常量池”。 当编译器遇到字符串文字时,它会检查池以查看是否已存在相同的字符串。 如果找到匹配项,则对新文字的引用将定向到现有字符串,并且不会创建新的字符串文字对象。

#9楼 票数:8

不可变对象在创建后不能更改其状态。

尽可能使用不可变对象有三个主要原因,所有这些都将有助于减少您在代码中引入的错误数量:

  • 当你知道一个对象的状态不能被另一种方法改变时,就更容易推理你的程序是如何工作的
  • 不可变对象自动是线程安全的(假设它们是安全发布的),因此永远不会成为那些难以确定的多线程错误的原因
  • 不可变对象将始终具有相同的哈希码,因此它们可以用作 HashMap(或类似的)中的键。 如果哈希表中某个元素的哈希码要更改,则表条目实际上将丢失,因为尝试在表中查找它最终会在错误的位置查找。 这是 String 对象不可变的主要原因 - 它们经常用作 HashMap 键。

当您知道对象的状态是不可变的时,您可能还可以在代码中进行一些其他优化 - 例如缓存计算出的哈希 - 但这些都是优化,因此并不是那么有趣。

#10楼 票数:5

一种含义与值如何存储在计算机中有关,例如对于 .Net 字符串,这意味着内存中的字符串无法更改,当您认为您正在更改它时,实际上您正在创建一个新的内存中的字符串并将现有变量(它只是指向其他地方的实际字符集合的指针)指向新字符串。

#11楼 票数:5

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" 值和s1引用的新 String 对象。 在这里,当我们打印s2值时,结果将是“Hi”而不是“Bye”,因为s2引用了先前具有“Hi”值的s1对象。

#12楼 票数:3

不可变意味着一旦对象被创建,它的成员都不会改变。 String是不可变的,因为你不能改变它的内容。 例如:

String s1 = "  abc  ";
String s2 = s1.trim();

在上面的代码中,字符串 s1 没有改变,使用s1创建了另一个对象( s2 )。

#13楼 票数:3

不可变仅意味着不可更改或不可修改。 一旦字符串对象被创建,它的数据或状态就不能改变

考虑下面的例子,

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 中是不可变的。

#14楼 票数:2

一经实例化,不可更改。 考虑一个类,它的实例可能用作哈希表或类似的键。 查看 Java 最佳实践。

#15楼 票数:1

由于接受的答案并没有回答所有的问题。 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

#16楼 票数:0

不可变对象

如果对象在构造后其状态无法更改,则该对象被认为是不可变的。 最大程度地依赖不可变对象被广泛接受为创建简单、可靠代码的合理策略。

不可变对象在并发应用程序中特别有用。 因为它们不能改变状态,所以它们不会被线程干扰破坏或在不一致的状态下被观察到。

程序员通常不愿意使用不可变对象,因为他们担心创建新对象而不是更新对象的成本。 对象创建的影响通常被高估,并且可以被与不可变对象相关的一些效率所抵消。 这些包括由于垃圾收集而减少的开销,以及消除保护可变对象免受损坏所需的代码。

以下小节采用一个实例可变的类,并从中派生出一个具有不可变实例的类。 这样做时,他们给出了这种转换的一般规则,并展示了不可变对象的一些优点。

来源

#17楼 票数:0

oracle docs说

如果一个对象的状态在构造后不能改变,则该对象被认为是不可变的 最大程度上依赖不可变对象被广泛接受为创建简单,可靠代码的合理策略。

不可变对象在并发应用程序中特别有用。 由于它们不能改变状态,因此它们不会被线程干扰破坏或在不一致状态下被观察到。

我喜欢帖子中的这句话

不可变对象简化了并发编程

#18楼 票数:-1

不可变对象是创建后无法修改的对象。 一个典型的例子是字符串文字。

越来越流行的 AD 编程语言通过“不变”关键字具有“不变性”的概念。 检查这篇 Dr.Dobb 关于它的文章 - http://dobbscodetalk.com/index.php?option=com_myblog&show=Invariant-Strings.html&Itemid=29 它完美地解释了这个问题。

  ask by ashokgelal translate from so

未解决问题?本站智能推荐:

19回复

字符串是不可变的。究竟是什么意思? [重复]

这个问题在这里已有答案: Java中字符串的不变性 26答案 我在不可变字符串上编写了以下代码。 输出: 这里变量a的值已经改变(许多人说不可变对象的内容不能改变)。 但究竟是什么意思说String是不可变的 ? 你能否为我澄清一下这个话题?
4回复

是什么使String不可变?

我知道String是不可变的,但是为什么呢? 哪个概念使字符串类不可变? 结果:
2回复

像String这样的不可变对象而不是仅使用static final的实际好处是什么? [重复]

这个问题已经在这里有了答案: 为什么在Java中String是不可变的? 12个答案 我的意思是,为什么事实上的不可变对象存在? 为什么我们不仅仅使用最终的静态修饰符? 对于String来说,什么使Java变得不可变如此重要?
2回复

Java字符串不是真正不可变的含义是什么?

背景 在Java 101中,我们被教导: String是不可变的。 是。 好。 谢谢。 然后我们到达Java 102(或者Java 201),我们发现: String不是真正不可变的:你可以使用反射来改变它。 啊。 精细。 根据你的观点,要
14回复

不可变类?

如何使 Java 类不可变,什么是不可变性的需要,使用它有什么好处?
15回复

“new String()”也是不可变的吗?

我一直在研究Java String。 以下问题基于以下帖子 Java String很特别 java中String的不变性 不可变性:现在,通过不变性,String类的设计使得公共池中的值可以在其他位置/变量中重用。 如果将String创建为,则这很好 String a
2回复

不可变对象上的线程安全

我正在阅读有关Java中不可变对象的信息。 有一条声明指出-“不可变对象是线程安全的”。 对于上述声明,我需要进一步说明: 如果我具有与多个线程共享的类型为'String'的共享资源(例如3),并且如果其中一个线程对共享引用进行了更改,它将创建一个新的String对象,并且该对象仅
4回复

Java不可变对象[关闭]

我正在学习不变性的概念。 我知道一旦创建对象,不可变对象就无法更改它们的值。 但我不明白以下对不可变对象的使用。 他们是 是自动线程安全的,没有同步问题。 How ? Proof ? 不需要复制构造函数。 How ? Any example ? 不需要