简体   繁体   中英

Best way to map multiple determinants to a value in Java

I have a requirement in which I need to map multiple determinants to values.

  • Each set of determinants in a given job execution is guaranteed to be unique. The value to be determined doesn't have to be unique but it probably is.

  • Depending on the input to the job execution, this could be either one key, or the combination of two keys, or the combination of n keys that will be mapped to a single value. In practice this n will probably be limited to no more than 5, although it is possible it could exceed that.

  • Each job execution will have a set number of determinants for all inputs (Ie, all inputs will have either 2 determinants, 3 determinants, or n determinants, and will not have a mix).

One key example: foo --> bar

Two keys: foo, bar --> baz

Three keys: foo, bar, baz --> hai

Prior to this, the requirement was that I would only ever map two values to another value. I created an immutable Key class with two member variables and the appropriate override of equals and hashCode .

public class Key {
    String determinant0;
    String determinant1;
    public Key(String d0, d1) {
        determinant0 = d0;
        determinant1 = d1;
    }
    // ..
} 

However, now that I may be dealing with n number of values, I want to take a look at using a list as the key.

Map<List, String> map = new HashMap<List, String>();
map.put(Arrays.asList("foo", "bar", "baz"), "hai");
String determined = map.get(Arrays.AsList("foo","bar","baz"));
assert (determined.equals("hai"));

This question reminds me that it is bad to use a mutable object (like a List) as a key in a map. However, in my application, the key is only set once and is never altered. Here is an alternative from this question that forces it to be immutable:

HashMap<List<String>, String> map;

map.put(
    // unmodifiable so key cannot change hash code
    Collections.unmodifiableList(Arrays.asList("foo", "bar", "baz")),
    "hai"
);

In addition, I could always make a class like the following to prevent mutations on the list:

public class Key {
    List<String> determinants;
    public Key(List<String> determinants) {
        this.determinants = determinants
    }
    @Override
    public boolean equals(Object obj) {
        //...
    }
    @Override
    public int hashCode() {
        //...
    }
}

Key key = new Key(Arrays.asList("foo","bar","baz"));

Using a plain array as the key won't work, because an array's equal method only checks for identity:

Map<String[], String> map = new HashMap<String[], String>();
String[] key = new String[]{"foo", "bar", "baz"}
map.put(key, "hai");
System.out.println(map.get(key)); // null

That could be fixed by the following:

public class Key {
    String[] determinants;
    public Key(String... determinants) {
        this.determinants = determinants;
    }
    @Override
    public boolean equals(Object obj) {
        //...
    }
    @Override
    public int hashCode() {
        //...
    }
}

How about concatting all the determinants together in a string?

public class Key {
    String hash = "";
    public Key(String... determinants) {
        for (String determinant : determinants) {
            hash += determinant + "_";
        }

    }
    @Override
    public boolean equals(Object obj) {
        //...
    }
    @Override
    public int hashCode() {
        //...
    }
}

Which one of these solutions (or another one that I did not propose) is the best suited for these requirements?

As a comment, your question includes too much details and could have been way shorter. Now comes my answer.

I prefer using a wrapper class that completely hides the representation of the class. One thing you can do as a small optimization is storing the hashCode of your keys to prevent computing it every time. The equals method will be called more rarely (each collision in the map) and you can't do much about it :

public class Key {
    private String[] determinants;
    private int hashCode;

    public Key(String... determinants) {
        if (determinants == null || determinants.length == 0) {
            throw new IllegalArgumentException("Please provide at least one value");
        }
        this.determinants = determinants;
        this.hashCode = Objects.hash(determinants);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Key)) return false;
        Key that = (Key) o;
        return Arrays.equals(determinants, that.determinants);
    }

    @Override
    public int hashCode() {
        return hashCode;
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM