java 为什么要在不可变类中的 getter 中创建防御性副本?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10951820/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-31 03:12:55  来源:igfitidea点击:

Why make defensive copies in getters inside immutable classes?

java

提问by iozee

This question is about good programming practices and avoiding potential holes.
I read Joshua Bloch's Effective Java and here is what I wonder:
Why should I consider making defensive copies in getter methods in my immutable class with no mutators in it?
And second: why should I make my fields finalin addition to private? Is this only about performance (not security) ?

这个问题是关于良好的编程实践和避免潜在的漏洞。
我阅读了 Joshua Bloch 的 Effective Java 并且我想知道:
为什么我应该考虑在我的不可变类中的 getter 方法中制作防御性副本,而其中没有突变器?
第二:为什么除了private之外,我还应该将我的字段设为final?这仅与性能有关(而非安全性)吗?

回答by Tomasz Nurkiewicz

I believe this is the case that justifies this statements:

我相信这种情况可以证明这种说法是正确的:

public class Immutable {

    private final String name;

    private Date dateOfBirth;

    public Immutable(String name, Date dateOfBirth) {
        this.name = name;
        this.dateOfBirth = dateOfBirth;
    }

    public String getName() {
        return name;
    }

    public Date getDateOfBirth() {
        return dateOfBirth;
    }

}

getName()is fine as it returns immutable object as well. However the getDateOfBirth()method can break immutability because the client code can modify returned object, hence modifying the Immutableobject as well:

getName()很好,因为它也返回不可变对象。然而,该getDateOfBirth()方法可以打破不变性,因为客户端代码可以修改返回的对象,因此也修改Immutable对象:

Immutable imm = new Immutable("John", new Date());

imm.getName(); //safe
Date dateOfBirth = imm.getDateOfBirth();
//hundreds of lines later
dateOfBirth.setTime(0);  //we just modified `imm` object

It is safe to return immutable objects and primitives (as they are returned by value). However you need to make defensive copies of mutable objects, like Date:

返回不可变对象和原语是安全的(因为它们是按值返回的)。但是,您需要制作可变对象的防御性副本,例如Date

public Date getDateOfBirth() {
    return new Date(dateOfBirth.getTime());
}

and wrap collections in immutable views (if they are mutable), e.g. see Collections.unmodifiableList():

并将集合包装在不可变视图中(如果它们是可变的),例如参见Collections.unmodifiableList()

public List<Integer> getSomeIds() {
    return Collections.unmodifiableList(someIds);
}

回答by supercat

Object references in Java are promiscuous; if an object reference is passed to a method, that method will have no way of knowing who else might have a reference to that same object, or likewise what they might try to do with it. Likewise if a method returns an object reference, there's no telling what the recipient might do with it. Consequently, if a type allows anyone with a reference to mutate it, the only way an entity which holds a reference to such an object can ensure it won't be mutated is to keep a reference to a private object, which outside has never had, and will never get, a reference to.

Java 中的对象引用是混杂的;如果将对象引用传递给方法,则该方法将无法知道还有谁可能拥有对同一对象的引用,或者同样地,他们可能会尝试用它做什么。同样,如果一个方法返回一个对象引用,则不知道接收者可能会用它做什么。因此,如果一个类型允许任何拥有引用的人对其进行变异,那么持有对此类对象的引用的实体可以确保它不会被变异的唯一方法是保留对私有对象的引用,而外部对象从未有过该引用,并且永远不会得到引用。

An alternative to the style of defensive copying shown here (constructing a new object instance every time information is requested) is to have an object's information-retrieval method accept a mutable object and populate that object with the information that is retrieved. This approach requires that the code which is asking for the information construct a (likely blank) object to accept the information before calling the method to retrieve it, but if one will e.g. use a loop to retrieve, examine briefly, and discard 100 pieces of information, it may be faster to construct one object which can be reused every time through the loop, than to construct 100 new objects each of which will be used only briefly.

