6

I have read some answers re the pro's and con's of throwing an exception within an accessor, but I thought I would broach my specific question with an example:

public class App {
  static class Test {       
    private List<String> strings;

    public Test() {
    }

    public List<String> getStrings() throws Exception {
        if (this.strings == null)
            throw new Exception();

        return strings;
    }

    public void setStrings(List<String> strings) {
        this.strings = strings;
    }
}

public static void main(String[] args) {
    Test t = new Test();

    List<String> s = null;
    try {
        s = t.getStrings();
    } catch (Exception e) {
        // TODO: do something more specific
    }
  }
}

getStrings() is throwing an Exception when strings has not been set. Would this situation be better handled by a method?

wulfgarpro
  • 6,666
  • 12
  • 69
  • 110

8 Answers8

7

Yes, that's bad. I suggest initializing the strings variable in the declaration, like:

private List<String> strings = new ArrayList<String>();

and you could replace the setter with something like

public void addToList(String s) {
     strings.add(s);
}

So there's no possibility of a NPE.

(Alternatively don't initialize the strings variable in the declaration, add the initialization to the addToList method, and have the code using it check getStrings() to see if it gets null back.)

As to why it's bad, it's hard to say with such a contrived example, but it seems like there's too much state-changing and you're penalizing the user of this class for it by making the user handle the exception. Also there's the issue that once methods in your program start throwing Exception then that tends to metastasize all over your codebase.

Checking that this instance member gets populated needs to be done somewhere, but I think it should be done somewhere that has more knowledge of what is going on than in this class.

Nathan Hughes
  • 94,330
  • 19
  • 181
  • 276
  • 1
    that's fine - but why is it bad? – wulfgarpro Sep 07 '11 at 13:09
  • 2
    @wulfgar: because you should make sure your objects are always in a valid state. if you make sure you never allow an object to be in an invalid state, you can always get its properties without being worried that there might be something wrong. plus you only have to check things once (in the setters). – devoured elysium Sep 07 '11 at 13:11
  • Does not allow for knowledge of an illegal state of the setting not having been called. – John B Sep 07 '11 at 13:12
  • @John B: yes, but my point is I'd like to reduce the illegal states if possible. There probably should be a check for this not being populated, I'm not convinced it belongs in the accessor/mutator methods. Probably needs a more detailed example. – Nathan Hughes Sep 07 '11 at 13:18
  • @Nathan Hughes - your answer is logical, and it was my initial thinking - yet, I can't have the `List` initialized as being empty. It either has to contain elements, or be `null`. – wulfgarpro Sep 07 '11 at 13:22
  • @wulgar.pro: alternatively you could have the setter initialize the list if it's null, would that work? – Nathan Hughes Sep 07 '11 at 13:28
  • If you consider that check as a precondition for your method to work then throwing a unchecked RuntimeException (like IllegalArgumentException) is fine; just make sure your javadocs reflects that. You also then don't need to mention that throws clause. However, throwing a checked exception leads to clumsy client code and hence should be avoided. – Scorpion Sep 07 '11 at 13:38
  • @Scorpion: definitely, I've seen many applications where *every* method `throws Exception`, undermining checked exceptions as much as if they used unchecked exceptions for everything, just with more typing :-P – Nathan Hughes Sep 07 '11 at 13:42
7

It really depends on what are are doing with the list you get. I think a more elegant solution would be to return a Colletions.EMPTY_LISTor, as @Nathan suggests, an empty ArrayList. Then the code that uses the list can check if it is empty if it wants to, or just work with it like with any other list.

The difference is the distinction between there are no strings and there is no list of strings. The first should be represented by an empty list, the second by either returning null or throwing an exception.

Björn Pollex
  • 75,346
  • 28
  • 201
  • 283
6

The correct way to handle this, assuming it is a requirement that the list be set elsewhere before calling the getter, is as follows:

public List<String> getStrings() {
    if (strings == null)
        throw new IllegalStateException("strings has not been initialized");

    return strings;
}

Being an unchecked exception, it isn't required that you declare it on the method, so you don't pollute the client code with lots of try/catch noise. Also, because this condition is avoidable (ie the client code can ensure the list has been initialized), it is a programming error, and therefore an unchecked exception is acceptable and preferable.

Bohemian
  • 412,405
  • 93
  • 575
  • 722
  • I was about to comment that in C# (Sorry, not a Java programmer) it would be appropriate to throw an InvalidOperationException, which seems to be equivalent. – Toby Sep 07 '11 at 17:26
2

In general, this is a design smell.

If it really is an error that strings is not initialized, then either remove the setter, and enforce the constraint in a constructor, or do as Bohemian says and throw an IllegalArgumentException with an explanatory message. If you do the former, you get fail-fast behaviour.

If it is not an error is strings is null, then consider initialising the list to an empty list, and enforce it to be not-null in the setter, similarly to the above. This has the advantage that you don't need to check for null in any client code.

for (String s: t.getStrings()) {
  // no need to check for null
}

This can simplify client code a lot.

EDIT: Ok, I've just seen that the you are serializing from JSON, so you can't remove the constructor. So you'll need to either:

  1. throw an IllegalArgumentException from the getter
  2. if strings is null, return a Collections.EMPTY_LIST.
  3. or better: add a validation check just after the deserialization where you specifically check the value of strings, and throw a sensible exception.
Matthew Farwell
  • 60,889
  • 18
  • 128
  • 171
1

I think you've exposed a more important question behind the original one: should you work to prevent exceptional cases in getters and setters?

The answer is yes, you should work to avoid trivial exceptions.

What you have here is effectively lazy instantiation that is not at all motivated in this example:

In computer programming, lazy initialization is the tactic of delaying the creation of an object, the calculation of a value, or some other expensive process until the first time it is needed.

You have two problems in this example:

  1. Your example is not thread safe. That check for null can succeed (i.e., find that the object is null) on two threads at the same time. Your instantiation then creates two different lists of strings. Non-deterministic behavior will ensue.

  2. There is no good reason in this example to defer the instantiation. It's not an expensive or complicated operation. This is what I mean by "work to avoid trivial exceptions": it is worth investing the cycles to create a useful (but empty) list to ensure that you aren't throwing delayed detonation null pointer exceptions around.

Remember, when you inflict an exception on outside code, you're basically hoping that developers know how to do something sensible with it. You're also hoping that there isn't a third developer in the equation whose wrapped everything in an exception eater, just to catch and ignore exceptions like yours:

try {
    // I don't understand why this throws an exception.  Ignore.
    t.getStrings();
} catch (Exception e) {
    // Ignore and hope things are fine.
}

In the example below, I am using the Null Object pattern to indicate to future code that your example hasn't been set. However, the Null Object runs as non-exceptional code and, therefore, doesn't have the overhead and impact on the workflow of future developers.

public class App {   
    static class Test {            
        // Empty list
        public static final List<String> NULL_OBJECT = new ArrayList<String>();

        private List<String> strings = NULL_OBJECT;

        public synchronized List<String> getStrings() {
            return strings;
        }      

        public synchronized void setStrings(List<String> strings) {         
            this.strings = strings;     
        } 
    }  

    public static void main(String[] args) {     
        Test t = new Test();      

        List<String> s = t.getStrings();

        if (s == Test.NULL_OBJECT) {
            // Do whatever is appropriate without worrying about exception 
            // handling.

            // A possible example below:
            s = new ArrayList<String>();
            t.setStrings(s);
        }

        // At this point, s is a useful reference and 
        // t is in a consistent state.
    }
}
Bob Cross
  • 22,116
  • 12
  • 58
  • 95
0

I would consult the Java Beans specification on this. Probably would be better not to throw a java.lang.Exception but instead use the java.beans.PropertyVetoException and have your bean register itself as a java.beans.VetoableChangeListener and fire the appropriate event. It is a tad overkill but would correctly identify what you are trying to do.

Dave G
  • 9,639
  • 36
  • 41
0

It really depends on what you are trying to do. If you have attempting to enforce the condition that the setting has been called prior to the getter being called, there might be a case for throwing an IllegalStateException.

John B
  • 32,493
  • 6
  • 77
  • 98
-1

I'd suggest to throw the exception from the setter method. Is there any special reason why wouldn't it better to do so?

public List<String> getStrings() throws Exception {
    return strings;
}

public void setStrings(List<String> strings) {
    if (strings == null)
        throw new Exception();

    this.strings = strings;
}

Also, depending on the particular context, wouldn't it be possible to simply pass the List<String> strings directly by the constructor? That way maybe you wouldn't even need a setter method.

devoured elysium
  • 101,373
  • 131
  • 340
  • 557
  • 1
    -1 This does not take into account the state at which the setting has not been called before the getter is called. – John B Sep 07 '11 at 13:11