如何在Java中创建不可变对象?

Nee*_*lpe 80 java immutability

如何在Java中创建不可变对象?

应该将哪些对象称为不可变?

如果我有所有静态成员的类是不可变的?

nsf*_*n55 82

以下是不可变对象的硬性要求.

  1. 让课程最终
  2. 使所有成员最终,在静态块或构造函数中显式设置它们
  3. 让所有成员都私密
  4. 没有修改状态的方法
  5. 要非常小心限制对可变成员的访问(记住该字段可能是final但该对象仍然是可变的.即private final Date imStillMutable).你应该defensive copies在这些情况下做出来.

上课的final原因非常微妙,经常被忽视.如果不是最终的人可以自由地扩展你的类,覆盖publicprotected行为,添加可变属性,然后提供他们的子类作为替代.通过声明课程,final您可以确保不会发生这种情况.

要查看运行中的问题,请考虑以下示例:

public class MyApp{

    /**
     * @param args
     */
    public static void main(String[] args){

        System.out.println("Hello World!");

        OhNoMutable mutable = new OhNoMutable(1, 2);
        ImSoImmutable immutable = mutable;

        /*
         * Ahhhh Prints out 3 just like I always wanted
         * and I can rely on this super immutable class 
         * never changing. So its thread safe and perfect
         */
        System.out.println(immutable.add());

        /* Some sneak programmer changes a mutable field on the subclass */
        mutable.field3=4;

        /*
         * Ahhh let me just print my immutable 
         * reference again because I can trust it 
         * so much.
         * 
         */
        System.out.println(immutable.add());

        /* Why is this buggy piece of crap printing 7 and not 3
           It couldn't have changed its IMMUTABLE!!!! 
         */
    }

}

/* This class adheres to all the principles of 
*  good immutable classes. All the members are private final
*  the add() method doesn't modify any state. This class is 
*  just a thing of beauty. Its only missing one thing
*  I didn't declare the class final. Let the chaos ensue
*/ 
public class ImSoImmutable{
    private final int field1;
    private final int field2;

    public ImSoImmutable(int field1, int field2){
        this.field1 = field1;
        this.field2 = field2;
    }

    public int add(){
        return field1+field2;
    }
}

/*
This class is the problem. The problem is the 
overridden method add(). Because it uses a mutable 
member it means that I can't  guarantee that all instances
of ImSoImmutable are actually immutable.
*/ 
public class OhNoMutable extends ImSoImmutable{   

    public int field3 = 0;

    public OhNoMutable(int field1, int field2){
        super(field1, field2);          
    }

    public int add(){
       return super.add()+field3;  
    }

}
Run Code Online (Sandbox Code Playgroud)

在实践中,在依赖注入环境中遇到上述问题是很常见的.你没有明确地实例化事物,你给出的超类引用实际上可能是一个子类.

需要注意的是,要对不可变性做出硬性保证,你必须将该类标记为final.这在Joshua Bloch的Effective Java中有详细介绍,并在Java内存模型的规范中明确引用.

  • @Nilesh:immutability是**实例**的属性.静态成员通常不涉及任何单个实例,因此它们不会出现在此处. (11认同)
  • Joshua Bloch关于不变性的第15项 - 没有修改状态的方法,所有字段最终,所有字段都是私有的,确保无法扩展类,确保对任何可变组件的独占访问. (4认同)
  • @Jaochim - 它们绝对是等式的一部分 - 如果我添加一个可变的静态成员并在ImSoImmutable的add函数中使用它,你会遇到同样的问题.如果一个类是不可变的,那么所有方面都必须是不可变的. (2认同)

Bal*_*usC 14

只是不要在类中添加public mutator(setter)方法.

  • 无所谓.如果您不能通过某种方法在外部更改它们,那么它是不可变的. (7认同)

Ale*_*yak 13

类不是不可变的,对象是.

不可变的意思是:初始化后我的公共可见状态不能改变.

字段不必声明为final,但它可以极大地帮助确保线程安全

如果你的类只有静态成员,那么这个类的对象是不可变的,因为你不能改变那个对象的状态(你可能无法创建它:))

  • 使所有字段静态限制所有intsances共享相同的状态,这是非常有用的. (3认同)

小智 6

要使类在Java中不可变,您可以记下以下几点:

1.不要提供setter方法来修改类的任何实例变量的值.

2.将该类声明为"最终".这将阻止任何其他类扩展它,从而阻止覆盖任何可能修改实例变量值的方法.

3.将实例变量声明为private和final.

4.您还可以将类的构造函数声明为private,并在需要时添加工厂方法以创建类的实例.

这些要点应该有帮助!!

  • WRT#4构造函数visibilty如何影响mutabilty?String是不可变的,但有几个公共构造函数. (3认同)

e11*_*438 5