此处显示的防御性复制样式(每次请求信息时构造一个新对象实例)的替代方法是让对象的信息检索方法接受可变对象并用检索到的信息填充该对象。这种方法要求请求信息的代码在调用方法检索信息之前构造一个(可能是空白的)对象来接受信息,但是如果一个人将例如使用循环检索,简要检查并丢弃 100 条信息,构建一个可以在每次循环中重复使用的对象可能比构建 100 个新对象更快,每个新对象都只会被短暂使用。

回答by c0der

I slightly modified Tomasz Nurkiewicz answer, to demonstrate why making dateOfBirth final does not defend it from being changed by a client class:

我稍微修改了 Tomasz Nurkiewicz 的答案,以说明为什么将 dateOfBirth 设为 final 并不能保护它不被客户端类更改:

    public class Immutable {

        private final String name;
        private final Date dateOfBirth;

        public Immutable(String name, Date dateOfBirth) {
            this.name = name;
            this.dateOfBirth = dateOfBirth;
        }

        public String getName() { return name;  }

        public Date getDateOfBirth() { return dateOfBirth;  }

        public static void main(String[] args) {

            //create an object
            Immutable imm = new Immutable("John", new Date());

            System.out.println(imm.getName() + " " + imm.getDateOfBirth());

            //try and modify object's intenal
            String name = imm.getName(); //safe because Integer is immutable
            name = "George";             //has no effect on imm

            Date dateOfBirth = imm.getDateOfBirth();
            dateOfBirth.setTime(0);  //we just modified `imm` object

            System.out.println(imm.getName() + " " + imm.getDateOfBirth());
        }
    }

    **Output:** 
    John Wed Jan 13 11:23:49 IST 2016
    John Thu Jan 01 02:00:00 IST 1970

回答by Andrea Parodi

You should do defensive copy only if the object you're returning in your getter is mutable, because the client could otherwise change your object state.

仅当您在 getter 中返回的对象是可变的时才应该进行防御性复制,否则客户端可能会更改您的对象状态。

Regarding the final question, it's no strictly necessary to make the fields final, but make them final grant that cannot be modified once the object is created.

关于最后一个问题,严格来说没有必要使字段成为最终的,而是使它们成为一旦创建对象就无法修改的最终授权。

Infact, if you need to modify some fields of your object after it is created, this also is fine, but you must assure that client code cannot distinguish that the object state has been altered. What you have to make immutable is the external visible state of the object, not the internal state.

事实上,如果您在创建对象后需要修改对象的某些字段,这也可以,但是您必须确保客户端代码无法区分对象状态已被更改。您必须使不可变的是对象的外部可见状态,而不是内部状态。

E.g. the String class does not calculate it's hashcode on creation, it calculate it on the first time it's needed, and then cache it on private mutable field.

例如,String 类在创建时不计算它的哈希码,它在第一次需要时计算它,然后将它缓存在私有可变字段上。

I'm assuming that your class is declared final or have only private constructors, otherwise subclasses of it could alter your non-final fields in unpredictable ways...

我假设你的类被声明为 final 或者只有私有构造函数,否则它的子类可能以不可预测的方式改变你的非 final 字段......

Some example code to clarify:

一些示例代码来澄清:

public final class Question {      //final class to assure that inheritor could not
                                   // make it mutable

    private int textLenCache = -1;     //we alter it after creation, only if needed  
    private final String text;

    private Date createdOn;

    public Immutable(String text, Date createdOn) {
        Date copy=new Date(createdOn.getTime() ) //defensive copy on object creation

        //Ensure your class invariants, e.g. both params must be non null
        //note that you must check on defensive copied object, otherwise client 
        //could alter them after you check.
        if (text==null) throw new IllegalArgumentException("text");
        if (copy==null) throw new IllegalArgumentException("createdOn");

        this.text= text;  //no need to do defensive copy an immutable object
        this.createdOn= copy;
    }

