2

Using this class:

public class Fallible<T> {

    private final Exception exception;
    private final T value;

    public Fallible(final T value) {
        this.value = value;
        this.exception = null;
    }

    public Fallible(final Exception exception) {
        this.value = null;
        this.exception = exception;
    }

}

Can I safely assume value will never contain an Exception object?

cuihtlauac
  • 1,808
  • 2
  • 20
  • 39

2 Answers2

3

No, you can't make such an assumption. For example:

Object obj = new Exception();
Fallible f = new Fallible(obj);

would invoke the generic constructor.

The only way to check this would be to check the type of value explicitly using instanceof:

public Fallible(final T value) {
  if (value instanceof Exception) {
    this.exception = (Exception) value;
    this.value = null;
  } else {
    this.value = value;
    this.exception = null;
  }
}
Andy Turner
  • 137,514
  • 11
  • 162
  • 243
  • Note that while this example, widening the exception to `Object` is equivalent to a loss of information, there’s also the opposite possible: `new Fallible(new RuntimeException())` will also invoke the generic constructor, now, because it is *more specific* than the explicit constructor. And perhaps this is intentional. The correct solution would be to drop the overloaded `public` constructors and use distinctively named factory methods, e.g. `value(T)` and `exceptionally(Exception)` to allow the caller to document the intention and remove the need for `instanceof`. – Holger Sep 22 '16 at 17:49
  • These factory methods add nothing in terms of the assumption that "`value` will **never** contain an Exception object": you can still invoke `Fallible.value(new Exception())` or `Fallible.value(obj)`. If this class needs to make the stated assumption, the `instanceof` check is necessary as a defensive measure. If it's a public class, you simply can't trust its users to do the right thing to set up its invariants. – Andy Turner Sep 23 '16 at 07:12
  • @Holger Actually, I'd like both `value(T)` and `exceptionally(Exception)` to be of type `Fallible`. – cuihtlauac Sep 23 '16 at 07:50
  • @cuihtlauac then you could invoke `Fallible.exceptionally(...)`. – Andy Turner Sep 23 '16 at 07:53
  • @cuihtlauac: that’s no contradiction. In the exceptionally case, `T` can be anything the caller wants as there is no actual value: `public static Fallible exceptionally(Exception e) { … }`. Compare with [`Optional.empty()`](https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html#empty--) which returns an `Optional`. – Holger Sep 23 '16 at 07:53
  • @Andy Turner: I assumed that the main concern is to prevent accidentally passing the exception as value, not to preclude it as intentional value, but even if so (I said *perhaps* this is intentional), I’d prefer to throw an exception if `value` is invoked with an illegal value, rather than to silently assume an `exceptionally` use case. – Holger Sep 23 '16 at 07:57
  • @Holger throwing an `IllegalArgumentException` is a fine approach too, but you'd still need the `instanceof` check to determine whether to throw the exception. – Andy Turner Sep 23 '16 at 07:58
  • That’s right, but using `instanceof` to sort out illegal arguments is a different use case as using `instanceof` to differentiate between two legal cases. Maybe it’s just me, but I’m happier with that. – Holger Sep 23 '16 at 08:03
  • @Holger I understand your point. It ultimately depends on OP's requirements :) – Andy Turner Sep 23 '16 at 08:04
2

No you can't.

This is easier to analyse if you consider the type-erased version that the runtime gets:

public Fallible(final T value) becomes public Fallible(final java.lang.Object value).

So the overload Fallible(final Exception exception) will be used if it's a better match; i.e. if exception is of type Exception or a child class of it.

Building a class that permits construction from a java.lang.Object but forbids construction (at compile-time) from a child class is not possible in Java. You'll have to rely on runtime checks (instanceof &c.). You can, by the way, solve this in C++ using templates. In this, and many other respects, Java when considered as an evolution of C++ is a case of one step forward and two steps back.

Bathsheba
  • 231,907
  • 34
  • 361
  • 483