I recently encountered code like the following:
public List<Item> getItems() {
if (items == null) {
items = new ArrayList<Item>();
}
return this.items;
}
and crucially, no setter method.
So if you wanted to add to the arrayList, you would have to do
foo.getItems().add(...)
rather than
foo.setItems(myArrayList)
I've not seen this idiom before, and I can't say I like it, but when I generated some mapping code using mapstruct.org (great tool by the way), mapstruct handles it fine and correctly generates code that uses the getter as a setter as well.
I'm just wondering - is this a common idiom that I've somehow missed? It seems pointless to me, but perhaps there is some wisdom behind it I'm not seeing?
Getters and setters are used to protect your data, particularly when creating classes. For each instance variable, a getter method returns its value while a setter method sets or updates its value. Given this, getters and setters are also known as accessors and mutators, respectively.
Getter and Setter are methods used to protect your data and make your code more secure. Getter returns the value (accessors), it returns the value of data type int, String, double, float, etc.
In general, they should be public. If they are private they can only be called from within your class and, since you already have access to the private variables within your class, are redundant. The point of them is to allow access to these variables to other, outside, objects.
The reason for declaring the getters and setters private is to make the corresponding part of the object's abstract state (i.e. the values) private. That's largely independent of the decision to use getters and setters or not to hide the implementation types, prevent direct access, etc.
The problem isn't with the lazy initialisation (that part is fine) but the exposure of what should be an implementation detail. Once you expose the actual, mutable(!) list object you store in your field, the caller of the code can do anything with the list, even things you don't expect.
They could for example remove objects when really all you want to allow is add()
. They could modify it from different threads, making your code break in interesting and frustrating ways. They could even cast it to a raw List
and fill it with entirely different types of objects, making your code throw ClassCastException
s.
In other words, it makes it impossible to enforce class invariants.
Note that there are two things that work together to cause this problem:
If any of those two aren't true, there's no problem. So this is fine:
public String getFoo() {
return this.foo;
}
Because String
is immutable. And this is fine too:
public List<String> getFooList() {
return new ArrayList<>( this.fooList );
}
Because now you're returning a defensive copy and not the actual object. (However, if the elements of the lists were mutable, you'd be in trouble again.)
Imagine this scenario:
public class Foo {
private List<String> list;
public Foo( List<String> list ) {
this.list = list; // Don't do this
}
...
}
This looks perfectly harmless, and you see it in many places. However there is a hidden catch here too: by not making a copy before storing the list, you're in exactly the same situation. You can't stop someone from doing this:
List<String> list = new ArrayList<>();
list.add( "nice item" );
Foo foo = new Foo( list );
list.add( "hahahaha" );
list.add( "i've just added more items to your list and you don't know about it." );
list.add( "i'm an evil genius" );
So you should be making defensive copies both before assigning a mutable object to a field and when returning it.
Apart from simply not knowing why it isn't a good idea, there are two broad categories of excuses given.
There are two things in your code:
ArrayList
, which is not fine, because it breaks encapsulation. If you would like to add items to this list, you should expose addItem()
method only.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With