views:

319

answers:

11

Its great when you can return a null/empty object in most cases to avoid nulls, but what about Collection like objects?

In Java, Map returns null if key in get(key) is not found in the map.

The best way I can think of to avoid nulls in this situation is to return an Entry<T> object, which is either the EmptyEntry<T>, or contains the value T.

Sure we avoid the null, but now you can have a class cast exception if you don't check if its an EmptyEntry<T>.

Is there a better way to avoid nulls in Map's get(K)?

And for argument sake, let's say this language don't even have null, so don't say just use nulls.

+1  A: 

Throw an exception. Examples of this are .NET's KeyNotFoundException, Java's ArrayIndexOutOfBoundsException, and Python's KeyError.

As we all know exceptions are for exceptional situations, so users should be expected to check that a key exists before looking it up.

Good

if collection.contains(key):
    return collection.get(key);

Bad

try:
    return collection.get(key);
catch KeyError:
    pass # Don't care.
John Kugelman
Tom's right: having to catch an exception for a non-exceptional case is slow.
Steven Sudit
Your good and bad examples are highly dependent on the situation. The choice of whether or not to check if the key exists first can depend on a number of things. For example, you may not want to check first if:- You're optimizing a tight loop for speed, where- It's rare that a key won't exist (ie, limited number of keys)
RHSeeger
I'm going to repeat my suggestion: something like TryGet would be ideal here.
Steven Sudit
+2  A: 

You could throw an "Element not present excepton", but exceptions are expensive, and should be reserved for "Exceptional situations". A value not being present in a map is hardly the case, so it might be a speed bump, but as usual, it depends on the context you are in.

Either way, as an advice, you should consider using the contains(key) method. There's always the posiblity that key has been mapped to the null value, so get(key) would return null, even if present in your map !!!.

Edit:

After watching get()´s source code, I came up with something ( for the record: completely untested and current time here is 01:08 AM, and I have a terrible cold !!)

  314       public V get(Object key) {
  315           if (key == null)
  316               return getForNullKey();
  317           int hash = hash(key.hashCode());
  318           for (Entry<K,V> e = table[indexFor(hash, table.length)];
  319                e != null;
  320                e = e.next) {
  321               Object k;
  322               if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
  323                   return e.value;
  324           }
                //This could be instanced by reflection (bad idea?)
  325           return new MappeableElementImpl();
   }

I you could force V to implement some interface such as MappeableElement or something like that which had a method boolean isUnmappedValue(), then the get() method could return an instance of that interface.

So it would end up in something like:

Element e = map.get(notpresentkey);

if (e.isUnmappedValue()){sysout (notpresentkey  +" is not present");}
Tom
How is that an improvement over the situation with nulls? Because it sure as hell looks like a convoluted way to get exactly the same result to me...
Michael Borgwardt
+5  A: 

Two possible solutions:

  1. Provide a contains(key) function. Throw an exception if get(key) is called for a non-existent key. The downside is: calling get() after contains() duplicates operations; not efficient.

  2. Functional languages use Maybe in similar situations. This article explains how to implement Maybe in Java.

Igor Krivokon
This would be slow, because the call to Contains pretty much does the same thing as Get.
Steven Sudit
hence the tryGet options. Outputs are 1. Found or not 2. Result if found
Nader Shirazie
@Steven - I agree, thanks for the comment. Updated my answer.
Igor Krivokon
@Steven: the implementation could easily cache the last successful `contains()` call, check if `get()` is called with the same argument and return the value.
Joachim Sauer
+1 for Maybe, but realize that it takes you right back to where you started from in type-theoretic terms. If the language permits nulls, you've effectively got maybes built in (though with slightly different clothes on).
Donal Fellows
@Joachim: I'm not sure this is a wise optimization, as it penalizes the more common pattern where get() is called with a different key each time. I think a better answer is to offer a TryGet, the way .NET collections do. This decouples the value from whether it was found.
Steven Sudit
+1  A: 

Return an instance of a generic Optional<T> type.

Doug McClean
A: 

I suppose you could return an object that has a boolean for Found and an Item that has either the found item or throws an exception. Another way is to use a TryGet technique.

Steven Sudit
+1  A: 

