Why make defensive copies in getters inside immutable classes?

前端 未结 7 610
余生分开走
余生分开走 2020-12-08 16:13

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 conside

相关标签:
7条回答
  • 2020-12-08 16:43

    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.

    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.

    0 讨论(0)
  • 2020-12-08 16:47

    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:

    public class MyImmutable
    {
      private final StringBuilder foo = new StringBuilder("bar");
    
      public StringBuilder getFoo()
      {
        return foo; // BAD!
      }
    }
    

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

    0 讨论(0)
  • 2020-12-08 16:51

    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 Immutable object as well:

    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:

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

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

    public List<Integer> getSomeIds() {
        return Collections.unmodifiableList(someIds);
    }
    
    0 讨论(0)
  • 2020-12-08 16:52

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

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

    Deep immutability 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?

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

    0 讨论(0)
  • 2020-12-08 16:55

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

        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
    
    0 讨论(0)
  • 2020-12-08 16:56

    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.

    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.

    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...

    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.

    0 讨论(0)
提交回复
热议问题