不变的课程?

时间:2010-07-02 01:05:08

标签: java string immutability

如何使Java类不可变,对不可变性的需求是什么,使用它有什么好处?

14 个答案:

答案 0 :(得分:127)

什么是不可变对象?

不可变对象是在实例化后不会改变状态的对象。

如何使对象不可变?

通常,可以通过定义一个没有暴露任何成员的类来生成不可变对象,并且没有任何setter。

以下类将创建一个不可变对象:

class ImmutableInt {
  private final int value;

  public ImmutableInt(int i) {
    value = i;
  }

  public int getValue() {
    return value;
  }
}

从上面的例子中可以看出,ImmutableInt的值只能在实例化对象时设置,并且只有一个getter(getValue),对象的状态不能改变实例化后。

但是,必须注意对象引用的所有对象也必须是不可变的,或者可以更改对象的状态。

例如,允许通过getter获取对数组或ArrayList的引用将允许通过更改数组或集合来更改内部状态:

class NotQuiteImmutableList<T> {
  private final List<T> list;

  public NotQuiteImmutableList(List<T> list) {
    // creates a new ArrayList and keeps a reference to it.
    this.list = new ArrayList(list); 
  }

  public List<T> getList() {
    return list;
  }
}

上面代码的问题是ArrayList可以通过getList获得并被操纵,导致对象本身的状态被改变,因此,不是不可变的。

// notQuiteImmutableList contains "a", "b", "c"
List<String> notQuiteImmutableList= new NotQuiteImmutableList(Arrays.asList("a", "b", "c"));

// now the list contains "a", "b", "c", "d" -- this list is mutable.
notQuiteImmutableList.getList().add("d");

解决此问题的一种方法是在从getter调用时返回数组或集合的副本:

public List<T> getList() {
  // return a copy of the list so the internal state cannot be altered
  return new ArrayList(list);
}

不变性有什么好处?

不变性的优势在于并发性。在可变对象中难以保持正确性,因为多个线程可能试图改变同一对象的状态,导致一些线程看到同一对象的不同状态,这取决于对所述读取和写入的时间。对象

通过拥有一个不可变对象,可以确保查看该对象的所有线程都将看到相同的状态,因为不可变对象的状态不会改变。

答案 1 :(得分:18)

除了已经给出的答案之外,我还建议阅读有效Java,第二版中的不变性,因为有些细节很容易被遗漏(例如防御性副本)。另外,有效的Java第二版。是每个Java开发人员必读的。

答案 2 :(得分:6)

你可以像这样创建一个不可变的类:

public final class Immutable
{
    private final String name;

    public Immutable(String name) 
    {
        this.name = name;
    }

    public String getName() { return this.name; } 

    // No setter;
}

以下是使Java类不可变的要求:

  • 必须声明为final(因此无法创建子类)
  • 必须将类中的
  • 成员声明为final(这样我们就无法在创建对象后更改它的值)
  • 为其中的所有变量编写Getter方法以获取成员
  • 没有Setters方法

不可变类很有用,因为
  - 它们是线程安全的   - 他们还对你的设计表达了深刻的东西:“不能改变它。”,当它适用时,它正是你所需要的。

答案 3 :(得分:5)

