Java 什么是不可变的?

Java 什么是不可变的?,java,string,immutability,Java,String,Immutability,这可能是有史以来问过的最愚蠢的问题,但我认为对于一个Java新手来说,这是相当令人困惑的 有人能澄清什么是不可变的吗 为什么字符串是不可变的 不可变对象的优点/缺点是什么 为什么像StringBuilder这样的可变对象应该优先于字符串和反之亦然 一个很好的例子(Java)将非常受欢迎。一旦实例化,就无法更改。考虑一个类,该实例可能用作哈希表或类似的密钥。查看Java最佳实践。不可变意味着一旦创建了对象,它的非成员将发生更改字符串是不可变的,因为您无法更改其内容。 例如: String s1 =

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

  • 有人能澄清什么是不可变的吗
  • 为什么
    字符串是不可变的
  • 不可变对象的优点/缺点是什么
  • 为什么像
    StringBuilder
    这样的可变对象应该优先于字符串和反之亦然

  • 一个很好的例子(Java)将非常受欢迎。

    一旦实例化,就无法更改。考虑一个类,该实例可能用作哈希表或类似的密钥。查看Java最佳实践。

    不可变意味着一旦创建了对象,它的非成员将发生更改<代码>字符串
    是不可变的,因为您无法更改其内容。 例如:

    String s1 = "  abc  ";
    String s2 = s1.trim();
    
    在上面的代码中,字符串s1没有更改,另一个对象(
    s2
    )是使用
    s1

    创建的。“不可变”表示您不能更改值。如果您有一个String类的实例,那么您调用的任何似乎修改该值的方法实际上都会创建另一个字符串

    String foo = "Hello";
    foo.substring(3);
    <-- foo here still has the same value "Hello"
    
    String foo=“你好”;
    foo.子串(3);
    
    一种含义与值在计算机中的存储方式有关,例如,对于.Net字符串,它意味着内存中的字符串不能更改,当您认为正在更改它时,实际上是在内存中创建一个新字符串并指向现有变量(它只是指向其他地方的实际字符集合的指针)指向新字符串。

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

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

    e、 g

    Foo
    不必担心
    getValue()
    的调用方可能会更改字符串中的文本

    如果您设想一个类似于
    Foo
    的类,但是成员是
    StringBuilder
    而不是
    String
    ,那么您可以看到
    getValue()
    的调用方能够更改
    Foo
    实例的
    StringBuilder
    属性


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

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

    然而,为了澄清这一点,StringBuilder实际上是一个可变对象,而不是一个不可变的对象。常规java字符串是不可变的(这意味着一旦创建了它,就不能在不更改对象的情况下更改基础字符串)

    例如,假设我有一个名为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是可变的,因为您可以更改(mutate)它的一个关键属性没有创建新的ColoredString类。这可能不好的原因是,例如,假设您有一个具有多个线程的GUI应用程序,并且您正在使用ColoredString将数据打印到窗口。如果您有一个创建为

    new ColoredString("Blue", "This is a blue string!");
    
    然后,您会期望字符串始终为“蓝色”。但是,如果另一个线程控制了该实例并调用

    blueString.setColor("Red");
    
    当你想要一根“蓝色”的线时,你会突然,也许是出乎意料地,现在有了一根“红色”的线第一,正因为如此,在传递对象实例时,不可变对象几乎总是首选对象。如果确实需要可变对象,则通常只通过从特定控制域传递副本来保护对象


    综上所述,在Java中,Java.lang.String是一个不可变的对象(一旦创建它就不能更改),而Java.lang.StringBuilder是一个可变的对象,因为它可以在不创建新实例的情况下更改。

    我非常喜欢来自的解释

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


    不可变对象是在创建后不能修改的对象。典型的示例是字符串文本


    越来越流行的D编程语言通过“不变量”关键字具有“不变性”的概念。请查看Dobb博士的这篇文章-。它完美地解释了这个问题。

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

    只要有可能,就使用不可变对象有三个主要原因,所有这些都有助于减少代码中引入的bug数量:

    • 当您知道一个对象的状态不能由另一种方法更改时,就更容易对程序的工作方式进行推理
    • 不可变对象是自动线程安全的(假设它们是安全发布的),因此永远不会成为那些难以确定的多线程错误的原因
    • 不可变对象始终具有相同的哈希代码,因此它们可以用作哈希映射(或类似对象)中的键。如果哈希表中某个元素的哈希代码发生更改,则该表条目将实际上丢失,因为在表中查找该条目的尝试最终会查找到错误的位置。这是字符串ob
      blueString.setColor("Red");
      
      String substring = fullstring.substring(x,y);
      
      // 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;
      }
      
      foo = foo.substring(0,4) + "a" + foo.substring(5);  // foo is a String
      bar.replace(4,5,"a"); // bar is a StringBuilder
      
      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));
      
      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();
      }
      
      //s1 variable, refers to string in memory
              reference                 |     MEMORY       |
              variables                 |                  |
      
                 [s1]   --------------->|   "Old String"   |
      
      //s2 refers to same string as s1
                                        |                  |
                 [s1]   --------------->|   "Old String"   |
                 [s2]   ------------------------^
      
      //s1 deletes reference to old string and points to the newly created one
                 [s1]   -----|--------->|   "New String"   |
                             |          |                  |
                             |~~~~~~~~~X|   "Old String"   |
                 [s2]   ------------------------^
      
      LocalDate date = LocalDate.of(2014, 3, 18); 
      date.plusYears(2);
      System.out.println(date);
      
      LocalDate date = LocalDate.of(2014, 3, 18); 
      LocalDate dateAfterTwoYears = date.plusYears(2);
      
      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
      
      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  
        }  
       }  
      
      String s="Future";  
      s=s.concat(" World");  
      System.out.println(s);//print Future World
      
      String s = "";
      for (int i = 0; i < n; ++i) {
          s = s + n;
      }
      
      StringBuilder sb = new StringBuilder();
      for (int i = 0; i < n; ++i) {
        sb.append(String.valueOf(n));
      }
      String s = sb.toString();