1

I have a class containing a list, for example:

class User{
   ....
   List<String> cards
   ...
}

What's the best way to provide access to this member?

List<String> getCards()

or

String getCard(int index)

I am tempted by the second option so that I can avoid a null pointer exception like this:

getCards().get(0) //if getCards() returns null

But then I also wonder if this is something that should not be the responsibility of the class.

  • 5
    Why would you represent "no cards" as null? Use an empty list like God intended! Don't be afraid of 0, civilization has wasted enough time on number systems that don't understand zero. – Kilian Foth Oct 17 '19 at 10:45
  • I'm pretty sure that the C# compiler would look at the eventual usage, e.g. myUser.getCards().ElementAt(0), and would optimize that in a way that it is effectively equivalent to myUser.getCard(0). However, I don't know if the same applies for the Java compiler, so I'm not posting this as an answer until I find confirmation on this. – Flater Oct 17 '19 at 10:49
  • @KilianFoth The problem is that this list will dont fill by the json parser, and make a null, so null.get(0) -> error – Tlaloc-ES Oct 17 '19 at 10:50
  • @Tlaloc-ES: Regardless of the question you posted, whether you take option A or B, you should always be relying on an empty list instead of null. It's a matter of avoiding runtime exceptions and avoiding countless null checks littering your codebase. Sure, the JSON parser might give you null, but that doesn't mean you can't then make sure you fall back on an empty list. – Flater Oct 17 '19 at 10:52
  • @Tlaloc-ES Regarding the null/empty collection issue, try search for relevant questions on Stack Overflow (such as this one), as it is likely already answered. If the answer is negative (the json parsing code has no such support), you will still be able to find recommended workarounds. – rwong Oct 18 '19 at 0:33
1

This answer is written from C# perspective. Please feel free to make edits to replace C# interfaces with Java equivalents, or to mention discrepancies or lack of equivalents between the two languages where appropriate.


Evan's answer has value, in terms of keeping things simple (KISS).


