真的用过clone()吗? getter/setter 中的防御性复制怎么样?

发布于 2024-07-21 07:52:48 字数 537 浏览 12 评论 0原文

人们实际上使用过防御性 getter/setter 吗? 对我来说,99% 的情况下,您希望在另一个对象中设置的对象成为同一对象引用的副本,并且您希望对其所做的更改也在其设置的对象中进行。如果你setDate ( Date dt ) 并稍后修改 dt ,谁在乎呢? 除非我想要一些基本的不可变数据 bean,它只包含原语,也许还有像日期这样简单的东西,否则我从不使用它。

就克隆而言,存在复制深度或浅度的问题,因此知道克隆对象时会产生什么似乎有点“危险”。 我想我只使用过clone()一两次,那就是复制对象的当前状态,因为另一个线程(即访问会话中同一对象的另一个HTTP请求)可能正在修改它。

编辑-我在下面发表的评论更多的是问题:

但话又说回来,你确实改变了日期,所以这是你自己的错,因此整个讨论都是“防御性”一词。 如果中小型开发人员组中的所有应用程序代码都在您自己的控制之下,那么仅记录您的类是否足以作为制作对象副本的替代方法? 或者这是不必要的,因为在调用 setter/getter 时您应该始终假设某些内容没有被复制?

Do people practically ever use defensive getters/setters? To me, 99% of the time you intend for the object you set in another object to be a copy of the same object reference, and you intend for changes you make to it to also be made in the object it was set in. If you setDate ( Date dt ) and modify dt later, who cares? Unless I want some basic immutable data bean that just has primitives and maybe something simple like a Date, I never use it.

As far as clone, there are issues as to how deep or shallow the copy is, so it seems kind of "dangerous" to know what is going to come out when you clone an Object. I think I have only used clone() once or twice, and that was to copy the current state of the object because another thread (ie another HTTP request accessing the same object in Session) could be modifying it.

Edit - A comment I made below is more the question:

But then again, you DID change the Date, so it's kind of your own fault, hence whole discussion of term "defensive". If it is all application code under your own control among a small to medium group of developers, will just documenting your classes suffice as an alternative to making object copies? Or is this not necessary, since you should always assume something ISN'T copied when calling a setter/getter?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

送舟行 2024-07-28 07:52:48

摘自 Josh Bloch 的《Effective Java》:

您必须进行防御性编程,假设您的类的客户将尽最大努力破坏其不变量。 如果有人试图破坏系统的安全性,这实际上可能是正确的,但更有可能的是,您的类将不得不应对由于使用 API 的程序员的无心错误而导致的意外行为。 不管怎样,花时间编写能够应对不良客户端的健壮类是值得的。

第 24 项:在需要时制作防御性副本

From Josh Bloch's Effective Java:

You must program defensively with the assumption that clients of your class will do their best to destroy its invariants. This may actually be true if someone tries to break the security of your system, but more likely your class will have to cope with unexpected behavior resulting from honest mistakes on the part of the programmer using your API. Either way, it is worth taking the time to write classes that are robust in the face of ill-behaved clients.

Item 24: Make defensive copies when needed

亚希 2024-07-28 07:52:48

这是一个重要的问题。 基本上,您必须考虑通过 getter 或调用另一个类的 setter 赋予任何其他类的类的任何内部状态。 例如,如果您这样做:

Date now = new Date();
someObject.setDate(now);
// another use of "now" that expects its value to not have changed

那么您可能会遇到两个问题:

  1. someObject 可能会更改“now”的值,这意味着上面的方法稍后使用该变量时可能具有与预期不同的值,并且
  2. 如果在将“now”传递给 someObject 后更改了它的值,并且如果 someObject 没有使防御性副本,那么您就更改了 someObject 的内部状态。

您应该防止这两种情况的发生,或者您应该记录您对允许或禁止的内容的期望,具体取决于代码的客户端是谁。 另一种情况是,一个类有一个 Map,并且您为 Map 本身提供了一个 getter。 如果Map是对象内部状态的一部分,并且该对象希望完全管理Map的内容,那么您应该<永远不要让地图出去。 如果您必须为地图提供 getter,则返回 Collections.unmodifyingMap(myMap) 而不是 myMap。 由于潜在的成本,您可能不想制作克隆或防御性副本。 通过返回包装好的 Map 使其无法被修改,您可以保护您的内部状态不被另一个类修改。

