1

Let's say I have a simple class that stores a user's friends in an ArrayList of strings, with a getter to access that ArrayList:

public class User
{
  private ArrayList<String> mFriends;
  
  // ...other code like constructors and setters...

  public ArrayList<String> getFriends()
  {
    return mFriends;
  }
}

Since Java and many other languages are (equivalently) pass-by-reference, does this not allow the caller of getFriends() direct access to my ArrayList of strings i.e. they could modify it without even calling my setter?

I feel this immensely breaks the concept of encapsulation. Is there a normal workaround for this, besides dynamically creating a new ArrayList with the same values and returning that, or am I misunderstanding something?

Edit: I understand that Java is not truly pass-by-reference but rather passes over a copy of the address of the original object, but this has the same effect as pass-by-reference when exposing objects to those outside your class

Gary Allen
  • 1,218
  • 1
  • 13
  • 28
  • What you're protecting against here is outside code modifying which `ArrayList` `mFriends` refers to. But as you've noted, it's still possible for outside code to modify _the contents_ of that list. If you want to protect against that you could perhaps use `Collections.unmodifiableList`, or return a copy. – Michael Jul 31 '20 at 10:31
  • Yes, that is exactly what I mean. How would this work with regards to passing non built-in "standard" Java objects back? Should I just trust the user not to modify its contents without using my setter, for example? – Gary Allen Jul 31 '20 at 10:34
  • If it's important that they do not modify the contents, then you should probably make it impossible for them to do so. If you make something available to users, there's quite a high risk that someone will make use of it regardless of what you recommend. – Michael Jul 31 '20 at 10:38
  • The short answer is 'yes'. But you have a responsibility as a designer not to expose the internal implementation of your class to the outside world. Making ```mFriends``` private and then providing a getter method for it is no different to making it public. Consider ```return Collections.unmodifiableList(mFriends)``` instead. – nullTerminator Jul 31 '20 at 10:50
  • You may be aware that articles like [Why getter and setter methods are evil](https://www.infoworld.com/article/2073723/why-getter-and-setter-methods-are-evil.html) have been circulating for decades. – jaco0646 Jul 31 '20 at 13:19

3 Answers3

7
  1. Java is not, and has never been implementing Pass by Reference mechanism, it has always been Pass by Value;
  2. The problem you are describing is known as Reference Escape, and yes, you are right, caller can modify your object, if you expose it via reference;
  3. In order to avoid the Reference Escape problem, you can either:
    1. return a deep copy of the object (with .clone());
    2. create a new object with the existing data (e.g. new ArrayList<>(yourObjectHere));
    3. or come up with some other idea, there are some other ways too do this;
  4. This does not really break the Encapsulation, per se, it is rather a point of correct design how you implement the encapsulation;
  5. Your concern about performance: no, it is not going to break performance, moreover - it has nothing to do with performance; rather it is a matter of proper design of the OOP, mandating either mutability or immutability of the object. If you were to always return a deep copy instead of reference, you would not have a chance to have a good leverage of your object.
    Taking your example: what if you want to change the state of the object without just setting a new object via its setter? what if you want to amend the existing friends (which is in your example)? do you think it is rather better to create a new List of friends and set it into the object? no, you are simply losing control over your object in the latter case.
Giorgi Tsiklauri
  • 9,715
  • 8
  • 45
  • 66
  • 1
    Thanks, the Reference Escape comment was mostly what I was looking for. This video also is helping me a lot. Appreciate it. (video: https://www.youtube.com/watch?v=nW48K2a6t7k) – Gary Allen Jul 31 '20 at 10:46
1

If you are worried about encapsulation then you can return a copy of your list e.g.

public ArrayList<String> getFriends() {
    return new ArrayList<>(mFriends);
  }

By the way, Java is not truly pass-by-reference it's more pass-by-value.

Murat Karagöz
  • 35,401
  • 16
  • 78
  • 107
  • Yes sorry, I edited my past regarding the pass-by-reference comment. However, I cannot believe that creating a new copy each time would be performant, as I specifically did say in my comments "besides dynamically creating a new ArrayList" – Gary Allen Jul 31 '20 at 10:35
  • @GaryAllen That's how it is. And performance should be the least of your worries in this case. – Murat Karagöz Jul 31 '20 at 10:37
1

You are right for mutable objects. You could wrap the field with Collections.unmodifiableList and such. What one also sees, is for (mutable) collections just have no getters, but addFriend, getFriend(index) and such.

In fact getters (and estpecially setters) are no longer a very esteemed pattern.

The member "m" prefix is imho better suited for other languages.

Joop Eggen
  • 107,315
  • 7
  • 83
  • 138
  • 1
    Despite the fact, that your answer points to the correct behaviour, I think it's a bit misleading in terms of *Semantics* of what the question asks. It's the problem of Reference Escaping. – Giorgi Tsiklauri Jul 31 '20 at 10:36
  • @GiorgiTsiklauri Indeed, I even upvoted your answer. The problem with OOP is that its basic (historical) concept is of internal _state_. Can you reach via access methods some mutable object, then that should be _intended_ behavior. I primarily answered, because letting getters return a copy did seem a rather weak technique. – Joop Eggen Jul 31 '20 at 11:55