代码之家  ›  专栏  ›  技术社区  ›  parkr

Java中不可变对象的缺点?[关闭]

  •  32
  • parkr  · 技术社区  · 15 年前

    Java中不可变对象的优点似乎是显而易见的:

    • 一致状态
    • 自动螺纹安全
    • 简单

    您可以使用私有的final字段和构造函数注入来支持不变性。

    但是,支持Java中不可变对象的缺点是什么呢?

    • 与ORM或Web演示工具不兼容?
    • 不灵活的设计?
    • 实施的复杂性?

    是否可以设计一个主要使用不可变对象的大规模系统(深对象图)?

    17 回复  |  直到 8 年前
        1
  •  19
  •   Jezor    8 年前

    但是,有什么坏处 支持Java中的不可变对象? 与ORM或Web不兼容 演示工具?

    基于反射的框架由于不可变的对象而变得复杂 要求 施工人员注入:

    • Java中没有默认参数,这迫使我们总是提供所有必要的依赖项。
    • 构造函数重写可能很混乱
    • 构造函数参数名通常不能通过反射获得,这迫使我们依赖参数顺序来解决依赖关系。

    实施的复杂性?

    创建不可变对象仍然是一项枯燥的任务;编译器应该注意实现细节,如 groovy

    是否可以设计一个主要使用不可变对象的大规模系统(深对象图)?

    当然是的;不可变对象为其他对象(它们更喜欢组合)提供了很好的构建基块,因为当您可以依赖于复杂对象的不可变组件时,更容易维护其不变量。对我来说唯一真正的缺点是创造了许多临时的对象(例如 String concat was a problem in the past )

        2
  •  15
  •   AndreiM    15 年前

    有了不变性,无论何时需要修改数据,都需要创建一个新对象。这可能很贵。

    想象一下,需要修改一个占用几兆内存的对象中的一位:您需要实例化一个全新的对象,分配内存等。如果您需要多次这样做,易变性变得非常有吸引力。

        3
  •  3
  •   TofuBeer    15 年前

    如果你追求易变性,那么你会发现每当你需要调用一个你不想改变对象的方法,或者你需要返回一个属于内部状态的对象时,你需要做一个防御性的拷贝。

    如果您真的看到使用多个对象的程序,您会发现它们很容易通过修改来“攻击”:

    • 传递给构造函数的对象
    • 传递给方法的对象
    • 从方法返回的对象。

    这个问题并不经常出现,因为大多数程序都不会改变数据(事实上,由于它们永远不会改变,所以它们是不变的)。

    我亲自做我能做的每件事。我可能有90%-95%的变量(参数、局部、实例、静态、异常等)标记为最终变量。在某些情况下,它必须是可变的,但绝大多数情况下它不是可变的。

    我想这可能取决于你的注意力。如果您正在为第三方编写库以供使用,那么您需要考虑的远不止是编写一个只有您(或您的团队)才能维护的应用程序。

    我发现您可以为大多数系统编写使用不可变对象的大规模应用程序,而不会有太多的麻烦。

        4
  •  3
  •   supercat    10 年前

    从根本上说,在现实世界中,与许多特定身份相关联的状态将发生变化。如果我问“乔的别克现在的位置”是什么,今天可能是西雅图的位置,明天可能是洛斯阿拉莫斯的位置。可以定义和创建 GeographicLocation 对象的值将始终表示Joe's Buick在某个特定时间点所在的位置,并且永远不会更改--如果今天它表示西雅图的一个地点,那么它将始终这样做。然而,这样的一个目标,将没有持续的身份作为“现在的位置乔的别克”。

    也可以定义事物,以便 VehicleLocation 连接到Joe别克的对象,使该对象始终表示“Joe别克的当前位置”。这样的一个物体可以保留它的身份,作为“现在的位置乔的别克”,即使汽车移动,但不会代表一个恒定的地理位置。如果考虑到乔把他的别克卖给鲍勃并买了一辆福特的情况,那么定义“身份”可能会很困难——如果对象跟踪“乔的福特的当前位置”或“鲍勃的别克的当前位置”——但在许多情况下,可以通过使用数据模型来避免此类问题,该模型可确保对象身份的某些方面永远不会受到影响。汉格。

    一个物体的一切都不可能是不变的。如果一个对象是不可变的,那么它不能有一个不可变的标识来封装当前状态之外的任何内容。然而,如果一个对象是可变的,它可以有一个不可变的身份,其含义超越了它的当前状态。在许多情况下,拥有不变的身份比拥有不变的状态更有用,在这种情况下,可变对象几乎是必不可少的。虽然在某些情况下,有可能通过拥有一个不可变对象来“模拟”可变对象,该对象将搜索不可变对象的最新版本,以查找可能在一个版本和下一个版本之间“更改”的信息,但这种方法通常效率极低。即使一个人每分钟能神奇地收到一本装订好的书,上面写着每辆车的位置,在书中查找“Joe's Buick”要比问一个“Joe's Buick的当前位置”物体要花更长的时间,而这个物体总是知道汽车在哪里。

        5
  •  2
  •   Julien Chastang    15 年前

    你几乎回答了你自己的问题。JavaBean规范,我不相信,提到了任何关于不可变的东西,但是JavaBeans是许多Java框架的重要组成部分。

        6
  •  1
  •   Lucero    15 年前

    对于习惯命令式编程风格的人来说,不可变类型的概念有点不常见。然而,在许多情况下,不变性具有严重的优点,您已经将最重要的优点命名为不变性。

    有很好的方法可以实现不可变的平衡树、队列、堆栈、出列和其他数据结构。实际上,许多现代编程语言/框架只支持不变的字符串,因为它们的优点,有时还支持其他对象。

        7
  •  1
  •   Steve Kuo    15 年前

    对于不可变对象,如果需要更改该值,则必须将其替换为新实例。根据对象的生命周期,用不同的实例替换它可能会增加持久(长)的垃圾收集时间。如果将对象保存在内存中足够长的时间,以便将其放置在持久的一代中,那么这一点就变得更加重要。

        8
  •  1
  •   Ingo    15 年前

    Java中的问题是,必须与所有这些对象一起生活,其中类看起来像:

    class Mutable {
        State1 f1;
        MoreState f2;
        void doSomething() {  // mutate the state, but don't document it }
        void doSomethingElse()  /// mutate the state heavily, do not mention in doc
    }  
    

    (注意丢失的可克隆接口)。

    现在垃圾收集器的问题不是那么大。虚拟机对短生命物体很满意。

    编译器/JIT技术的进步迟早会使优化中间临时对象创建成为可能。例如:

    BigInteger  three =, two =, i1 = ...;
    BigInteger  i2 = i1.mul(three).div(two);  
    

    JIT可以注意到中间对象i1.mul(three)可以用于最终结果,并调用在可变累加器上工作的DIV方法的变体。

        9
  •  1
  •   Tony Morris    14 年前

    Functional Java 对你的问题作出全面的回答。

        10
  •  0
  •   Jorn    15 年前

    不变性,就像其他设计模式一样,应该只在需要时使用。您举了一个线程安全的例子:在一个高度线程化的应用程序中,您可以选择不可变,而不必为自己的线程安全付出额外的代价。 但是,如果您的设计要求对象是可变的,不要因为“它是一个设计模式”而不遗余力地使它们不可变。

    对于您的图,您可以选择使节点不可变,并让另一个类处理它们之间的连接,或者您可以创建一个可变的节点,该节点处理自己的子节点,并具有一个不可变的值类。

        11
  •  0
  •   Kevin Montrose    15 年前

    在Java中使用不可变对象的最大成本可能是未来的开发人员不会期望它或习惯于这种风格。期望大量记录或者观察大量的对象,随着时间的推移产生可变的对等体。

    也就是说,我能想到的避免不可变对象的唯一真正技术原因是GC搅动。对于大多数应用程序,我不认为这是避免它们的一个令人信服的理由。

    我用一个90%个不可变的对象做的最大的事情是一个玩具方案EXQY解释器,所以它当然可以做复杂的Java项目。

        12
  •  0
  •   fabiokaminski    14 年前

    在不变的数据中,你不会设置两次…见哈斯凯尔和斯卡拉·瓦尔斯(和库尔的克洛尤)。

    例如。。对于数据结构..和树一样,当您对树执行写操作时,实际上是在不可变树之外添加元素。做完之后……树和树枝重新组合成一棵新树。这样,您就可以非常安全地执行并发读写。

    在传统模型中,您必须锁定一个值,因为它可以随时重置。所以…你最终会得到一个非常热的区域,因为它们在那里的作用是连续的。

    使用imuttable数据,您不能多次设置。这是一种全新的编程方式。最后你可能会用到更多的内存。但是平行化是自然的,无痛的。

        13
  •  0
  •   Jay    14 年前

    就像任何工具一样,你必须知道何时使用它,何时不使用。

    像Tehblanx指出的那样,如果你想改变一个保存不变对象的变量的状态,你必须创建一个新的对象,这可能会很昂贵,特别是如果对象很大而且很复杂。绝对正确,但这仅仅意味着你必须明智地决定哪些对象应该是可变的,哪些应该是不可变的。如果有人说所有的物体都应该是不可变的,那简直是胡说八道。

    我倾向于说,表示单个逻辑“事实”的对象应该是不可变的,而表示多个事实的对象应该是可变的。例如,整数或字符串应该是不可变的。包含名称、地址、当前金额、上次购买日期等的“客户”对象应该是可变的。当然,我可以立即想到这样一个一般规则的一百个例外。我一直在做的一个例外是,当我有一个类作为包装器来保存一个原语时,在某些情况下,原语是不合法的,比如在一个集合中,但是我需要不断地更新它。

        14
  •  0
  •   Hai Minh Nguyen    14 年前

    在Java中,方法不能返回多个对象,比如 return a, b, c . 返回对象数组会使代码看起来很难看。在这种情况下,我必须将可变对象传递给方法,并让它更改这些对象的状态。但是,我不知道返回多个对象是否是代码味道。

        15
  •  0
  •   Adam Gent    11 年前

    答案是没有。没有任何好的理由是可变的。

    很多框架(或框架版本)都需要可变对象才能使用它们(Spring我在你的方向上怒目而视),你确实会遇到这样的问题。当你和他们一起工作并浏览代码时,你会愤怒地挥动拳头,因为你需要在原本可以轻易避免的代码块中引入肮脏的可变性。

    我敢肯定,在一些有限的角情况下(可能更假定是任何情况),对象创建和收集的开销是不可接受的。但是,我敦促那些提出这种观点的人去研究像scala这样的语言,其中包含的集合在默认情况下是不可变的,然后再研究基于这一概念构建的大量性能关键型应用程序。

    这当然是夸张。在现实中,你应该首先使用不变性,看看它是否会导致你 可测量的 问题,如果确实如此,那么就引入了可变性,但要确保您能够证明它解决了您的问题。否则,你只是为了没有利益而建立了责任。在这样做的过程中,我认为您将很难找到“实现复杂性”和“不灵活”的客观案例。

        16
  •  0
  •   Arturo Hernandez    11 年前

    一些不可变对象的实现具有更新不可变对象的事务性方法。类似于数据库如何提供安全提交和回滚。但与这里的许多答案形成了明显的对比。不变的对象永远不会改变。典型的操作是。

    B = append(A,C)
    

    B是一个新物体。就像a和c一样,a和c没有被修改。在内部,红黑树实现使得这样的语义足够快,可以使用。

    其缺点是,它不如使业务到位的速度快。但这只比较了系统的一个部分。在评估可能的缺点时,我们需要从整体上看系统。我个人对整个影响没有清晰的了解。尽管我怀疑不变性最终会胜出。

    我知道一些专家认为红黑树的顶层存在争议。这对吞吐量有负面影响。

        17
  •  -1
  •   Zarkonnen    15 年前

    对于不可变的数据结构,我最大的担心是如何保存/重构它们。也就是说,如果一个类有最后的字段,我就不能实例化它,并且 然后 设置它的字段。