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

时间:2011-06-10 11:32:14

标签: java immutability

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

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

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

14 个答案:

答案 0 :(得分:82)

以下是不可变对象的要求。

  1. 让课程最终
  2. 让所有成员都成为最终成员 显式地,在静态块中,或在构造函数中
  3. 让所有成员保密“
  4. 没有修改状态的方法
  5. 要非常小心限制对可变成员的访问(请记住该字段可能是final,但该对象仍然可以变为可变。即private final Date imStillMutable)。在这些情况下,您应该defensive copies
  6. 制作课程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;  
        }
    
    }
    

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

    带走的是,为了对不变性做出硬性保证,你必须将该类标记为final。这在Joshua Bloch的Effective Java中有详细介绍,并在Java memory model的规范中明确引用。

答案 1 :(得分:14)

只是不要将公共mutator(setter)方法添加到类中。

答案 2 :(得分:13)

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

不可变意味着:初始化后我的公共可见状态无法改变。

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

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

答案 3 :(得分:6)

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

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

2.将该类声明为'final'。这样可以防止任何其他类扩展它,从而覆盖任何可以修改实例变量值的方法。

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

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

这些要点应该有所帮助!!

答案 4 :(得分:3)

oracle站点,如何在Java中创建不可变对象。

  
      
  1. 不要提供" setter" methods - 修改字段引用的字段或对象的方法。
  2.   
  3. 将所有字段设为最终字段并保密。
  4.   
  5. 不允许子类覆盖方法。最简单的方法是将类声明为final。更复杂的方法是使构造函数私有,并在工厂方法中构造实例。
  6.   
  7. 如果实例字段包含对可变对象的引用,则不允许更改这些对象:
      I.不要提供修改可变对象的方法   II。不要共享对可变对象的引用。永远不要存储对传递给构造函数的外部可变对象的引用;如有必要,创建副本并存储对副本的引用。同样,必要时创建内部可变对象的副本,以避免在方法中返回原始文件。
  8.   

答案 5 :(得分:2)

首先,您知道为什么需要创建不可变对象,以及不可变对象的优点。

不可变对象的优点

并发和多线程 它自动线程安全所以同步问题....等

不需要 复制构造函数 不需要实现 clone。 类不能 覆盖 将该字段设为 私有和最终 强制调用者在一个步骤中完全构造一个对象,而不是使用no-Argument构造函数

不可变对象只是对象,其状态意味着对象的数据在之后不能改变 构造了不可变对象。

请参阅以下代码。

public final class ImmutableReminder{
    private final Date remindingDate;

    public ImmutableReminder (Date remindingDate) {
        if(remindingDate.getTime() < System.currentTimeMillis()){
            throw new IllegalArgumentException("Can not set reminder" +
                    " for past time: " + remindingDate);
        }
        this.remindingDate = new Date(remindingDate.getTime());
    }

    public Date getRemindingDate() {
        return (Date) remindingDate.clone();
    }
}

答案 6 :(得分:2)

  • 不要提供&#34; setter&#34;方法 - 修改字段或方法的方法 字段引用的对象。
  • 将所有字段设为最终字段并保密。
  • 不允许子类覆盖方法。最简单的方法是将类声明为final。更复杂的方法是使构造函数私有,并在工厂方法中构造实例。
  • 如果实例字段包含对可变对象的引用,则不允许更改这些对象:
    • 不要提供修改可变对象的方法。
    • 不要共享对可变对象的引用。永远不要存储对传递给构造函数的外部可变对象的引用;如有必要,创建副本并存储对副本的引用。同样,必要时创建内部可变对象的副本,以避免在方法中返回原始文件。

答案 7 :(得分:2)

尽量减少可变性

不可变类只是一个无法修改其实例的类。每个实例中包含的所有信息都在创建时提供,并在对象的生命周期内得到修复。

JDK不可变类:String,盒装基本类(包装类),BigInteger和BigDecimal等。

如何使类不可变?

  1. 不提供任何修改对象状态的方法(称为mutators)。
  2. 确保无法扩展课程。
  3. 将所有字段设为最终字段。
  4. 将所有字段设为私有。 这可以防止客户端访问字段引用的可变对象并直接修改这些对象。
  5. 制作防御性副本。 确保对任何可变组件的独占访问。

    public List getList(){      return Collections.unmodifiableList(list); &lt; ===可变的防御性副本                                                                               将字段返回给调用者之前的字段 }

  6. 如果您的类具有引用可变对象的任何字段,请确保该类的客户端无法获取对这些对象的引用。永远不要将此类字段初始化为客户端提供的对象引用,也不要从访问者返回对象引用。

    driver.get(url)
    

    有关详细信息,请参阅我的博客:
    http://javaexplorer03.blogspot.in/2015/07/minimize-mutability.html

