13

how to rewrite this function to be more Java 8 with Optionals? Or should I just leave it as it is?

public void setMemory(ArrayList<Integer> memory) {
    if (memory == null)
        throw new IllegalArgumentException("ERROR: memory object can't be null.");
    if (memory.contains(null))
        throw new IllegalArgumentException("ERROR: memory object can't contain null value.");

    this.memory = memory;
}
Jump3r
  • 1,028
  • 13
  • 29
  • 1
    do you want a `IllegalArgumentException`? can it be changed to `NullPointerException`? – Eugene Nov 28 '17 at 09:23
  • 2
    I don't see how to make this any shorter if you still want to throw two different, custom exceptions. IMHO this is good as it is. – tobias_k Nov 28 '17 at 09:24
  • You can use the `Objects::nonNull` – Krishnanunni P V Nov 28 '17 at 09:26
  • 4
    I'd prefer to hand responsibility (Design by Contract) to the method caller by adding a precondition to the parameter as a Javadoc comment rather than doing an O(n) check for null values. – d.j.brown Nov 28 '17 at 09:29
  • "How do I write X in Y" does not make a good question. You've presented no research and no attempt at solving the problem yourself. – Michael Nov 28 '17 at 09:44
  • 1
    I think this is a valid question. I'd recommend leaving as is... you'll thank yourself later considering that introducing optionals would needlessly complicate the code in this case. However @d.j.brown does bring up a very good point. – shinjw Nov 28 '17 at 09:45
  • 3
    @shinjw `Objects.requireNonNull`? – Eugene Nov 28 '17 at 09:50
  • 2
    @shinjw I agree, Optionals would complicate the matter, especially as the second check is not even a null check. Some of the answers show other ways to make these checks more expressive to write. – Malte Hartwig Nov 28 '17 at 11:00
  • 8
    You should be aware that when you are storing a reference to the caller provided collection without copying, nothing prevents the caller from adding `null` *after* calling `setMemory`. – Holger Nov 28 '17 at 11:23
  • @Holger, very good point, I'll include it in code. I was following this answer to use IAE instead of NPE (https://stackoverflow.com/a/47710/4393368), I'm quite a begginer, so not really sure what should I use for throwing readable exceptions. – Jump3r Nov 28 '17 at 20:10
  • Also, @d.j.brown, thanks for simplest solution, I'll propably leave checking for nulls in List for obliging user to pass nonnull values. – Jump3r Nov 28 '17 at 20:26
  • 2
    @Jump3r: you might have noticed that the answer is not without controversy. As the highest voted comment mentions, `IndexOutOfBoundsException` is a clear counter example. No-one would replace that with an `IllegalArgumentException`. Also, all collections disallowing `null` consistently throw a `NullPointerException`, so does `Optional.of(...)`. I don't know whether there is any JRE API example of throwing `IllegalArgumentException` for a `null` argument. – Holger Nov 28 '17 at 20:42

9 Answers9

8

You've got a pattern condition -> throw an exception which can be moved to a method:

private void checkOrElseThrow(boolean condition, Supplier<? extends RuntimeException> exceptionSupplier) {
    if (condition) {
        throw exceptionSupplier.get();
    }
}

public void setMemory(List<Integer> memory) {

    checkOrElseThrow(memory == null, () -> new IllegalArgumentException("message #1"));
    checkOrElseThrow(memory.contains(null), () -> new IllegalArgumentException("message #2"));

    this.memory = memory;
}

If the type of the exception is not going to be changed, it's reasonable to pass only the message of the exception (thank @tobias_k for pointing it out):

private void checkOrElseThrow(boolean condition, String exceptionMessage) {
    if (condition) {
        throw new IllegalArgumentException(exceptionMessage);
    }
}