由于多种原因,clone() 通常不是正确的解决方案。 一些更好的解决方案是:

  1. 对于吸气剂:
    1. 不返回 Map,而是仅将 Iterator 返回到 keySetMap.Entry > 或任何允许客户端代码执行其需要执行的操作的内容。 换句话说,返回本质上是内部状态的只读视图的内容,或者
    2. 返回包装在不可变包装器中的可变状态对象,类似于 Collections.unmodifyingMap()
    3. 不是返回一个Map,而是提供一个get方法,该方法接受一个键并从地图返回相应的值。 如果所有客户端都会使用 Map 从中获取值,那么就不要向客户端提供 Map 本身; 相反,提供一个封装 Mapget() 方法的 getter。
  2. 对于构造函数:
    1. 在对象构造函数中使用复制构造函数来复制传入的任何可变内容。
    2. 尽可能采用不可变数量作为构造函数参数,而不是可变数量。 例如,有时采用 new Date().getTime() 返回的 long 值而不是 Date 对象是有意义的。
    3. 尽可能多地使用 final 状态,但请记住 final 对象仍然可以是可变的,并且 final 数组仍然可以进行修改。

在所有情况下,如果存在关于谁“拥有”可变状态的问题,请将其记录在 getter、setter 或构造函数中。 将其记录在某处。

下面是一个错误代码的简单示例:

import java.util.Date;

public class Test {
  public static void main(String[] args) {
    Date now = new Date();
    Thread t1 = new Thread(new MyRunnable(now, 500));
    t1.start();
    try { Thread.sleep(250); } catch (InterruptedException e) { }
    now.setTime(new Date().getTime());   // BAD!  Mutating our Date!
    Thread t2 = new Thread(new MyRunnable(now, 500));
    t2.start();
  }

  static public class MyRunnable implements Runnable {
    private final Date date;
    private final int  count;

    public MyRunnable(final Date date, final int count) {
      this.date  = date;
      this.count = count;
    }

    public void run() {
      try { Thread.sleep(count); } catch (InterruptedException e) { }
      long time = new Date().getTime() - date.getTime();
      System.out.println("Runtime = " + time);
    }
  }
}

应该看到每个可运行程序休眠 500 毫秒,但您却得到了错误的时间信息。 如果您更改构造函数以制作防御性副本:

    public MyRunnable(final Date date, final int count) {
      this.date  = new Date(date.getTime());
      this.count = count;
    }

那么您将获得正确的时间信息。 这是一个简单的例子。 您不想调试一个复杂的示例。

注意:无法正确管理状态的一个常见结果是在迭代集合时出现ConcurrentModificationException

你应该防御性地编码吗? 如果您可以保证同一小群专家程序员将始终是编写和维护您的项目的人,那么他们将持续致力于该项目,以便保留对项目细节的记忆,相同的人将在项目的整个生命周期中致力于该项目,并且该项目永远不会变得“大”,那么也许您可以不这样做。 但除极少数情况外,防御性编程的成本并不大,而且好处也很大。 另外:防御性编码是一个好习惯。 您不想鼓励养成将可变数据传递到不应该有它的地方的坏习惯。 这有一天会咬你的。 当然,所有这些都取决于您的项目所需的正常运行时间。

This is a non-trivial question. Basically, you have to think about any internal state of a class that you give to any other class via getter or by calling another class' setter. For example, if you do this:

Date now = new Date();
someObject.setDate(now);
// another use of "now" that expects its value to not have changed

then you potentially have two problems:

  1. someObject could potentially change the value of "now", meaning the method above when it later uses that variable could have a different value than it expected, and
  2. if after passing "now" to someObject you change its value, and if someObject did not make a defensive copy, then you've changed the internal state of someObject.

You should either protected against both cases, or you should document your expectation of what is allowed or disallowed, depending on who the client of the code is. Another case is when a class has a Map and you provide a getter for the Map itself. If the Map is part of the internal state of the object and the object expects to fully manage the contents of the Map, then you should never let the Map out. If you must provide a getter for a map, then return Collections.unmodifiableMap(myMap) instead of myMap. Here you probably do not want to make a clone or defensive copy due to the potential cost. By returning your Map wrapped so that it cannot be modified, you are protecting your internal state from being modified by another class.

