Why continue to use getters with immutable objects

2019-02-16 07:30发布

Using immutable objects has become more and more common, even when the program at hand is never meant to be ran in parallel. And yet we still use getters, which require 3 lines of boilerplate for every field and 5 extra characters on every access (in your favorite mainstream OO language). While this may seem trivial, and many editors remove most of the burden from the programmer anyways, it is still seemingly unnecessary effort.

What are the reasons for the continued use of accessors versus direct field access of immutable objects? Specifically, are there advantages to forcing the user to use accessors (for the client or library writer), and if so what are they?


Note that I am referring to immutable objects, unlike this question, which refers to objects in general. To be clear, there are no setters on immutable objects.

8条回答
放我归山
2楼-- · 2019-02-16 07:42

Joshua Bloch, in Effective Java (2nd Edition) "Item 14: In public classes, use accessor methods, not public fields," has the following to say about exposing immutable fields:

While it’s never a good idea for a public class to expose fields directly, it is less harmful if the fields are immutable. You can’t change the representation of such a class without changing its API, and you can’t take auxiliary actions when a field is read, but you can enforce invariants.

and summarizes the chapter with:

In summary, public classes should never expose mutable fields. It is less harmful, though still questionable, for public classes to expose immutable fields.

查看更多
不美不萌又怎样
3楼-- · 2019-02-16 07:49

You can have public final fields (to imitate some kind of immutability) but it doesn't mean that referenced objects can't change their state. We still need defensive copy in some cases.

 public class Temp {
    public final List<Integer> list;

    public Temp() {
        this.list = new ArrayList<Integer>();
        this.list.add(42);
    }

   public static void foo() {
      Temp temp = new Temp();
      temp.list = null; // not valid
      temp.list.clear(); //perferctly fine, reference didn't change. 
    }
 }
查看更多
够拽才男人
4楼-- · 2019-02-16 07:50

One very practical reason for the continued practice of generating (I hope nobody writes them by hand nowadays) getters in Java programs, even for immutable "value" objects where, in my opinion, it is unnecessary overhead :

Many libraries and tools rely on the old JavaBeans conventions (or at least the getters and setters part of it).

These tools, that use reflection or other dynamic techniques to access field values via getters, cannot handle accessing simple public fields. JSP is an example that comes to my mind.

Also modern IDEs make it trivial to generate getters for one or many fields at a time, and also to change the name of the getter when the name of the field is changed.

So we just keep writing getters even for immutable objects.

查看更多
欢心
5楼-- · 2019-02-16 07:52

Immutable objects should use direct field access for uniformity and because it allows one to design objects that perform exactly how the client expects they should.

Consider a system where every mutable field was hidden behind accessors while every immutable field was not. Now consider the following code snippet:

class Node {
    private final List<Node> children;

    Node(List<Node> children) {
        this.children = new LinkedList<>(children);
    }

    public List<Node> getChildren() {
        return /* Something here */;
    }
}

Without knowing the exact implementation of Node, as you must do when you design by contract, anywhere you see root.getChildren(), you can only assume one of three things is occurring:

  • Nothing. The field children is returned as is, and you can't modify the list because you will break the immutability of Node. In order to modify the List you must copy it, an O(n) operation.
  • It is copied, for example: return new LinkedList<>(children);. This is an O(n) operation. You can modify this list.
  • An unmodifiable version is returned, for example: return new UnmodifiableList<>(children);. This is an O(1) operation. Again, in order to do modify this List you must copy it, an O(n) operation.

In all cases, modifying the returned list requires an O(n) operation to copy it, while read only access takes anywhere from O(1) or O(n). The important thing to note here is that by following design by contract you cannot know which implementation the library writer chose and thus must assume the worst case, O(n). Hence, O(n) access and O(n) to create your own modifiable copy.

Now consider the following:

class Node {
    public final UnmodifiableList<Node> children;

    Node(List<Node> children) {
        this.children = new UnmodifiableList<>(children);
    }
}

Now, everywhere you see root.children, there is exactly one possibility, namely it is an UnmodifiableList and thus you can assume O(1) access and O(n) for creating a locally mutable copy.

Clearly, one can draw conclusions about the performance characteristics of accessing the field in the latter case, whereas the only conclusion that can be made in the former is that the performance, in the worst case, and thus the case we must assume, is far worse than the direct field access. As a reminder, that means the programmer must take into account a O(n) complexity function on every access.


In summary, with this type of system, wherever one sees a getter the client automatically knows that either the getter corresponds to a mutable field, or the getter performs some sort of operation, whether it be a time consuming O(n) defensive copy operation, lazy initialization, conversion, or otherwise. Whenever the client sees a direct field access, they immediately know the performance characteristics of accessing that field.

By following this style, more information can be inferred by the programmer as to the contract provided by the object he/she is interacting with. This style also promotes uniform immutability because as soon as you change the above snippet's UnmodifiableList to the interface List, the direct field access allows the object to be mutated, thus forcing your object heirarchy to be carefully designed to be immutable from top to bottom.

The good news is, not only do you gain all the benefits of immutability, you are also able to infer the performance characteristics of accessing a field no matter where it is, without looking at the implementation and with confidence that it will never change.

查看更多
爱情/是我丢掉的垃圾
6楼-- · 2019-02-16 07:54

It's a OOP practice to encapsulate fields and then expose it only through getters method. If you expose field directly this means that you will have to make it public. Making fields public is not good idea as it exposes inner state of object.

So making your field/data members public is not a good practice and it violates Encapsulation principle of OOP. Also i would say it's not specific to Immutable objects; this is true for non-immutable objects as well.

Edit As pointed by @Thilo ; Another reason : Maybe you don't need to expose how a field is stored.

thanks @Thilo.

查看更多
Juvenile、少年°
7楼-- · 2019-02-16 07:55

Encapsulation serves several useful purposes, but the most important one is that of information hiding. By hiding the field as an implementation detail, you protect clients of the object from depending on there actually being a field there. For example, a future version of your object may want to compute or fetch the value lazily, and that can only be done if you can intercept a request to read the field.

That said, there is no reasons for getters to be particularly verbose. In the Java world in particular, even where the "get" prefix is very well entrenched, you'll still find getter methods named after the value itself (that is, a method foo() instead of getFoo()), and that's a fine way to save a few characters. In many other OO languages, you can define a getter and still use syntax that looks like a field access, so there's no extra verbosity at all.

查看更多
登录 后发表回答