public void setMemory(List<Integer> memory) {

    checkOrElseThrow(memory == null, "message #1");
    checkOrElseThrow(memory.contains(null), "message #2");

    this.memory = memory;
}
Andrew Tobilko
  • 48,120
  • 14
  • 91
  • 142
  • checkAndThrow should take a predicate IMO – Michael Nov 28 '17 at 09:45
  • @Michael, it will add a generic param and an additional argument. Also, the condition will be always calculated – Andrew Tobilko Nov 28 '17 at 09:51
  • 2
    I'd prefer this answer if OP really wants to change the code. The null checks in the example good enough, yet this method might be considered more expressive, especially when using this across the whole application. Looking at the answers that tried to do something with optionals, I do not really see much benefit, as some of the checks are not even null checks (like the contains check in the example). – Malte Hartwig Nov 28 '17 at 10:57
  • 2
    Provided that OP wants to throw the same type of Exception in both cases, you could also just pass the error message to the method instead of a supplier. Right now it's not shorter or more readable than what OP has, just in a single line. – tobias_k Nov 28 '17 at 15:34
  • Thanks for another way to write this functionality, but I think new method taking bool expression and string message is a bit overkill compared to if + throw. I may be wrong, still learning Java, but that's what came to my mind. – Jump3r Nov 28 '17 at 20:17
7

If you want to stick to IllegalArgumentException and you have guava on the class path, you could use this:

Preconditions.checkArgument(memory != null, 
            "ERROR: memory object can't be null.");
Preconditions.checkArgument(!memory.contains(null), 
            "ERROR: memory object can't contain null value.");

You can't really use Optional here since you want different error messages for different conditions.

If you are OK having a single error message on the other hand, you could do:

this.memory = Optional.ofNullable(memory)
            .filter(x -> !x.contains(null))
            .orElseThrow(() -> new IllegalArgumentException(
                         "memory object is null or contains null values"));
Eugene
  • 117,005
  • 15
  • 201
  • 306
  • 2
    I don't believe there's a requirement for 2 distinct error messages (and if there is then it should be rethought) so I think your `Optional` example is the best way to express this. – Michael Nov 28 '17 at 10:01
  • @Michael IMHO no way. Assuming access to the source code in this exact version, then knowing what exactly was the problem is much more important than *any* message. A missing/insufficient message makes me checkout the version, copy the stacktrace to an IDE and click. Not knowing if the object itself or its content was `null` makes me having to consider (up to) twice as many causes. – maaartinus Nov 28 '17 at 14:14
  • @maaartinus agreed, the problem I have here personally is that OP is throwing IAE instead of NPE - which would be better to me – Eugene Nov 28 '17 at 14:15
  • 1
    @Eugene I'd also vote for the NPE, but I don't really care. Actually there should be an INAE extending both, but we can't have it. We could have an `INAE extends NPE`, but I'm not the one who introduces it. :D +++ I'd prefer *any* hack to conflating the two causes into one, including `ImmmutableList.copyOf(memory)` (short, concise and stupid; doesn't satisfy the OP's requirement). – maaartinus Nov 28 '17 at 14:25
  • @maaartinus Then simply change `ofNullable` to `of` - NPE if the list is null, IAE if the list contains null. – Michael Nov 28 '17 at 15:48
6

For the first case I would use: Objects.requireNonNull().

I don't think Optional is a way to go here as null is an illegal value.

ps-aux
  • 11,627
  • 25
  • 81
  • 128
  • which will throw a `NullPointerException`, might not be what the OP wants – Eugene Nov 28 '17 at 09:25
  • I took the liberty of assuming that OP wants such behaviour based on `object can't be null` error message :) – ps-aux Nov 28 '17 at 09:26
  • A require-non-null is exactly what the OP intends. That to prevent a NPE later on, one throws a NPE is a correct (perverse) fail-fast strategy. – Joop Eggen Nov 28 '17 at 09:48
  • 6
    I always prefer an exception type that doesn’t require parsing the message to find out what has happened, i.e. a `NullPointerException`. Might be worth adding the actual examples for the OP’s use case, i.e. `Objects.requireNonNull(memory, "ERROR: memory object can't be null.");` and `memory.forEach(o -> Objects.requireNonNull(o, "ERROR: memory object can't contain null value."));` which imho demonstrates that these are also the most concise and straight-forward constructs compared to the baroque `Optional` (ab)use. Not to speak about the performance difference… – Holger Nov 28 '17 at 11:15
5

I usually avoid Optional for such cases as it tends to obscure what's going on.

