2

If I have a class which looks like the following:

class A {
   private List<B> bs = new ArrayList<B>;

   public List<B> getBs() {
    return bs;
   }
}

It holds a list of object of type B.

Another class makes use of this list and hands one Object of the list to yet another object of class C:

class SomeOtherClass {

   private C c;

   public void someMethod() {
   //code...
   ArrayList<B> bs = mA.getBs();
   c.setB(bs.get(0));

   }

}

Finally, some values of member fields of object b are changed.

class C {
    private B b;

    public void setB(B b) {
       this.b = b;
       this.b.ChangeValue();
    }
}

This way of course the objects in the original list gets modified, which is what I want. However, I find it extremely confusing that class C modifies the values of the object in list bs of class A. For another developer it would be hard to see that this method change the values. So I think what I do here is considered to be bad practice, isn't it?` What's a better way of writing such kind of code?

aioobe
  • 413,195
  • 112
  • 811
  • 826
Moonlit
  • 5,171
  • 14
  • 57
  • 95
  • Take a look at this post [Is Java “pass-by-reference” or “pass-by-value”?](http://stackoverflow.com/questions/40480/is-java-pass-by-reference-or-pass-by-value) – Paco Abato Dec 10 '14 at 13:36

3 Answers3

1

I would agree with your reasoning. This method:

public void setB(B b) {
    this.b = b;
    this.b.ChangeValue();
}

is not very nicely implemented. It should preferably be refactored. If that's not an option, it should clearly document that the B argument is modified when passed to the method. If possible, this should be made clear from the method name as well. Perhaps you could name it something like setAndUpdate(B b).


This method:

class A {
    private List<B> bs = new ArrayList<B>;

    public List<B> getBs() {
        return bs;
    }
}

may be acceptable:

  • If the interface of A should allow insertion and deletion of B objects, I'd say it's acceptable to hand out the backing list to leverage the list-manipulation methods such as addAll, clear etc. provided by List.

  • If bs is indeed part of the internal state of A, and A is concerned with modification of bs (such as if it need to maintain some invariant that involves bs) then it should not leak the backing data structure.

You'll find examples of both variants in the standard collections API. keySet and entrySet for instance expose collections that affect the map they were retrieved from. List.toArray on the other hand returns a copy.

If you want to expose a read only version of the backing list, you could do

// Return a read only view of bs
public List<B> getBs() {
    return Collections.unmodifiableList(bs);
}
aioobe
  • 413,195
  • 112
  • 811
  • 826
1

I think what I do here is considered to be bad practice, isn't it?

Yes, exposing mutable portions of your class through a getter may be misleading, because the data is not encapsulated well enough. There is nothing wrong with changing the data, it's just that it is done in a way that does not immediately stand out as modification.

There is no universal rule to fix this. Generally, you should avoid returning mutable objects inside your class directly. Instead, provide getters and setters where appropriate, and do not give List<B> to your caller:

class A {
     private List<B> bs = new ArrayList<B>;

     public void getSizeB() {
         return bs.size();
     }
     public B getB(int i) {
         return bs.get(i);
     }
     public void setB(int i, B b) {
         bs.set(i, b);
     }
     public void addB(B b) {
         bs.add(B);
     }
     ... // Add more methods as needed
}

The idea is to stay in control of what gets into your List<B>: since there is no direct access, you can validate every B that gets into the list. The code that uses your class A must be explicit about all modifications that it makes, because it cannot get the list directly and do whatever it wants with it.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • So if you have a `House` you should expose methods such as `closeWindow(windowId)`? I'd say it's better to have `house.getWindow(windowId).close()`. If the `house` needs to maintain an invariant such as `numberOfOpenWindows` it's another story of course, but still, exposing part of your state is not necessarily a bad thing in my opinion. – aioobe Dec 10 '14 at 13:45
  • @aioobe My answer suggests `a.getB(bId).doSomething()` - an equivalent to your `house.getWindow(windowId).close()`. I argue against `house.getWindows().get(windowId).close()`, not for `house.closeWindow(windowId)`. – Sergey Kalinichenko Dec 10 '14 at 13:48
  • It depends of course of the purpose and responsibility of the `A` class. If the client should be able to modify the collection of `B`s it contains, I'd say it's better to provide an access method to the collection of `B`s and document that the method exposes a backing data structure rather than replicating several list-methods and simply delegate them to the `bs` list. – aioobe Dec 10 '14 at 13:53
  • What I wanted to show with `house.getWindow` is that it's not always a bad idea to expose mutable objects that are part of your state. – aioobe Dec 10 '14 at 14:03
  • @aioobe I do not mind exposing mutable objects and even collections, but generally I expose collections through their read-only wrappers to maintain control over what's in them. – Sergey Kalinichenko Dec 10 '14 at 14:22
1

First of all, I'd say getBs() is a bad idea, since the caller of that method can clear the list or add elements to it, which would be a much more serious change to the state of A than just changing a property in one of the elements of the list.

You can replace it with getB(int), which returns the i'th element of that list. Now, if B is immutable, your job is done, and the caller of getB(int) can't change anything in the state of A.

If B is mutable, you can decide that getB(int) would return a copy of bs.get(i).

To summarize, I'd replace :

public void getBs() {
    return bs;
}

with either

public void getB(int i) {
    return bs.get(i);
}

or

public void getB(int i) {
    return new B(bs.get(i));
}

Of course, you should also add range checks, to make sure i is a valid index.

Eran
  • 387,369
  • 54
  • 702
  • 768
  • Exposing the backing list is not neccesarily a bad thing. Many of the standard collections allows you to get a modifiable view of the backing data structures with for instance keySet or entrySet. If you have invariants that would break (such as an internal size-field) that must be in sync with the list, then it's another story. – aioobe Dec 10 '14 at 13:41
  • @aioobe When you have a data structure such as HashMap, you can change it directly, even without using keySet or entrySet. As the owner of the HashMap, you can do whatever you wish with it. But once you put that HashMap in your class, you want to be careful with how you allow users of your class to manipulate the HashMap. – Eran Dec 10 '14 at 13:46
  • Thanks, the thing is that the objects in the list should be mutable/changeable. This is what I want. my question, however was how to make it more clear that this object in fact gets modified. – Moonlit Dec 10 '14 at 13:58