java.util.Optional fields

Vitaly Davidovich vitalyd at gmail.com
Fri Sep 21 07:41:16 PDT 2012


Peter,

IMHO Optional should not capture three states: non-null value, null value,
and no value.  This is not a common semantic (and probably isn't the type
of design guidance one would give - i.e. don't use null for presence, use a
Null object instead).  For stream interfaces that allow impls to store
null, something else should be used (StreamOptional?) to denote this - I'd
leave Optional as a general class akin to Guava that only handles null vs
non-null.

Also, I don't have stats to back this up but Guava is widely used now
(based on my empirical observation) and people may already have a certain
notion of what Optional means based on guava's definition.  Adding
same-named API to JDK but with different semantics seems undesirable.

P.S. your version of Optional will generate synthetic accessors in the
serialization proxy code/usage, which I believe is undesirable in JDK code.

Sent from my phone
On Sep 21, 2012 9:44 AM, "Peter Levart" <peter.levart at gmail.com> wrote:

>  I actually like Optional as a name, especially because it has additional
> targeted API that simplifies logic by allowing calling it's methods in a
> chain...
>
> When I see where it's used in Stream, for example:
>
>     Optional<T> findFirst();
>
> I can only imagine that it's purpose was to allow null values as elements
> of streams. That is, it allows to differentiate between a not-present
> (elements of a stream) and an element of 'null'...
>
> An alternative of throwing NoSuchElementException was replaced by
> returning an Optional.
>
> Regards, Peter
>
> P.S.
>
> As to implementation details, here's a way to implement Optional in a
> single final class and not having additional boolean flag:
>
>
> public final class Optional<T> implements Serializable {
>
>     private static long serialVersionUID = 1L;
>
>     /**
>      * Common instance for {@code empty()}.
>      */
>     private final static Optional<?> EMPTY = new Optional<>(null);
>
>     /**
>      * Value, if present.
>      */
>     private final T value;
>
>     public Optional(T value) {
>         this.value = value;
>     }
>
>     /**
>      * An empty object.
>      *
>      * Note: Though it may be tempting to do so, avoid testing if an object
>      * is empty by comparing with {@code ==} against instances returned
>      * {@code Option.empty()}. There is no guarantee that it is a
> singleton.
>      *
>      * @param <T> Type of the non-existent value.
>      * @return an empty object.
>      */
>     public static<T> Optional<T> empty() {
>         return (Optional<T>) EMPTY;
>     }
>
>     /**
>      * Returns the value of this object.
>      *
>      * @return the value of this object.
>      * @throws NoSuchElementException if there is no value present.
>      */
>     public T get() {
>         if (this == EMPTY) {
>             throw new NoSuchElementException("No value present");
>         }
>         return value;
>     }
>
>     /**
>      * Return {@code true} if there is a value present otherwise {@code
> false}.
>      * @return {@code true} if there is a value present otherwise {@code
> false}.
>      */
>     public boolean isPresent() {
>         return this != EMPTY;
>     }
>
>     /**
>      * Return the value if present otherwise return {@code other}.
>      *
>      * @param other value to be returned if there is no value present.
>      * @return the value if present otherwise return {@code other}.
>      */
>     public T orElse(T other) {
>         return this != EMPTY ? value : other;
>     }
>
>     /**
>      * Return the value if present otherwise return result of {@code
> other}.
>      *
>      * @param other Factory who's result is returned if there is no value
> present.
>      * @return the value if present otherwise return result of {@code
> other}.
>      */
>     public T orElse(Factory<T> other) {
>         return this != EMPTY ? value : other.make();
>     }
>
>     /**
>      * Return the value otherwise throw an exception to be created by the
>      * provided factory.
>      *
>      * @param <V> Type of the exception to be thrown.
>      * @param exceptionFactory The factory which will return the exception
> to
>      * be thrown.
>      * @return the value.
>      * @throws V if there is no value present.
>      */
>     public<V extends Throwable> T orElseThrow(Factory<V> exceptionFactory)
> throws V {
>         if (this != EMPTY) {
>             return value;
>         } else {
>             throw exceptionFactory.make();
>         }
>     }
>
>     /**
>      * Return the value otherwise throw an exception of the provided class.
>      * Exception will be thrown with the message "No value present".
>      *
>      * @param <V> Type of the exception to be thrown.
>      * @param exceptionClass The class if exception to be thrown. Must
> support
>      * the default zero arguments constructor.
>      * @return the value.
>      * @throws V if there is no value present.
>      */
>     public<V extends Throwable> T orElseThrow(Class<V> exceptionClass)
> throws V {
>         if (this != EMPTY) {
>             return value;
>         } else {
>             try {
>                 throw exceptionClass.newInstance();
>             } catch (InstantiationException | IllegalAccessException e) {
>                 throw new IllegalStateException("Unexpected exception
> attempting to throw " + exceptionClass, e);
>             }
>         }
>     }
>
>     public<V> Optional<V> map(Mapper<T, V> mapper) {
>         return this != EMPTY ? new Optional<>(mapper.map(value)) :
> Optional.<V>empty();
>     }
>
>     @Override
>     public boolean equals(Object o) {
>         if (this == o) {
>             return true;
>         }
>         if (this == EMPTY || o == EMPTY || o == null || Optional.class !=
> o.getClass()) {
>             return false;
>         }
>
>         return Objects.equals(value, ((Optional)o).value);
>     }
>
>     @Override
>     public int hashCode() {
>         int result = Objects.hashCode(value);
>         result = 31 * result + (this != EMPTY ? 1 : 0);
>         return result;
>     }
>
>     @Override
>     public String toString() {
>         return this != EMPTY ? String.format("Optional[%s]", value) :
> "Optional.empty";
>     }
>
>     // Serialization
>
>     private Object writeReplace() throws ObjectStreamException {
>         if (this == EMPTY) {
>             return EmptyProxy.INSTANCE;
>         }
>         else {
>             return this;
>         }
>     }
>
>     private static final class EmptyProxy implements Serializable {
>         private static final EmptyProxy INSTANCE = new EmptyProxy();
>         private Object readResolve() throws ObjectStreamException {
>             return EMPTY;
>         }
>     }
> }
>
>
>
>
>
> On 09/21/2012 03:11 PM, Vitaly Davidovich wrote:
>
> I disagree, as I mentioned on that other thread as well. :) A lot of types
> can be considered as containers in the abstract - that's not a very
> interesting distinction.  In this case, a library class is added to make a
> language feature a bit less error prone (i.e. null and associated NPEs that
> unsuspecting developers hit).  Given this, Optional sounds correct and more
> targeted.  Moreover, given that Optional is meant to replace use of null, I
> don't think it should allow null as a valid value.  If null and "absent"
> have different meaning in a given scenario then don't use Optional for
> those.
>
> Sent from my phone
> On Sep 21, 2012 9:00 AM, "Paul Benedict" <pbenedict at apache.org> wrote:
>
>> > On Fri, Sep 21, 2012 at 7:19 AM, Vitaly Davidovich <vitalyd at gmail.com>wrote:
>> > Why not implement this like Guava with two concrete subtypes of
>> Optional:
>> > Present and Absent? It seems cleaner and I don't think performance will
>> be
>> > worse as compiler will only ever see two possible receivers and can use
>> a
>> > PIC to eliminate calls via vtable in those cases.
>>
>> I think this thread touches on an email I wrote earlier, which is
>> Optional is not really a good name choice. It's all focusing on its
>> potential lack of value rather than just being what it is -- a container.
>>
>> > On Sep 21, 2012 7:42 AM, "Peter Levart" <peter.levart at gmail.com> wrote:
>> > Maybe the intention was to allow null values to be wrapped in non-empty
>> > Optional? In that case the check for non-null in constructor is wrong...
>>
>> Peter, I agree that a present value of null and an absent value (defaults
>> to null) need to be differentiated. Yes, it looks wrong.
>>
>> Paul
>>
>
>


More information about the lambda-dev mailing list