可变性主要通过两种方式实现:

  • 使用final实例属性来避免重新分配
  • 使用类接口,该接口根本不允许任何能够修改类内部操作的操作(只需 getters 且没有 setters

不变性的优点是您可以对这些对象做出的假设:

  • 你获得了无副作用规则(在函数式编程语言中非常流行)并允许你更容易地在并发环境中使用对象,因为你知道它们不能以原子或非原子的方式改变当它们被许多线程使用时
  • 语言的实现可以以不同的方式处理这些对象,将它们放在用于静态数据的内存区域中,从而更快,更安全地使用这些对象(这是JVM中为字符串发生的事情)

答案 4 :(得分:3)

不可变类在实例化后无法重新赋值。构造函数将值赋给其私有变量。在对象变为null之前,由于setter方法不可用,因此无法更改值。

是不可变的应该满足以下,

  • Al变量应为私有
  • 未提供 mutator 方法(setter)。
  • 通过使类最终(强不可变性)避免方法重写 或方法final(Week immutability)。
  • 如果它包含非原始或可变类,则深入克隆。

/**
* Strong immutability - by making class final
*/
public final class TestImmutablity {

// make the variables private
private String Name;

//assign value when the object created
public TestImmutablity(String name) {
this.Name = name;
}

//provide getters to access values
public String getName() {

return this.Name;
}
}

Advanteges: 不可变对象包含其初始值,直到它死亡。

java-immutable-classes-short-note

答案 5 :(得分:2)

如何使Java类不可变?

在具有JEP 359的JDK 14+中,我们可以使用“ records ”。这是创建Immutable类的最简单,最轻松的方式。

记录类是浅的不可变透明载体,用于固定的一组称为记录components的字段,该字段为记录提供了state描述。每个component都会引起一个final字段,该字段保存提供的值和一个accessor方法来检索该值。字段名称和访问者名称与组件名称匹配。

让我们考虑创建不可变矩形的示例

record Rectangle(double length, double width) {}

无需声明任何构造函数,无需实现equals&hashCode方法。只是任何记录都需要名称和状态说明。

var rectangle = new Rectangle(7.1, 8.9);
System.out.print(rectangle.length()); // prints 7.1

如果要在对象创建期间验证值,则必须显式声明构造函数。

public Rectangle {

    if (length <= 0.0) {
      throw new IllegalArgumentException();
    }
  }

记录主体可以声明静态方法,静态字段,静态初始化器,构造函数,实例方法和嵌套类型。

实例方法

record Rectangle(double length, double width) {

  public double area() {
    return this.length * this.width;
  }
}

静态字段,方法

由于状态应该是组件的一部分,因此我们无法将实例字段添加到记录中。但是,我们可以添加静态字段和方法:

record Rectangle(double length, double width) {

  static double aStaticField;

  static void aStaticMethod() {
    System.out.println("Hello Static");
  }
}

不变性是什么,使用它有什么好处?

以前发布的答案足以证明不变性的必要性,而且是优点

答案 6 :(得分:1)

不可变类是那些在创建后无法更改其对象的类。

不可变类对

很有用
  • 缓存目的
  • 并发环境(ThreadSafe)
  • 难以继承
  • 无法在任何环境中更改值

示例

String Class

代码示例

public final class Student {
    private final String name;
    private final String rollNumber;

    public Student(String name, String rollNumber) {
        this.name = name;
        this.rollNumber = rollNumber;
    }

    public String getName() {
        return this.name;
    }

    public String getRollNumber() {
        return this.rollNumber;
    }
}

答案 7 :(得分:0)

制作不可变对象的另一种方法是使用Immutables.org库:

  

假设添加了所需的依赖项,请创建一个抽象   使用抽象访问器方法的类。你也可以这样做   用接口或注释注释(@interface):

package info.sample;

import java.util.List;
import java.util.Set;
import org.immutables.value.Value;

@Value.Immutable
public abstract class FoobarValue {
  public abstract int foo();
  public abstract String bar();
  public abstract List<Integer> buz();
  public abstract Set<Long> crux();
}
  

现在可以生成然后使用生成的不可变项   实现:

package info.sample;

import java.util.List;

public class FoobarValueMain {
  public static void main(String... args) {
    FoobarValue value = ImmutableFoobarValue.builder()
        .foo(2)
        .bar("Bar")
        .addBuz(1, 3, 4)
        .build(); // FoobarValue{foo=2, bar=Bar, buz=[1, 3, 4], crux={}}

    int foo = value.foo(); // 2

    List<Integer> buz = value.buz(); // ImmutableList.of(1, 3, 4)
  }
}

答案 8 :(得分:0)

作为一个非母语英语的人,我不喜欢“不可变类”的共同解释是“构造的类对象是不可变的”;相反,我自己倾向于将其解释为“类对象本身是不可变的”。

那就是说,“不可变类”是一种不可改变的对象。不同之处在于回答利益是什么。据我所知,不可变类阻止其对象进行运行时行为修改。

答案 9 :(得分:0)

不可变的类只是其实例无法修改的类。

每个实例中包含的所有信息在对象的生命周期中都是固定的,因此无法观察到任何变化。

不可变类比可变类更易于设计,实现和使用。

要使一个类不可变,请遵循以下五个规则:

  1. 不提供修改对象状态的方法

  2. 确保无法扩展课程。

  3. 将所有字段都设为最终字段。

  4. 将所有字段设为私有。

  5. 确保对任何可变组件的专有访问。

不可变对象本质上是线程安全的;他们不需要同步。

不变的对象可以自由共享。

不可变的对象是其他对象的重要构建块

答案 10 :(得分:0)

@Jack,在课堂上拥有最终的领域和二传手将不会使班级一成不变。 final关键字仅确保永不重新分配变量。您需要在getter方法中返回所有字段的深层副本。这样可以确保从getter方法获取对象后,该对象的内部状态不会受到干扰。

答案 11 :(得分:0)

以下是要求:

  • 类必须声明为 final(这样就不能创建子类)

  • 类中的数据成员必须声明为final(这样我们就不能 创建对象后更改它的值)

  • 一个参数化的 constructor 应该初始化所有的字段 执行深拷贝(这样数据成员不能被修改 对象引用)

  • 对象的深度复制应该在 getter 方法中执行(为了 返回一个副本而不是返回实际的对象引用)

  • No setters(为了不能改变 实例变量)

import java.util.HashMap;
import java.util.Map;
 
// An immutable class
public final class Student {
    private final String name;
    private final int regNo;
    private final Map<String, String> metadata;
 
    public Student(String name, int regNo,
                   Map<String, String> metadata)
    {
        this.name = name;
        this.regNo = regNo;
        Map<String, String> tempMap = new HashMap<>();
        for (Map.Entry<String, String> entry :
             metadata.entrySet()) {
            tempMap.put(entry.getKey(), entry.getValue());
        }
        this.metadata = tempMap;
    }
 
    public String getName() { return name; }
 
    public int getRegNo() { return regNo; }
 
    public Map<String, String> getMetadata()
    {
        Map<String, String> tempMap = new HashMap<>();
        for (Map.Entry<String, String> entry :
             this.metadata.entrySet()) {
            tempMap.put(entry.getKey(), entry.getValue());
        }
        return tempMap;
    }
}
 
// Driver class
class Test {
    public static void main(String[] args)
    {
        Map<String, String> map = new HashMap<>();
        map.put("1", "first");
        map.put("2", "second");
        Student s = new Student("ABC", 101, map);
        System.out.println(s.getName());
        System.out.println(s.getRegNo());
        System.out.println(s.getMetadata());
 
        // Uncommenting below line causes error
        // s.regNo = 102;
 
        map.put("3", "third");
        System.out.println(s.getMetadata()); // Remains unchanged due to deep copy in constructor
 
        s.getMetadata().put("4", "fourth");
        System.out.println(s.getMetadata()); // Remains unchanged due to deep copy in getter
    }
}

答案 12 :(得分:-1)

这里的大多数答案都是好的,有的提到了规则,但是当我们需要遵循这些规则时,我用文字写下为什么和这样的感觉很好。因此,请给出以下说明

  • 将成员变量声明为“ final” –当我们将其声明为final时,编译器会强制我们对其进行初始化。我们可以通过默认构造函数直接通过arg构造函数进行初始化(请参见下面的示例代码) 初始化后,我们将无法修改它们,因为它们是最终的。
  • 当然,如果我们尝试将Setters用于这些最终变量,则编译器将引发错误。

    public class ImmutableClassExplored {
    
        public final int a; 
        public final int b;
    
        /* OR  
        Generally we declare all properties as private, but declaring them as public 
        will not cause any issues in our scenario if we make them final     
        public final int a = 109;
        public final int b = 189;
    
         */
        ImmutableClassExplored(){
            this. a = 111;
            this.b = 222;
        }
    
        ImmutableClassExplored(int a, int b){
            this.a = a;
            this.b= b;
        }
    }
    

我们需要将类声明为“最终”吗?

  • 在类声明中没有final关键字,则可以继承类。因此,子类可以覆盖getter方法。在这里,我们必须考虑两种情况:

1。只有原始成员:我们没有问题如果类只有原始成员,则不需要将类声明为final。

2。将对象作为成员变量:如果我们将对象作为成员变量,则必须使这些对象的成员也成为最终成员。意味着我们需要遍历树的深处并使所有对象/图元最终成为不可能的对象。因此,解决方法是使类为final,以防止继承。因此,没有子类覆盖getter方法的问题。

答案 13 :(得分:-1)

Lombok的

@Value注释可用于生成不可变的类。就像下面的代码一样简单。

@Value
public class LombokImmutable {
    int id;
    String name;
}

根据龙目岛网站上的文档:

@Value是@Data的不可变形式;默认情况下,所有字段都设为私有和最终字段,并且不会生成设置器。默认情况下,该类本身也被设置为final,因为不可改变性不能强加于子类。像@Data一样,还会生成有用的toString(),equals()和hashCode()方法,每个字段都有一个getter方法,并且还会生成一个覆盖每个参数的构造函数(除了在字段声明中初始化的最终字段之外)

可以找到一个完整的示例here.