Before going into Robert Bräutigam's answer, let's look at how this task is traditionally handled by Java practitioners (of which I'm not):

  • A Card object or an AbstractCard interface (instead of using String),
  • An UserCardConsumer interface or abstract class (or CardConsumer for short), which defines methods that accept either one card at a time, or entire collection of cards at once. This is meant to be inheritable (be implemented or extended).
  • Any business logic that wants to "consume" cards from a User will need to implement an inner class that inherits from UserCardConsumer, which will consume the cards when its method is called.

Given the additional amount of traditional code (of which Java is famous for), Robert Bräutigam reiterates the need to think about the bare necessary needs of the application. If you can encapsulate the entire logic that operates on that Card collection in one class, go for it.


Decide what User class needs to be. Typically, more than one needs exist.

  • An interface?
  • A mutable data transfer object?
  • A data model (as in MVC, MVVM etc)
  • An immutable data object?
  • An actor?
    • An actor (as in actor model) basically handles the entire business logic within itself (not necessary encapsulating the whole application; just its share of responsibilities), typically not having to reveal most of its information to outsiders.

If you expose the getCard(int index) method, you might also need to expose an int getCardCount() method, so that the callers can use a for-loop.


If you are unsure about exposing a mutable List<String> member (i.e. sharing the reference to the internal member with the caller), for various reasons, here are some options:

  • IReadOnlyList<> (immutable; otherwise behaves like a list)
  • IList<> (maybe-mutable; otherwise behaves like a list)
    • Why I said "maybe-mutable"? Many immutable list implementations offer an IList interface as well, but their mutators will throw an exception.
    • LSP (Liskov) and ISP (Interface Segregation) would suggest that, if the list is always going to be immutable, exposing IReadOnlyList<> is better than IList<>.
    • However, if the object or interface must allow flexibility in the underlying implementation, IList<> would still be the choice.
  • ICollection<> ... This one does exactly what you want:
    • Has a Count property, so that you can do user.Cards.Count
    • Has an indexer, so that you can do user.Cards[2]
    • Enumerable (iterable)
    • Basically, ICollection<> is a better choice over IEnumerable<>.
    • Note that ICollection<> does not have an Add(T) method.
      • This may not be an issue for you, since you can put this method on the User class. This is even more preferable, since this allows User to be notified of changes to the list, so that it can perform additional actions to maintain the object invariants if necessary.

Issues with staying consistent and synchronized.

For simple applications this may not be a big issue.

For slightly more involved applications, a mutable data transfer object can return a snapshot (copied) list of items. Modification of the list afterwards would not be synchronized to the copied list that was returned earlier.


Preventing null exceptions.

My typical approach is as follows.

  • Mark the internal List member readonly. This means the same list will always be used (and reused), and will survive modifications and item removals. This means the reference to this list will always be the same.
  • Inside the User constructor, initialize this List member. Combined with the readonly keyword, this ensures the reference to this list will always be same and non-null.
  • Expose this member as an IList<>. or IReadOnlyList<>
  • Operations, such as destructive and non-destructive ones, are carried out on this List.
|improve this answer|||||
8

It is worth noting, that if you are interested in object-orientation and maintainable designs, you should not expose that list at all.

Of course there are rare exceptions, but in general object-orientation is about hiding the data, and giving the object behavior to work with that data.

Every time you expose data or a pure data structure, you essentially lose control over that data, which is really bad for maintenance. Instead of maintaining a meaningful behavior you now have to maintain an arbitrary (that means technical) data interface.

So "the best" option, if there is such a thing, is to look at what the whole application needs and choose a method from that pool of behavior, instead of looking at it just locally and think about what data you would want to have as a developer.

|improve this answer|||||
  • This is a good answer but it would be helpful to provide some examples of how give access to the data without breaking encapsulation. – JimmyJames Oct 17 '19 at 15:24
  • @JimmyJames The traditional ("Java") way is to define CardConsumer abstract class, but I think Robert's point is to try to handle everything within the User class, to keep things simple. – rwong Oct 18 '19 at 0:28
  • @JimmyJames It's hard to give examples, because all methods should depend on the actual business-case involved. Let's say those "cards" are credit cards, and you want to freeze all of them. Instead of getting the cards, iterating on them externally and freezing them individually, you just do 'User.freezeCards()`. Done. Simple, and leaves the implementation free to do anything at all, without going through unnecessary objects. Not breaking encapsulation is basically achieved through giving responsibilities to objects, instead of making them responsibility-less data structures. – Robert Bräutigam Oct 18 '19 at 9:27
  • In general, I agree with what you are saying here but I think it's possible to assume some use-case, state what it is, and provide an approach. Perhaps if I have some time, I'll throw together one as an addendum answer. – JimmyJames Oct 18 '19 at 14:48
  • @JimmyJames Perhaps you missed it, but I did provide an example use-case in my comment. – Robert Bräutigam Oct 18 '19 at 15:52
-1

Gona chime in on this one

class User{
   ....
   public List<String> cards {get;set}
   ...
}

Is the most common solution for a good reason.

If you need to provide access to the list then this is just a struct defined as a class. There's no benefit in hiding the data or the methods on List and it's a lot of extra work to do that.

Now if you can hide the list completely any only expose methods on your object then yeah that's great, you are doing OOP, but most classes are just used as structs.

Another option is to make the class immutable. Java provides the ImmutableList<> to help you

|improve this answer|||||
  • 1
    I agree that exposing data is the most common solution. I emphatically disagree with "for a good reason", "there's no benefit in hiding the data...". I think those statements should need to be backed up and justified. – Robert Bräutigam Oct 18 '19 at 11:58
  • There's only a point in encapsulation if you have methods. If we change it to struct User then the argument vanishes. But most classes are used like structs, people just declare them as class – Ewan Oct 18 '19 at 12:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.