来自甲骨文站点,如何在 Java 中创建不可变对象。

\n\n
\n
    \n
  1. 不要提供修改字段或字段引用的对象的“setter”方法\xe2\x80\x94 方法。
  2. \n
  3. 将所有字段设为最终字段和私有字段。
  4. \n
  5. 不允许子类重写方法。最简单的方法是将类声明为final。更复杂的方法是将构造函数设为私有并在工厂方法中构造实例。
  6. \n
  7. 如果实例字段包含对可变对象的引用,则不允许更改这些对象:
    \n I. 不提供修改可变对象的方法。
    \n 二. 不要共享对可变对象的引用。切勿存储对传递给构造函数的外部可变对象的引用;如有必要,创建副本并存储对副本的引用。同样,必要时创建内部可变对象的副本,以避免在方法中返回原始对象。
  8. \n
\n
\n


ami*_*788 5

不可变对象是创建后不会改变其内部状态的对象。它们在多线程应用程序中非常有用,因为它们可以在没有同步的情况下在线程之间共享。

要创建不可变对象,您需要遵循一些简单的规则:

1.不要添加任何setter方法

如果你正在构建一个不可变对象,它的内部状态永远不会改变。setter 方法的任务是更改字段的内部值,因此您无法添加它。

2. 将所有字段声明为 final 和 private

从类外部看不到私有字段,因此不能对其应用任何手动更改。

声明一个字段 final 将保证如果它引用一个原始值,如果它引用一个对象,则该值永远不会改变,如果它引用一个不能改变的引用。这不足以确保只有私有 final 字段的对象不可变。

3.如果一个字段是一个可变对象,为getter方法创建它的防御性副本

我们之前已经看到,定义一个字段 final 和 private 是不够的,因为它可以改变其内部状态。为了解决这个问题,我们需要创建该字段的防御副本,并在每次请求时返回该字段。

4. 如果传递给构造函数的可变对象必须分配给一个字段,则创建它的防御副本

如果您持有传递给构造函数的引用,则会发生同样的问题,因为可以更改它。因此,持有对传递给构造函数的对象的引用可以创建可变对象。为了解决这个问题,如果参数是可变对象,则有必要创建参数的防御副本。

请注意,如果字段是对不可变对象的引用,则无需在构造函数和 getter 方法中创建它的防御性副本,只需将该字段定义为 final 和 private 就足够了。

5. 不允许子类覆盖方法

如果子类覆盖一个方法,它可以返回可变字段的原始值而不是它的防御性副本。

要解决此问题,可以执行以下操作之一:

  1. 将不可变类声明为 final 使其无法扩展
  2. 声明不可变类 final 的所有方法,这样它们就不能被覆盖
  3. 创建一个私有构造函数和一个工厂来创建不可变类的实例,因为不能扩展具有私有构造函数的类

如果你遵循这些简单的规则,你就可以在线程之间自由共享你的不可变对象,因为它们是线程安全的!

以下是几个值得注意的点:

  • 在许多情况下,不可变对象确实让生活变得更简单。它们特别适用于值类型,其中对象没有标识,因此可以轻松替换它们,并且它们可以使并发编程方式更安全、更干净(大多数众所周知的难以发现的并发错误最终是由对象之间共享的可变状态引起的)线程)。 但是,对于大型和/或复杂的对象,为每次更改创建对象的新副本可能非常昂贵和/或乏味。对于具有独特标识的对象,更改现有对象比创建一个新的、修改过的副本要简单和直观得多。
  • 对于不可变对象,有些事情您根本无法做到,例如具有双向关系。一旦在一个对象上设置了关联值,它的身份就会发生变化。因此,您在另一个对象上设置了新值,它也会发生变化。问题是第一个对象的引用不再有效,因为已经创建了一个新实例来表示具有引用的对象。继续这样做只会导致无限回归。
  • 要实现二叉搜索树,您必须每次都返回一棵新树:您的新树必须复制每个已修改的节点(共享未修改的分支)。对于您的插入功能,这还不错,但对我来说,当我开始处理删除和重新平衡时,事情很快就变得相当低效。
  • Hibernate 和 JPA本质上规定您的系统使用可变对象,因为它们的整个前提是它们检测并保存对数据对象的更改。
  • 根据语言的不同,编译器在处理不可变数据时可以进行一系列优化,因为它知道数据永远不会改变。各种东西都被跳过了,这给你带来了巨大的性能优势。
  • 如果您查看其他已知的 JVM 语言(Scala、Clojure),在代码中很少看到可变对象,这就是为什么人们开始在单线程不够的情况下使用它们的原因。

没有对与错,这只是取决于你喜欢什么。这仅取决于您的偏好以及您想要实现的目标(并且能够轻松地使用这两种方法而不会疏远一方或另一方的铁杆粉丝是某些语言所追求的圣杯)。