    public int getTextLen() {  
         if (textLenCache == -1)
            textLenCache=text.length();   //client can't see our changed state, 
                                          //this is fine and your class is still 
                                          //immutable
         return textLenCache;    
    }

    public Date getCreatedOn() {
        return new Date(createdOn.getTime());         //Date is mutable, so defend!
    }

}

EDIT:

编辑

The defensive copy on the constructor mutable params is needed for two reasons:

需要构造函数可变参数上的防御性副本有两个原因:

  1. client code could change the state of the parameter after your object is created. You need to make a copy of the parameter to avoid this possibility. E.g. think if String object constructor String(char[] value)had used the char array you provide without copy it: you will be able to alter the String content by alter the char array you provide in constructor.

  2. you want to be sure that the mutable object state does not change between the time when you check for constraint on it and the time you copy it i your field. For this reaon, you always have to check constraint on your local copy of the parameter.

  1. 创建对象后,客户端代码可能会更改参数的状态。您需要复制参数以避免这种可能性。例如,如果 String 对象构造函数 String(char[] value)使用了您提供的字符数组而不复制它:您将能够通过更改构造函数中提供的字符数组来更改字符串内容。

  2. 您希望确保可变对象状态在您检查它的约束和在您的字段中复制它的时间之间不会改变。为此,您必须始终检查对参数本地副本的约束。

回答by Gili

While the enclosing class may be immutable, the references returned by its getter methods might be mutable, allowing the caller to modify the immutable's transitive state. Example:

虽然封闭类可能是不可变的,但其 getter 方法返回的引用可能是可变的,允许调用者修改不可变的传递状态。例子:

public class MyImmutable
{
  private final StringBuilder foo = new StringBuilder("bar");

  public StringBuilder getFoo()
  {
    return foo; // BAD!
  }
}

You should use privatefor encapsulation (prevent classes from depending on your class' implementation details). You should use finalto make sure you don't modify the field by mistake if it isn't meant to be modified (and yes, it mighthelp performance).

您应该使用private进行封装(防止类依赖于您的类的实现细节)。如果不打算修改该字段,您应该使用final确保不会错误地修改该字段(是的,它可能有助于提高性能)。

回答by Johan Sj?berg

Why should I consider making defensive copies in getter methods in my immutable class with no mutators in it?

为什么我应该考虑在我的不可变类中的 getter 方法中制作防御性副本,其中没有突变器?

This is only useful if the returned objects are not already immutable.

这仅在返回的对象不是不可变的情况下才有用。

Deepimmutability like this is useful to prevent changes in any objects held by the immutable class.

像这样的深度不变性对于防止不可变类持有的任何对象发生更改非常有用。

Consider you have a Cache of objects. Whenever an object is retrieved from the cache and altered you run the risk of also modifying the value in the cache.

考虑你有一个对象缓存。每当从缓存中检索并更改对象时,您都会冒着修改缓存中值的风险。

Why should I make my fields final in addition to private?

除了私有之外,为什么我还应该将我的字段设为 final?

Simply to aid you achieve immutability and prevent the values from accidently or deliverately be altered once set (e.g., by subclassing).

只是为了帮助您实现不变性并防止值在设置后被意外或交付更改(例如,通过子类化)。

回答by mprabhat

1 If you don't make defensive copy, you can just let your object be given out, once given out, no more your class is immutable, caller is free to change the object.

1 如果你不做防御性复制,你可以让你的对象被发出,一旦发出,你的类就不再是不可变的,调用者可以自由地改变对象。

public class Immutable {
   private List<Object> something;

   public List<Object> getSomething(){
      return something; // everything goes for a toss, once caller has this, it can be changed
   }
}

2 If your field is just private and not final it means you can reinitialize the field, but if your field is final it will be initialized only once and not multiple times and you achieve immutability.

2 如果您的字段只是私有的而不是最终的,则意味着您可以重新初始化该字段,但如果您的字段是最终的,它将仅被初始化一次而不是多次,并且您实现了不变性。