But first I'd like to mention that the original code lets the caller retain a reference to what is now an internal field memory of the containing class. Maybe you trust your callers not to be malicious, but the caller might accidentally reuse the list passed as an argument. If it does, despite the meticulous argument checking, the memory list might end up containing nulls after all. Or, it could change unexpectedly, leading to other malfunctions.

The solution is to make a defensive copy of the argument list. The straightforward way to do this is as follows:

public void setMemory(ArrayList<Integer> memory) {
    if (memory == null)
        throw new IllegalArgumentException("memory is null");

    List<Integer> temp = new ArrayList<>(memory);

    if (temp.contains(null))
        throw new IllegalArgumentException("memory contains null");

    this.memory = temp;
}

Note that the copy is made and stored in a local variable temp prior to being checked. Obviously, you don't want to store into the field before the list is checked for containing nulls. But the check for containing nulls should be done on the copy, not on the argument list, otherwise, the caller could modify the list after the check but before the copy. (Yes, this is being paranoid.)

If you don't care about the exact exception message, this could be shortened as follows:

public void setMemory(ArrayList<Integer> memory) {
    List<Integer> temp;
    if (memory == null || ((temp = new ArrayList<>(memory)).contains(null)))
        throw new IllegalArgumentException("memory is or contains null");
    this.memory = temp;
}

Now this could be rewritten to use Optional:

public void setMemory(ArrayList<Integer> memory) {
    this.memory = Optional.ofNullable(memory)
                          .map(ArrayList::new)
                          .filter(list -> ! list.contains(null))
                          .orElseThrow(() -> new IllegalArgumentException("memory is or contains null"));
}

Compared to the usual abuses :-) of Optional I see frequently, this one isn't too bad. The chaining here serves to avoid creation of a local variable, which is a bit of a win. The logic is fairly straightforward, especially if one has Optional on the forebrain. However, I'd be somewhat concerned about revisiting this code in, say, a month. You'd probably have to squint at it a while before convincing yourself it does what you intended it to do.