For many reasons, clone() is often not the right solution. Some better solutions are:

  1. For getters:
    1. Instead of returning a Map, return only Iterators to either the keySet or to the Map.Entry or to whatever allows client code to do what it needs to do. In other words, return something that is essentially a read-only view of your internal state, or
    2. Return your mutable state object wrapped in an immutable wrapper similar to Collections.unmodifiableMap()
    3. Rather than returning a Map, provide a get method that takes a key and returns the corresponding value from the map. If all clients will do with the Map is get values out of it, then don't give clients the Map itself; instead, provide a getter that wraps the Map's get() method.
  2. For constructors:
    1. Use copy constructors in your object constructors to make a copy of anything passed in that is mutable.
    2. Design to take immutable quantities as constructor arguments when you can, rather than mutable quantities. Sometimes it makes sense to take the long returned by new Date().getTime(), for example, rather than a Date object.
    3. Make as much of your state final as possible, but remember that a final object can still be mutable and a final array can still be modified.

In all cases, if there is a question about who "owns" mutable state, document it on the getters or setters or constructors. Document it somewhere.

Here's a trivial example of bad code:

import java.util.Date;

public class Test {
  public static void main(String[] args) {
    Date now = new Date();
    Thread t1 = new Thread(new MyRunnable(now, 500));
    t1.start();
    try { Thread.sleep(250); } catch (InterruptedException e) { }
    now.setTime(new Date().getTime());   // BAD!  Mutating our Date!
    Thread t2 = new Thread(new MyRunnable(now, 500));
    t2.start();
  }

  static public class MyRunnable implements Runnable {
    private final Date date;
    private final int  count;

    public MyRunnable(final Date date, final int count) {
      this.date  = date;
      this.count = count;
    }

    public void run() {
      try { Thread.sleep(count); } catch (InterruptedException e) { }
      long time = new Date().getTime() - date.getTime();
      System.out.println("Runtime = " + time);
    }
  }
}

You should see that each runnable sleeps for 500 msec, but instead you get the wrong time information. If you change the constructor to make a defensive copy:

    public MyRunnable(final Date date, final int count) {
      this.date  = new Date(date.getTime());
      this.count = count;
    }

then you get the correct time information. This is a trivial example. You don't want to have to debug a complicated example.

NOTE: A common result of failure to properly manage state is a ConcurrentModificationException when iterating over a collection.

Should you code defensively? If you can guarantee that the same small team of expert programmers will always be the ones writing and maintaining your project, that they will continuously work on it so they retain memory of the details of the project, that the same people will work on it for the lifetime of the project, and that the project will never become "large," then perhaps you can get away with not doing so. But the cost to defensive programming is not large except in the rarest of cases -- and the benefit is large. Plus: defensive coding is a good habit. You don't want to encourage the development of bad habits of passing mutable data around to places that shouldn't have it. This will bite you some day. Of course, all of this depends on the required uptime of your project.

淡淡的优雅 2024-07-28 07:52:48

对于这两个问题,重点是对国家的明确控制。 也许大多数时候你可以不用考虑这些事情就“逃脱”。 随着应用程序变得越来越大,并且越来越难以推理状态及其在对象之间传播的方式,这种情况往往不太正确。

您已经提到了您需要对此进行控制的一个主要原因 - 能够在另一个线程访问数据时安全地使用数据。 也很容易犯这样的错误:

class A {
   Map myMap;
}


class B {
   Map myMap;
   public B(A a)
   {
        myMap = A.getMap();//returns ref to A's myMap
   }
    public void process (){ // call this and you inadvertently destroy a
           ... do somethign destructive to the b.myMap... 
     }
}

重点不是你总是想要克隆,那将是愚蠢和昂贵的。 重点不是要对何时适合进行此类事情做出笼统的声明。

For both of these issues, the point is explicit control of state. It may be that most of the time you can "get away" without thinking about these things. This tends to be less true as your application gets larger and it gets harder to reason about state and how it propagates among objects.

You've already mentioned a major reason why you'd need to have control over this - being able to use the data safely while another thread was accessing it. It's also easy to make mistakes like this:

class A {
   Map myMap;
}


class B {
   Map myMap;
   public B(A a)
   {
        myMap = A.getMap();//returns ref to A's myMap
   }
    public void process (){ // call this and you inadvertently destroy a
           ... do somethign destructive to the b.myMap... 
     }
}

The point is not that you always want to clone, that would be dumb and expensive. The point is not to make blanket statements about when that sort of thing is appropriate.

蓦然回首 2024-07-28 07:52:48

我使用 Clone() 在用户会话中保存对象状态,以便在编辑期间进行撤消。 我也在单元测试中使用了它。

I've used Clone() to save object state in the user's session to allow for Undo during edits. I've also used it in unit tests.

