0

NOTE : Feel free to edit the title if it is somewhat different than my question.

In one of our application, we are maintaining a lot of properties inside Instance object. Obviously it's maintained as Map<K, Map...>. There are few instance objects which holds different types of properties.

For example

1) AppPropertyReader - Map<String, String>

2) SMSPropertyReader - Map<UUID, Map<SMSType, String>> // SMSType is enum

3) PropertyReader - Map<UUID, Map<String, Map<String, String>>>

and a few more...

So for every reader class, I've to define getter(s) and setter. Depending upon the no of nested types, new getters have to be introduced. But I feel that it's somewhat poorer / uglier (something which I can't express clearly) to duplicate the getter & other essential codes in every reader class. So I've written three generic classes for handling upto three maps (like defined in PropertyReader class).

Base Map holder

import java.util.HashMap;
import java.util.Map;

public abstract class CommonPropertyHolder<K, V> {

    protected Map<K, V> properties;

    public CommonPropertyHolder() {
        init();
    }

    protected void init() {
        properties = new HashMap<K, V>();
    }

    public Map<K, V> getProperties() {
        return properties;
    }

    public V getProperty(K key) {
        return properties.get(key);
    }

    protected void setProperty(K key, V value) {
        properties.put(key, value);
    }

    protected void clear() {
        init();
    }

}

Nested Map holder

import java.util.HashMap;
import java.util.Map;

public class CommonMapPropertyHolder<T, K, V> extends CommonPropertyHolder<T, Map<K, V>> {

    public V getProperty(T type, K key) {
        Map<K, V> properties = getProperty(type);
        if( properties == null ) return null;
        return properties.get(key);
    }

    protected void setProperty(T type, K key, V value) {
        Map<K, V> properties = getProperty(type);
        if( properties == null ) {
            properties = new HashMap<K, V>();
            setProperty(type, properties);
        }
        properties.put(key, value);
    }

}

Deep Nested Map holder (not sure about if it can be expressed as "Deep Nested")

import java.util.HashMap;
import java.util.Map;

public class CommonMultiMapPropertyHolder<T, PK, K, V> extends CommonMapPropertyHolder<T, PK, Map<K, V>> {

    public V getProperty(T type, PK parentKey, K key) {
        Map<K, V> properties = getProperty(type, parentKey);
        if( properties == null ) return null;
        return properties.get(key);
    }

    protected void setProperty(T type, PK parentKey, K key, V value) {
        Map<PK, Map<K, V>> mapProperties = getProperty(type);
        if( mapProperties == null ) {
            mapProperties = new HashMap<PK, Map<K, V>>();
            setProperty(type, mapProperties);
        }
        Map<K, V> properties = mapProperties.get(parentKey);
        if( properties == null ) {
            properties = new HashMap<K, V>();
            mapProperties.put(parentKey, properties);
        }
        properties.put(key, value);
    }

}

So my property reader classes can extend like

class AppPropertyReader extends CommonPropertyHolder<String, String>

class SMSPropertyReader extends CommonMapPropertyHolder<UUID, SMSType, String>

class PropertyReader extends CommonMultiMapPropertyHolder<UUID, String, String, String>

This works amazingly perfect.

In normal view, these generic classes won't be much a difference. But if you look at how each class refers super methods and variables, you will know how beautiful it is.

If I ever want to introduce another PropertyReader, I just have to extend the appropriate Generic class without writing any code other than class declaration.

Here are my questions.

1) Since it goes upto 3 levels deeper for most parameterized generics, will it affect the runtime performace..? (I read somewhere in stackoverflow that depth upto 5 levels don't have noticable performance impact.) But, since every extended class is generics, will there be a performance impact..?

2) Before I came up with these 3 generic classes, I tried to achieve any level of nested maps with just a single generic class. But I can't come up with any implementable solution. Is it possible to just implement a single generics class which can handle any levels of depths..? (Ofcourse everything have to be recursion if implementable)

  • Why didn't you just use a custom key or library classes as in here? – Ordous Jul 11 '16 at 17:33
  • @Ordous , that is how it's coded now. As stated, it leads to lot of code duplications and less manageable.. that's y I've moved to generic pattern – The Coder Jul 11 '16 at 17:42
  • 1
    Reason for downvote..? – The Coder Jul 11 '16 at 19:36
4

To answer your questions directly:

1) No, it will not affect performance much. Generics are mostly erased by the time the program gets executed anyway, so the additional cost is really only multiple (up to 3) map lookups instead of 1. I would not expect this to ever be a bottleneck in a scenario where usage of a standard HashMap is acceptable.

2) Not really. Generics are used for compile-time safety, and the compiler doesn't really like any unknown thing during compilation, including number of params. I hear C++ templates would do the trick, but that's quite a different beast (although often used for the same thing).


A couple of side comments about your approach:

1) At least the 2-key map is a very common problem, and a solved one. Look up Guava's Table - it's pretty much what you want, a table that has 2 keys, along with some additional bonuses.

2) Unless you need to query per-row or per-column maps separately (rather than just do single property reads or writes), a combined key is preferable when there are multiple dimensions. You can see how your signatures bloat in size. Whereas a simple key class will have it in a much more readable form.

3) If you insist on long generic type lists, then a much better way of doing this would be composition. It's what you're doing anyway, just without the clutter (you have 3 setProperty methods exposed in your "multimap", and if you decide you need a 4th param, the method will just keep on growing). All arguments for the "Composition over inheritance" principle set out by numerous people apply here as well.

4) A Multimap is commonly known as a map of keys to collection of values, rather than map-to-map-to-map-to-values. Might confuse your teammates if you stick with that naming of your class :)

  • Thank you for the pointer to Guava's Table. I have used other Guava data-structures but I have never noticed this one. I always used the: HashMap<R, HashMap<C, V>> type setup. Thanks again. It is always great to find something legit useful. – Ivan Jul 12 '16 at 12:17
  • @Ivan I've only noticed it recently myself, when searching for a good way to solve this problem! Unfortunately this class doesn't have a nice way to make it thread-safe, bar full external synchronization. – Ordous Jul 12 '16 at 12:21
1

If it is a little confusing to you, it's a problem. Someone unfamiliar to the code (which will probably be you in 2 months) will be utterly baffled.

From the look of your code, you are letting yourself in for a lot of boilerplate. You might be best served looking at a single problem and saying "What's the simplest thing that could possibly work" and implement only that--then move to the next problem. If you find similarities pop up THEN refactor into something more general. When you are working on business logic, don't immediately try to make reusable tools, instead make a business class that just does what you need and wait for patterns to emerge later.

Here's why I'm assuming you are building general tools: In Java, Generics are mostly a tool for tool builders. Not exclusively (They come in handy a lot), but if you need a hashmap-based class that holds, say, Invoices indexed by invoice number, you would build a class that is exactly that, InvoiceHolder. a getInvoice would take an invoice number and return an invoice object--no generics needed outside the class.

You seem to be trying to simplify properties access throughout your app--which is a laudable goal, but as I said earlier, it's tooling. The easiest way to figure out tooling is to do the simple thing first then only generalize it as required by DRY, otherwise YAGNI.

A second point: Extends generally adds to the complexity of your code and does not provide the benefit you'd think. I learned this the hard way because it seems like a very powerful tool, it just doesn't usually work out that way. Wherever possible consider encapsulation first. This doesn't mean never extend, it just means that a combination of containing another object and/or implementing an interface always seems to work out better than sub-classing even if you have to have a couple forwarding methods.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.