There are three possibilities as I see it.

  • Return a null or a Null Object.
  • Throw and catch an exception.
  • Or avoid the issue by calling containsKey before you call get.

If you're worried about the Null Object being the wrong type, then you could design your map to accept a NullObjectFactory, that creates a special Null Object of the correct type for what you're dealing with (as in the Null Object pattern). That way you could get from a Map without having to check if it contains the key, without having to check if it returns a null, and without having to catch any exceptions.

_jameshales
A: 

I understand that you are looking for an alternative to null, but the alternatives all seem to result in some Exception case, which is much more expensive (generating stacktraces,etc.) than testing for null.

So to keep in the game, return an InvalidValueException when trying to insert an invalid (ie null) value or return a NoValuePresentException on a bad get(). (all the while wishing there was a simple null test that you could perform)

akf
+2  A: 

Is your question caused by some unusual requirement or scenario for the data structure you're designing, or are you just theoreticizing? If it's the latter, you might want to consider the fact that null is conceptually indistinguishable from any other reference value (e.g. ref to some final obj denoting null), and exceptions are expensive. Unless you have a specific concern or goal making you ask this question, you're really wasting (y)our time. Cheers!

Paul Milovanov
Designing a system that doesn't have (or at least eschews the use of) `null` can be very beneficial, it's not just theoretical thought experiments. See the Null Object pattern for example: http://en.wikipedia.org/wiki/Null_Object_pattern
Joachim Sauer
A: 

Conceptually it is a big problem. One of useful scenarios is to create adapter which delegates all calls into underlying map object. For this adapter there will be required parameter which specify null object. E.g:

class MapAdapter<K,V> implements Map<K,V> {
    private Map<K,V> inner = new HashMap<K,V>();
    private final V nullObject;

    private MapAdapter(V nullObject) {
        this.nullObject = nullObject;
    }

    public static <K,V> Map<K,V> adapt(Map<K,V> mapToAdapt, V nullObject) {
        MapAdapter<K,V> adapter = new MapAdapter<K,V>(nullObject);
        adapter.inner.addAll(mapToAdapt);
        return adapter;
    }


    //Here comes implementation of methods delegating to inner.


   public V get(K key) {
       if (inner.containsKey(key)) {
           return inner.get(key);
       }
       return nullObject;
   }
}

A lot of work, but it allows generic NullSafe implementation.

Rastislav Komara
+1  A: 

Cf. @Doug McClean above -- this sounds like what Scala calls Option and Haskell calls Maybe. It's a lot like what you described, with the Entry and EmptyEntry -- Scala uses Some for the valid Entrys and None for the EmptyEntry.

Daniel Spiewak has a good intro to Option, including a basic Java implementation. (Instead of his instanceof None check, though, I would probably have an isNone() method, and maybe just have one None instance -- since Java generics are erased at run time, and it never actually contains anything, you can cast it, or have a "factory" method that casts it, to whatever None<T> you need.)

David Moles
A: 

It looks like Maybe and Option is the way to go.

But pattern matching is also required to make it simpler for the user of this class. This way the user does not need to use instanceof and cast with the risk of a real time class cast exception.

Pyrolistical
That being the case, maybe it's best not to implement `Map` at all -- instead, implement a map-like interface that lets you, instead of `get()`, pass in a key and a value handler, and likewise for iteration.
David Moles
@David: With all due respect, that sort of delegate-based answer sounds more complex and slow than TryGet.
Steven Sudit
@Steven Sudit: Can you elaborate? I would expect the performance to be at worst the same, but perhaps I'm misunderstanding your tryGet() approach. It also doesn't seem more complex to me, but that could be because I'm used to a more functional programming style.
David Moles
@David: It's probably a safe bet that passing in a delegate is slower than using TryGet, but it's not necessarily *much* slower, so speed isn't the real issue. Complexity is. Maybe it's because I'm *not* coming from a functional programming style background, but the idea of setting up a callback instead of just checking a return value sounds Rube Goldbergish. I don't see how anything can be simpler than `if (map.tryGet(key, out value))`. Perhaps an example of the delegate approach might help.
Steven Sudit
@Steven Sudit: Done: http://stackoverflow.com/questions/2681415/null-free-maps-is-a-callback-solution-slower-than-tryget :)
David Moles