-1

Why do we have to write it like so? Is creating an exception instance more expensive than creating a supplier instance?

var value = getOptional().orElseThrow(NullPointerException::new);
// instead of
var value = getOptional().orElseThrow(new NullPointerException());
  • 9
    This may be a duplicate, so I'm not adding it as an answer - but basically, your second piece of code would create a new instance of `NullPointerException` every time that line of code is executed. The first piece of code only constructs the exception when it's needed – Jon Skeet Nov 17 '21 at 12:54
  • And creating an exception can be expensive (although it probably doesn't fill in the stacktrace at creation anymore, oh wait it does), but it's still quicker to use a supplier. – Kayaman Nov 17 '21 at 12:55
  • @JonSkeet does the supplier not get created every time? Is it cheaper to create than an exception? – Bogdan Tischenko Nov 17 '21 at 12:56
  • 4
    Whether the supplier gets created every time is an implementation detail; the JLS says that a method reference expression (or lambda expression) is allowed to reuse a cached value. – kaya3 Nov 17 '21 at 12:57
  • 1
    Another difference is that, in principle, you could write `.orElseThrow(() -> { ...; return new SomethingException(); })` where the `...` part has side-effects (e.g. logging for debug purposes) that you want to execute in the case something gets thrown. – kaya3 Nov 17 '21 at 12:59
  • @JonSkeet That primarily looks like a reason regarding performance, but I wonder whether there is any other reason (like practical, esthetic, etc). (Edit: I noticed kaya3 has already provided such reason.) – MC Emperor Nov 17 '21 at 13:00
  • 1
    @BogdanTischenko it seems whether the supplier gets created or not is an implementation detail, but I think more importantly we can a priori tell that the cost of creating a supplier is minimal. One could create a custom exception class and sneak in solving an NP-complete problem into the constructor. So there is no a priori limit on how much creating an exception costs. – emory Nov 17 '21 at 13:05
  • 1
    This is essentially a duplicate of https://stackoverflow.com/q/33170109/2541560 the fact that it's exceptions doesn't change anything. – Kayaman Nov 17 '21 at 13:10
  • @Kayaman it does. orElseGet may call an expensive operation which asks a remote servet. We don't travel far for exceptions. My question is: is creating an exception object more expensive than creating a supplier object and why? – Bogdan Tischenko Nov 17 '21 at 13:14
  • 1
    Yes, an exception object (almost) always contains a stacktrace, and building that is expensive. – Hulk Nov 17 '21 at 13:20
  • Related: https://stackoverflow.com/questions/299068/what-are-the-effects-of-exceptions-on-performance-in-java – Hulk Nov 17 '21 at 13:22
  • Suppliers are faster, more useful and additionally it's semantically correct. Performance is just one part of it, and probably the least interesting part. – Kayaman Nov 17 '21 at 13:48
  • @Kayaman, what do you mean by semantically correct? – Bogdan Tischenko Nov 17 '21 at 13:49
  • 1
    The exception shouldn't be created if it isn't needed. If it's needed, it's supplied. – Kayaman Nov 17 '21 at 13:51

1 Answers1

1

.orElseThrow(NullPointerException::new) is equivalent to () -> new NullPointerException().

That's a function which will only be invoked if the Optional is empty

public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
    if (value != null) {
        return value;
    } else {
        throw exceptionSupplier.get(); // Only invoke on this path
    }
}

Code

The reason that's preferable to .orElseThrow(new NullPointerException()) is because of performance. When you pass a function, you are only create an exception on-demand, if you really need to throw it.

Creating most exceptions is expensive*, relatively speaking. The constructor will almost always create a stacktrace, and constructing the stacktrace is relatively slow*. Gathering the stacktrace is done within Throwable's constructor.

public Throwable() {
    fillInStackTrace();
}

There is a variant constructor which won't gather the stacktrace, but it's very rarely used.

They could have provided a second signature to allow people to choose between them, but it's likely that the signature you suggested would be frequently misused, resulting in worse performance.


* 'Expensive' and 'slow' are relative terms. Modern CPUs are fast. While it may be significantly slower than most normal operations, it's generally not going to be a bottleneck on most applications or anything like that.

Michael
  • 41,989
  • 11
  • 82
  • 128
  • 1
    The `fillInStackTrace()` in the constructor is what I was looking for. I thought the performance penalty for exceptions came for throwing them, not calling the constructor – Bogdan Tischenko Nov 17 '21 at 13:44
  • @BogdanTischenko Turns our there is one specific constructor of Throwable that lets you opt out of it, but I suspect in practice it's so infrequently used that you can pretty much ignore it. NPE and friends will all gather a stack trace. 99% of the time you want a stacktrace. Still, for the sake of accuracy I updated my answer a little – Michael Nov 17 '21 at 13:52
  • 1
    @BogdanTischenko throwing them can cost performance too—that depends on the distance between the throwing site and the exception handler, however, gathering the stacktrace is indeed the most expensive part. If you are interested in more details, you may read “[The Exceptional Performance of Lil' Exception](https://shipilev.net/blog/2014/exceptional-performance/)”. By the way, `NullPointerException::new` is non-capturing, which means once this `Supplier` has been constructed, it will (typically) be re-used throughout the application’s lifetime, so even this small construction cost is saved. – Holger Nov 17 '21 at 15:52