答案 8 :(得分:2)

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

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

1。不要添加任何设置器方法

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

2。声明所有字段为最终字段和私有字段

在班级外部看不到私有字段,因此不能对其进行任何手动更改。

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

3。如果字段是可变对象,请为该字段创建防御性副本     吸气方法

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

4。如果传递给构造函数的可变对象必须分配给     字段为其创建防御副本

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

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

5。不允许子类覆盖方法

如果子类重写方法,则它可以返回可变字段的原始值,而不是其防御性副本。

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

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

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

以下是几个值得注意的地方:

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

没有对与错,这取决于您的喜好。这仅取决于您的喜好以及要实现的目标(并且能够轻松使用两种方法而不会疏远一方或另一方的顽固支持者是某些语言所追求的圣杯)。

答案 9 :(得分:1)

如果一个对象在创建后无法更改,则该对象称为不可变。在Java中创建不可变类的最简单方法之一是将所有字段设置为final。如果需要编写包含可变类(如“java.util.Date”)的不可变类。为了在这种情况下保持不变性,建议返回原始对象的副本,

答案 10 :(得分:1)

不可变对象是那些在创建状态后无法更改状态的对象,例如String类是不可变类。不可修改的对象不能被修改,因此它们在并发执行中也是线程安全的。

不可变类的功能:

  • 简单构建
  • 自动线程安全
  • 地图键的好候选者和设置为内部状态在处理时不会改变
  • 不需要实现克隆,因为它们总是代表相同的状态

写不可变类的关键:

  • 确保无法覆盖课程
  • 将所有成员变量设为私有&amp;最终
  • 不提供他们的setter方法
  • 在施工阶段不应泄漏对象参考

答案 11 :(得分:1)

当您希望任何类作为不可变类时,必须考虑以下几个步骤。

  1. 类应标记为最终
  2. 所有字段必须是私有且最终
  3. 用构造函数替换setter(用于为a赋值) 变量)。
  4. 让我们一瞥我们上面输入的内容:

    //ImmutableClass
    package younus.attari;
    
    public final class ImmutableExample {
    
        private final String name;
        private final String address;
    
        public ImmutableExample(String name,String address){
            this.name=name;
            this.address=address;
        }
    
    
        public String getName() {
            return name;
        }
    
        public String getAddress() {
            return address;
        }
    
    }
    
    //MainClass from where an ImmutableClass will be called
    package younus.attari;
    
    public class MainClass {
    
        public static void main(String[] args) {
            ImmutableExample example=new ImmutableExample("Muhammed", "Hyderabad");
            System.out.println(example.getName());
    
        }
    }
    

答案 12 :(得分:0)

在不可变对象上通常被忽略但重要的属性

除了@ nsfyn55提供的答案外,还需要考虑以下方面以实现对象不变性,这些方面具有首要重要性

请考虑以下课程:

public final class ImmutableClass {

  private final MutableClass mc;

  public ImmutableClass(MutableClass mc) {
    this.mc = mc;
  }

  public MutableClass getMutClass() {
    return this.mc;
  }
}

public class MutableClass {

  private String name;

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

  public void setName(String name) {
    this.name = name;
  }
}


public class MutabilityCheck {

public static void main(String[] args) {

  MutableClass mc = new MutableClass();

  mc.setName("Foo");

  ImmutableClass iMC = new ImmutableClass(mc);

  System.out.println(iMC.getMutClass().getName());

  mc.setName("Bar");

  System.out.println(iMC.getMutClass().getName());

  }

 }

以下将是MutabilityCheck的输出:

 Foo
 Bar

重要的是要注意,

  1. 在不可变对象上构造可变对象(通过构造函数),方法是'复制''cloing'来实例化由以下内容描述的不可变对象的变量:进行以下更改:

    public final class ImmutableClass {
    
       private final MutableClass mc;
    
       public ImmutableClass(MutableClass mc) {
         this.mc = new MutableClass(mc);
       }
    
       public MutableClass getMutClass() {
         return this.mc;
       }
    
     }
    
     public class MutableClass {
    
      private String name;
    
      public MutableClass() {
    
      }
      //copy constructor
      public MutableClass(MutableClass mc) {
        this.name = mc.getName();
      }
    
      public String getName() {
        return this.name;
      }
    
      public void setName(String name) {
       this.name = name;
      } 
     }
    

仍然不能确保完全不变,因为以下内容在MutabilityCheck类中仍然有效:

  iMC.getMutClass().setName("Blaa");
  1. 但是,运行MutabilityCheck并在1.中进行更改将导致输出为:

    Foo
    Foo
    
  2. 为了实现对象的完全不变性,其所有依赖对象也必须是不变的

答案 13 :(得分:0)

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

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

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

record Rectangle(double length, double width) {}

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

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");
  }
}
相关问题