Finally, a couple general style comments.

  1. The usual preference (at least in the JDK) is to use NullPointerException for these cases. I've stuck with IllegalArgumentException for these examples because that's what the OP is using.

  2. I'd recommend using List<Integer> instead of ArrayList<Integer> for the argument type and possibly the field type. This will enable the use of unmodifiable lists in situations where it's appropriate (e.g., using JDK 9's List.of).

Stuart Marks
  • 127,867
  • 37
  • 205
  • 259
  • I was following this: (https://stackoverflow.com/a/47710/4393368)when chosing right exception, so that's why I have IAE instead of NPE. Changing argument type to List and copying passed ArrayList seems to be good advice, thanks. – Jump3r Nov 28 '17 at 20:28
  • good point about a defensive copy, but you killed me by the assignment within the boolean condition :) It's a clever idea, but for readability, I would choose the `if-else-assignment-if` flow – Andrew Tobilko Nov 28 '17 at 20:47
  • 2
    When you follow the recommendation to use `NullPointerException` and don't care about the message, it's as simple as `memory = new ArrayList<>(memory);/* (copies and throws NPE if memory is null) */ memory.forEach(Objects::requireNonNull); this.memory = memory;`. Oh, and it's worth mentioning that parameters should prefer abstract types, i.e. `List` instead of `ArrayList`, especially when we create a defensive copy having our desired type `ArrayList` anyway. – Holger Nov 28 '17 at 20:49
  • I don't think copying memory is a good idea.you have checked that the memory is not null,so you can use `memory.contains(null)`, – Richard Chan Dec 04 '17 at 11:25
2

First, it may be a good idea to use the more general list type as input parameter, so change your implementation to:

public void setMemory(List<Integer> memory) {
    //stuff
}

and then as others mentioned, checking for null values for every "set" operation is a bit of an overkill.

If this "memory list" comes from some of your code and you can use guava, then maybe use guavas immutable list. This list throws an exception when someone tries to add "null" to your list.

ImmutableList.of( //your Integers)

If you cannot use guava but stillt want to use that approach you could always write your own list implementation that does this null checking for you.

And last, if all of this is not possible for you, just leave your code as is. It is very easy to read and everyone knows what you're doing. Using Optionals can be quite confusing as you can see in other answers here.

ChristophE
  • 760
  • 1
  • 9
  • 21
1

Do not use Optionals, they won't benefit you here.

Instead use a more suitable type in place of ArrayList. Storing Integers in collection incurs (un)boxing costs and does not make sense when nulls are not allowed.

Few possible collection libraries, that may suite your needs better:

  1. HPPC collections (my favorite, but API is incompatible with Java Collection framework)
  2. Koloboke
  3. Fastutil

All of those libraries provide specialized implementations of Lists, Maps and other containers for primitives. Those implementations are generally significantly faster than anything that involves ArrayList<Integer> (unless all integers in your ArrayList are small enough to fit into global Integer instance cache).

As a nice side-effect, using specialized Lists of primitive integers won't allow caller to store nulls by default.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
user1643723
  • 4,109
  • 1
  • 24
  • 48
1

One liner with Optionals:

public void setMemory(ArrayList<Integer> memory) {
    this.memory = Optional.ofNullable(memory).map((a) -> Optional.ofNullable(a.contains(null) ? null : a).orElseThrow(() -> new IllegalArgumentException("ERROR: memory object can't contain null value."))).orElseThrow(() -> new IllegalArgumentException("ERROR: memory object can't be null."));
}
Marcos Zolnowski
  • 2,751
  • 1
  • 24
  • 29
1

Sorry for adding yet another answer, but based on reading comments to the question, there might be even better way to change signature of the method: replace ArrayList<Integer> with IntStream:

public void setMemory(@NonNull IntStream input) {
    Objects.requireNonNull(input);

    this.memory = ...; // collect the stream into the storage
}

Primitive streams do not incur cost of (un)boxing.

This way you don't have to worry about the caller changing List contents under your feet, and will be able to chose the suitable storage for integers as explained in my other answer (or even resolve the stream contents lazily!).

user1643723
  • 4,109
  • 1
  • 24
  • 48
  • Let's say I'll stick with List, doesn't `intStream.boxed().collect(Collectors.toList())` impact negatively on performance boxing it into List (I mean, it's has to do O(n) copy, right?) and then assigning to memory field? I don't understand "Primitive streams do not incur cost of (un)boxing.", still, you have to take one by one from stream and box it into List, or I'm taking it wrong? Anyway, thanks for another way to do it. I can only add that this List gonna contain lots of small (<128) ints. – Jump3r Nov 29 '17 at 06:48
  • "I can only add that this List gonna contain lots of small (<128) ints" — In that case you should continue to use ArrayList. The main issue about (un)boxing is not memory use/copies, but GC strain. Since all your ints fit into The Great Integer Cache, you don't need to worry about clogging GC. – user1643723 Nov 29 '17 at 07:08
-1

My solution may be used only if you need two different exceptions and more functional style. But it looks complicated and even longer.

.map(e -> false) maps element of list (integer in this case) to boolean which is required for filter().

this.memory = Optional.ofNullable(memory)
            .orElseThrow(() -> new IllegalArgumentException("ERROR: memory object can't be null."))
            .stream()
            .filter(element -> 
                    Optional.ofNullable(element)
                    .map(e -> true)
                    .orElseThrow(
                            () -> new IllegalArgumentException("ERROR: memory object can't contain null value.")))
            .collect(Collectors.toList());
Mikita Herasiutsin
  • 118
  • 1
  • 2
  • 7
  • 7
    I wonder what the next person working on this code would think looking at it – Eugene Nov 28 '17 at 09:47
  • 1
    @Eugene Also raises the question why, if you're going to create a new `List` anyway, you wouldn't just remove the nulls and carry on. – Michael Nov 28 '17 at 09:50
  • @Eugene You are right, your solution is more elegant but there is a problem if you need the exact message for exception in case of null element – Mikita Herasiutsin Nov 28 '17 at 10:00
  • what does `map(e -> false)` do? don't you think it's much more complicated than the original snippet? – Andrew Tobilko Nov 28 '17 at 10:06
  • @AndrewTobilko It maps element of list (integer in this case) to boolean which is required for filter(method). So it is a dirty hack... – Mikita Herasiutsin Nov 28 '17 at 10:08
  • The map to false will mean the filter will always fail and the resulting list will always be empty. You should be mapping to true. Better yet, remove the hacky map and change the filter to map. – Michael Nov 28 '17 at 19:05