3

How do you usually do collection getters in Java? For example I have two classes.

public class A
{
    String s = "1";
    A () {}
}

public class B
{
     List<A> list;
     public B()
     {
       list = new ArrayList<>();
     }
     public List<A> getList()
     {
        return list;
     }

}

If I have getter like this, I can do something like:

B elem = new B();
elem.getList().add(new A());

So it breaks the idea of a getter.

I can do

 public List<A> getList()
 {
     return new ArrayList<>(list);
 }

But still, if I modify some elements of result, the original element will be changed.

Tunaki
  • 132,869
  • 46
  • 340
  • 423
  • refer SO Question [Immutable vs Unmodifiable collection](http://stackoverflow.com/questions/8892350/immutable-vs-unmodifiable-collection) can give you some idea – Dev Nov 24 '15 at 09:01
  • If you want to copy a list and everything it contains you have to use a deep copy function but it is not the way it is usually done in Java. It is normal to be able to edit a list when retrieved through a getter. – Raphaël Nov 24 '15 at 09:08
  • IMO, pay attention with this. While immutability is a great think in languages designed to care about (like `Clojure`), it may become very expensive in Java when you deal with collection immutability. Moreover, it may cause runtime errors (a developer who reads `List` expects to be able to call `add` and if you are returning an immutable view via `Collections.unmodifiableList` this will cause an `Exception`). I would rather leave it to best practice (your client should know he is not supposed to call `add` on a `getter` and let him do the copy if he needs to) – ThanksForAllTheFish Nov 24 '15 at 09:11

4 Answers4

5

Return an unmodifiable collection with Collection.unmodifiableList(list):

public List<A> getList() {
    return Collections.unmodifiableList(list);
}

Quoting the Javadoc:

Returns an unmodifiable view of the specified list. This method allows modules to provide users with "read-only" access to internal lists. Query operations on the returned list "read through" to the specified list, and attempts to modify the returned list, whether direct or via its iterator, result in an UnsupportedOperationException.

so appending / removing elements to the list returned will not be possible, and modifying its elements will also not be possible.

If you want to return a deep clone of the list instead of an unmodifiable list, refer to this question.

Community
  • 1
  • 1
Tunaki
  • 132,869
  • 46
  • 340
  • 423
2

If you need a deep copy you can do

public List<A> getList() {
    return list.stream().map(A::copy).collect(toList());
}

The better solution is to not return the list in the first place. You can instead return something like.

public int getCount() { return list.size(); }

public String getASAt(int index) { return list.get(index).s; }

Now there is no way to modify the underlying A objects, nor do you need to copy them redundantly.

Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • Thanks a lot. I undestood that returning a deep copy in getter isn't very good idea, but sometimes u need such getter. – Artem Vlasov Nov 24 '15 at 09:51
2

Normally I provide meaningful maintenance methods in my class to manage the list. List are returned just for viewing purpose:

public class Foo
{
    private List<Bar> bars = ....;
    // or even stricter, returning Iterator<Bar>
    /** be clear in javadoc the list is immutable */
    public List<Bar> getBars() {
       return Collections.unmodifiableList(bars);
    }

    public void addBar(Bar bar) {
        this.bars.add(bar);
        // and maybe some other works against bar
    }
    public void removeBar(Bar bar) {
        this.bars.remove(bar);
        // and maybe some other works against bar
    }
    // even something more meaningful
    public void removeExpiredBars() {
        // iterate thru this.bar and remove all bars with bar.isExpired()
    }
}

Always remind yourself: the idea of getter (and setter) is BAD. You shouldn't design your class to "get data out of it". You should give meaningful behavior to your class.

Adrian Shum
  • 38,812
  • 10
  • 83
  • 131
1

You can return a copy this way:

    public List<A> getList()
    {
       List<A> destination = new ArrayList<A> ( list.size());
       Collections.copy(destination , list);
       return destination ;
    }

EDITED

here is the shortest version:

    public List<A> getList()
    {
       return new ArrayList<A> ( list);
    }
jMounir
  • 495
  • 2
  • 11