[浮城] 2024-07-28 07:52:48

我可以想到一种情况,克隆比复制构造函数更可取。 如果您有一个函数,它接受 X 类型的对象,然后返回它的修改后的副本,如果您想保留内部的、非 X 相关的信息,那么该副本最好是克隆。 例如,将 Date 增加 5 小时的函数可能很有用,即使它传递的是 SpecialDate 类型的对象。 也就是说,很多时候使用组合而不是继承可以完全避免此类问题。

I can think of one situation where clone is much preferable to copy constructors. If you have a function which takes in an object of type X and then returns a modified copy of it, it may be preferable for that copy to be a clone if you want to retain the internal, non-X related information. For example, a function which increments a Date by 5 hours might be useful even if it was passed an object of type SpecialDate. That said, a lot of the time using composition instead of inheritance would avoid such concerns entirely.

谜兔 2024-07-28 07:52:48

我不喜欢clone()方法,因为总是需要进行类型转换。 出于这个原因,我大部分时间都使用复制构造函数。 它更清楚地说明了它的作用(新对象),并且您可以很好地控制它的行为方式或副本的深度。

在我的工作中,我们并不担心防御性编程,尽管这是一个坏习惯。 但大多数时候都没什么问题,但我想我要仔细看看。

I don't like the clone() method, because there is always a type-cast needed. For this reason I use the copy-constructor most of the time. It states more clearly what it does (new object) and you have much control about how it behaves, or how deep the copy is.

At my work we don't worry about defensive programming, although that is a bad habbit. But most of the time it goes ok, but I think I am going to give it a closer look.

腹黑女流氓 2024-07-28 07:52:48

在“防御性文案讨论”中我总是忽略的一件事是性能方面。
恕我直言,这种讨论是性能与可读性/安全性/稳健性的完美例子。

防御副本非常适合 Ropbust 颂歌。 但如果您在应用程序的时间关键部分使用它,则可能会成为主要的性能问题。 我们最近进行了一次讨论,其中数据向量将其数据存储在 double[] 值中。 getValues() 返回values.clone()。
在我们的算法中,为很多不同的对象调用了 getValues()。 当我们想知道为什么这段简单的代码需要这么长时间执行时,我们检查了代码 - 用返回值替换了 return values.clone() ,突然间我们的总执行时间降低到了原来的 1/10 以下价值。 好吧 - 我不需要说我们选择跳过防守。

注意:我不再是一般的防御性副本。 但是在克隆时要动动脑子!

One thing I'm alwyas missing at a "defensive copy discussion" is the performance aspect.
That aiscussion is IMHO a perfect example of performance vs readability/security/robustness.

Defence copies are great for ropbust ode. But if you use it in a time critical part of your app it can be a major performance issue. We had this discussion recently where a data vector stored its data in a double[] values. getValues() returned values.clone().
In our algorithm, getValues() was called for a lot of different objects. When we were wondering, why this simple piece of code took so long to execute, we inspected the code - replaced the return values.clone() with return values and suddenly our total execution time was lowered to less than 1/10 of the original value. Well - I don't need to say that we chose to skip the defensiveness.

Note: I'm not again defensive copies in general. But use your brain when clone()ing!

最初的梦 2024-07-28 07:52:48

我已经开始使用以下做法:

  1. 在类中创建复制构造函数,但使它们受到保护。 其原因是,使用 new 运算符创建对象可能会在处理派生对象时导致各种问题。

  2. 按如下方式创建 Copyable 接口:

     public interface Copyable<T> {
            public T copy();
     }

让实现 Copyable 的类的复制方法调用受保护的复制构造函数。 然后派生类可以调用 super.Xxx(obj_to_copy); 利用基类复制构造函数并根据需要添加其他功能。

事实上,Java 支持 协变返回类型 使这项工作有效。 派生类只需根据需要实现 copy() 方法,并为其特定类返回类型安全值。

I have started using the following practice:

  1. Create copy constructors in your classes but make them protected. The reason for this is that creating objects using the new operator can lead to various issues when working with derived objects.

  2. Create a Copyable interface as follows:

     public interface Copyable<T> {
            public T copy();
     }

Have the copy method of classes implementing Copyable call the protected copy constructor. Derived classes can then call super.Xxx(obj_to_copy); to leverage the base class copy constructor and adding additional functionality as required.

The fact that Java supports covariant return type makes this work. Derived classes simply implement the copy() method as appropriate and return a type-safe value for their particular class.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文