代码之家  ›  专栏  ›  技术社区  ›  Rakesh Juyal

为什么我们需要不变的类?

  •  70
  • Rakesh Juyal  · 技术社区  · 16 年前

    我无法得到我们需要一个不可变类的场景。

    19 回复  |  直到 16 年前
        1
  •  82
  •   Asmat K Bert F    6 年前

    其他答案似乎过于集中于解释为什么不变性是好的。这是非常好的,我用它在任何可能的时候。 然而,这不是你的问题

    我无法得到我们需要一个不可变类的场景。

    “需要”在这里是一个相对的术语。不可变类是一种设计模式,与任何范例/模式/工具一样,它可以使构建软件更容易。类似地,在OO范式出现之前,已经编写了大量的代码,但是把我算在那些 面向对象。像OO这样的不可变类并不是严格意义上的 需要 但我会表现得像我需要他们一样。

    你有没有遇到过这样的要求?

    如果您没有用正确的透视图查看问题域中的对象,则可能看不到 对于不可变对象。可能很容易认为问题域不存在 任何不可变的类,如果你不熟悉的话,什么时候使用它们是有利的。

    值或固定实例

    真的很有用 Java theory and practice: To mutate or not to mutate?

    我将在下面举几个例子,说明如何从不同的角度(可变的和不变的)观察对象,以阐明我所说的角度是什么意思。

    ... 你能给我们举个例子说明我们应该在哪里使用这种模式吗。

    经典值对象

    字符串和整数通常被认为是值。因此,在Java中发现String类和Integer包装类(以及其他包装类)是不可变的就不足为奇了。颜色通常被认为是一个值,因此是不可变的颜色类。

    相比之下,汽车通常不被视为价值对象。为汽车建模通常意味着创建一个状态(里程表、速度、燃油油位等)不断变化的类。但是,在某些域中,它可能是一个值对象。例如,在应用程序中,可以将一辆汽车(特别是一个汽车模型)视为一个值对象来查找给定车辆的合适机油。

    扑克牌

    你写过扑克牌程序吗?是的。我本可以把一张扑克牌描绘成一个有着易变套装和等级的易变物体。一张平局牌可以是5个固定的例子,其中替换我手上的第5张牌意味着通过改变它的套装和等级ivar将第5张扑克牌实例变为一张新牌。

    地图投影

    最后一个例子是,当我处理一些地图代码时,地图可以以各种方式显示自己 projections . 最初的代码让地图使用一个固定的,但可以改变的投影实例(就像上面的可变扑克牌)。更改地图投影意味着更改地图投影实例的IVAR(投影类型、中心点、缩放等)。

    MERCATOR_WORLD_VIEW .

        2
  •  42
  •   Community Mohan Dere    9 年前

    一般来说,不可变类 . 例如String: java.lang.String std::string 在C++中,主要是由于它的不可更改性。

    不变性带来巨大差异的一个特殊领域是并发性: ,而可变对象必须通过仔细的设计和实现使其成为线程安全的-通常这不是一项简单的任务。

    更新: Effective Java 2nd Edition 详细解决此问题-请参阅

    另请参见以下相关帖子:

        3
  •  39
  •   Amir Raminfar Hadi Rasouli    12 年前

    • 简单性-每个类只处于一种状态
    • 线程安全-由于状态无法更改,因此不需要同步

    一般来说,除非结果出现严重的性能问题,否则最好使对象不可变。在这种情况下,可变生成器对象可用于构建不可变对象,例如StringBuilder

        4
  •  17
  •   Kirk Woll    16 年前

    Hashmaps就是一个典型的例子。地图的键必须是不变的。如果该键不是不可变的,并且您更改了该键上的一个值,使得hashCode()将产生一个新的值,则映射现在被破坏(一个键现在位于哈希表中的错误位置)。

        5
  •  8
  •   BalusC    16 年前

    Java实际上是一个完整的引用。有时一个实例被多次引用。如果您更改这样一个实例,它将反映到它的所有引用中。有时候你根本不想用它来提高健壮性和线程安全性。然后,一个不可变的类是有用的,这样就必须创建一个 新的

    想象一下如果 String 是可变的。

        6
  •  6
  •   Damien_The_Unbeliever    16 年前

    需要 本身是不可变的类,但它们确实可以使某些编程任务更容易,特别是当涉及多个线程时。您不必执行任何锁定来访问一个不可变的对象,并且您已经确定的关于此类对象的任何事实在将来都将继续是正确的。

        7
  •  6
  •   Jay    16 年前

    让我们举一个极端的例子:整数常量。如果我写一个像“x=x+1”这样的语句,我想百分之百地告诉你,不管程序中其他地方发生了什么,数字“1”不会以某种方式变成2。

    整数常量不是一个类,但概念是一样的。假设我写:

    String customerId=getCustomerId();
    String customerName=getCustomerName(customerId);
    String customerBalance=getCustomerBalance(customerid);
    

    不变性仅仅意味着某类对象是常量,我们可以将它们视为常量。

    (当然,用户可以将不同的“常量对象”分配给变量。有人会写 String s=“你好”; s=“再见”; 除非我将变量设为final,否则我不能确定它在我自己的代码块中没有被更改。就像整数常量一样,我确信“1”总是相同的数字,但是“x=1”永远不会因为写“x=2”而改变。但我可以告诉你,如果我有一个不可变对象的句柄,我传递给它的任何函数都不能改变它,或者如果我复制了它的两个副本,对包含一个副本的变量的更改不会改变另一个副本。等。

        8
  •  5
  •   John Topley    16 年前

    不变性有多种原因:

    • 线程安全:不可变对象不能更改,其内部状态也不能更改,因此不需要同步。
    • 它还保证无论我通过(网络)发送什么,都必须与以前发送的状态相同。这意味着没有人(窃听者)可以来在我的不可变集合中添加随机数据。
    • String 班级。

    所以,如果你想通过网络服务发送数据,你需要一种 保证

        9
  •  5
  •   JUST MY correct OPINION    16 年前

    我将从另一个角度来讨论这个问题。我发现不可变的对象使我在阅读代码时的生活更轻松。

    如果我有一个可变的对象,如果它曾经在我的直接作用域之外使用过,我永远不知道它的值是什么。假设我创造了 MyMutableObject

    1. 我必须为我的对象创建五个浪费性的防御性副本,以确保将正确的值传递给每个方法。

    如果我用 MyImmutableObject 必须进行复制-但他们只有在确实必须进行复制时才这样做(而不是我在每次外部方法调用之前都这样做)。我节省了自己的精力来跟踪那些甚至可能不在我当前源文件中的方法,也节省了系统为了以防万一而无休止地复制不必要的防御副本的开销。

        10
  •  5
  •   hagrawal7777    8 年前

    我给未来访客的2美分:


    不可变对象是好选择的两种情况是:

    多线程环境中的并发问题可以通过同步很好地解决,但是同步是一件代价很高的事情(这里不讨论“为什么”),所以如果您使用的是不可变对象,那么就没有同步来解决并发问题,因为不可变对象的状态是不能更改的,如果状态不能更改,那么所有线程都可以无缝地访问对象。 因此,对于多线程环境中的共享对象,不可变对象是一个很好的选择。


    在使用基于哈希的集合时,需要注意的最重要的一点是,键应该是 hashCode() 应该在对象的生存期内始终返回相同的值,因为如果该值发生更改,则无法检索使用该对象创建到基于哈希的集合中的旧条目,因此会导致内存泄漏。 因此,如果您使用不可变对象作为基于哈希的集合的键,那么您可以确保不会因此发生任何内存泄漏(当然,当用作键的对象没有从其他任何地方引用时,仍然可能发生内存泄漏,但这不是这里的重点)。

        11
  •  2
  •   Shreya Agarwal    8 年前

    不可变对象是其状态在启动后不会更改的实例。 这些对象的使用是特定于需求的。

    不可变类很适合缓存,而且是线程安全的。

        12
  •  2
  •   Mozahler    7 年前

    通过不变性,您可以确保底层不可变对象的行为/状态不会改变,这样您就可以获得执行其他操作的额外优势:

    • 您可以使用多核/处理器( )轻松(因为操作顺序不再重要。)

    • 缓存 对于昂贵的操作(正如你所确信的一样

    • 我能做什么 调试
      再也没有了)

        13
  •  1
  •   Mark Peters    16 年前

    public class Scratchpad {
        public static void main(String[] args) throws Exception {
            SomeData sd = new SomeData("foo");
            System.out.println(sd.data); //prints "foo"
            voodoo(sd, "data", "bar");
            System.out.println(sd.data); //prints "bar"
        }
    
        private static void voodoo(Object obj, String fieldName, Object value) throws Exception {
            Field f = SomeData.class.getDeclaredField("data");
            f.setAccessible(true);
            Field modifiers = Field.class.getDeclaredField("modifiers");
            modifiers.setAccessible(true);
            modifiers.setInt(f, f.getModifiers() & ~Modifier.FINAL);
            f.set(obj, "bar");
        }
    }
    
    class SomeData {
        final String data;
        SomeData(String data) {
            this.data = data;
        }
    }
    

        14
  •  1
  •   Claudiu    16 年前

    在编码递归算法时,不可变的数据结构也会有所帮助。例如,假设您正在尝试解决 3SAT 问题。一种方法是执行以下操作:

    • 赋予它真实的价值。通过取出现在满足的子句来简化实例,并重复求解更简单的实例。

    如果您有一个可变结构来表示问题,那么当您在真分支中简化实例时,您必须:

    • 跟踪您所做的所有更改,并在意识到问题无法解决时撤消所有更改。这有很大的开销,因为你的递归可以走得很深,而且很难编写代码。

    String.replace -它不会替换字符串,只是给你一个新的)。实现这一点的最简单的方法是让“不可变”结构在任何修改时复制并生成一个新的结构,在有可变结构时将其减少到第二个解决方案,并且有所有的开销,但是您可以以更有效的方式来做。

        15
  •  1
  •   André Caron    16 年前

    对于不可变类的“需要”的一个原因是通过引用传递所有的东西,并且不支持对象的只读视图(即C++)。 const

    考虑一个支持观察者模式的类的简单情况:

    class Person {
        public string getName() { ... }
        public void registerForNameChange(NameChangedObserver o) { ... }
    }
    

    如果 string 如果不是一成不变的,那么 Person registerForNameChange() 正确,因为有人可以写下面的内容,有效地修改人的名字而不触发任何通知。

    void foo(Person p) {
        p.getName().prepend("Mr. ");
    }
    

    在C++中, getName() 返回 const std::string&

        16
  •  1
  •   Ryan Hayes    16 年前

    他们也给了我们一个保证。不变性的保证意味着我们可以在它们的基础上进行扩展,创造出新的模式来提高效率,而这在其他方面是不可能的。

    http://en.wikipedia.org/wiki/Singleton_pattern

        17
  •  1
  •   supercat    14 年前

    (*)一种有时很有用的模式是,可变对象有两个字段来保存其状态——一个是可变形式的,另一个是不可变形式的。对象可以作为可变的或不可变的复制,并且可以从一个或另一个引用集开始。一旦对象想要改变它的状态,它就会将不可变的引用复制到可变的引用(如果还没有这样做的话)并使不可变的引用无效。将对象复制为不可变时,如果未设置其不可变引用,则将创建一个不可变副本,并且不可变引用指向该副本。这种方法将需要比“写时完全复制”多几个复制操作(例如,要求复制一个自最后一个复制以来发生了变化的对象将需要一个复制操作,即使原始对象不再发生变化),但它避免了FFCOW将带来的线程复杂性。

        18
  •  1
  •   bharanitharan    8 年前

    为什么是不变类?

    一旦一个对象被实例化,它的状态就不能在生命周期内改变。这也使得它线程安全。

    示例:

    显然,String、Integer和BigDecimal等值一旦创建就不能在生命周期内更改。

    用例: 一旦用数据库连接对象的配置值创建了数据库连接对象,您可能就不需要更改它的状态,在那里您可以使用不可变的类

        19
  •  0
  •   tarikfasun    11 年前

    来自有效的Java; 不可变类只是一个实例不能修改的类。全部 在对象的生存期内修复。Java平台库包含许多 不可变类,包括String、装箱的基本类和BigInte- ger和BigDecimal。有很多很好的理由:不可变的类 比可变类更易于设计、实现和使用。他们不那么容易 更安全。

    推荐文章