Java hashcode: Object vs composite

I stumbled upon this method in some code whose sole purpose is to create a String Key for a HashMap (EDIT: In my case, all X, Y and Z will be numeric if that simplifies):

 protected String createMappingKey(String x, String y, String z) { return x+":"+y+":"+z; } 

Something in this code is not sitting right, and I think it would be better to replace such an object (note that this code was generated by my IDE, so you can change the implementation as you would like):

 public static class MyKey { String x,y,z; // Constructor(s), maybe getters and setters too here @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; MyKey myKey = (MyKey) o; if (x != null ? !x.equals(myKey.x) : myKey.x != null) return false; if (y != null ? !y.equals(myKey.y) : myKey.y != null) return false; if (z != null ? !z.equals(myKey.z) : myKey.z != null) return false; return true; } @Override public int hashCode() { int result = x != null ? x.hashCode() : 0; result = 31 * result + (y != null ? y.hashCode() : 0); result = 31 * result + (z != null ? z.hashCode() : 0); return result; } } 

But this seems like very complicated code for not very great value. I am sure that there will be only a slight difference in the number of collisions between the two approaches.

Which of these approaches would you prefer and why? Is there a better approach that I am missing?

If one of these approaches would have a significant number of collisions more than the other, this would also interest me, and I will open a separate question to deal with this.

+4
source share
9 answers

The approach that I usually use when I want to create a key, but donโ€™t feel that the full key class is justified, is to create a List using Arrays.asList .

 protected List<Object> createMappingKey(String x, String y, String z) { return Arrays.<Object>asList(x, y, z); } 

String's danger is that equals can collide if your elements use a character that you also used as a delimiter. The list ensures that such a collision cannot occur. It also has the advantage of working with any object with the correct equals / hashCode implementation, and not just with strings and objects with equivalent toString implementations.

If you want to create a key class, you can use Appache Commons EqualsBuilder and HashCodeBuilder to significantly reduce your hashCode and equals classes.

+3
source

You can try another delimiter, for example, assuming that your string does not have nul bytes. Most likely, the colon ':'

 protected String createMappingKey(String x, String y, String z) { return x+'\0'+y+'\0'+z; } 

As you said, a lot of work for a very small difference.

One drawback is that it will treat null and "null" as the same thing. You must judge whether this can be a problem.

+1
source

I am very sorry to say this, but your answer is worse - it will use 4 times the number of references to objects (one for the key, plus one for each line = 4), plus it must separately calculate 3 hash codes plus additional processing for each search .

0
source

Your code will be faster than a line.

You can simplify it by moving the logic to a common base class using the Java equivalent of this method . (using an abstract method that returns an array of property values)

He would look like

 abstract class EqualityComparable { protected abstract Object[] keys(); @Override public boolean equals(Object o) { ... } @Override public int hashCode() { ... } } 
0
source

If your strings are relatively short, then I would prefer a combo string key approach. The reason is because you do not enter code complexity. Do you really need to write / debug / maintain a composite key class and its equals / hashCode methods? On the other hand, if the strings are not short, then the class-based approach is better, since it will reduce the amount of memory.

0
source

I do not believe that your MyKey approach above would lead to significant behavior from HashMap compared to using the key generated using createMappingKey . Both MyKey and String have the correct behavior of equals and hashCode , so from the point of view of HashMap there is no difference between them.

I think that using MyKey as your HashMap key instead of the plain old String makes it clearer for another programmer that there are strict rules for what makes up the correct key, do something stupid like stick in "" in HashMap ( both by appointment and by accident).

0
source

The key created by createMappingKey will be used as the key in the hash table - this means that hashCode () will be called on it. So your hashCode key is probably worse than using the method. If you want to make the code more understandable by introducing a separate key class that will be great, but just use

  @Override public int hashCode() { return (x+":"+y+":"+z).hashCode(); } 

So much easier. Please note that you may want to make sure that the selected delimiter (in this case ":" does not occur in lines x, y, z, otherwise different lines can create the same hash code.

0
source

The createMappingKey() method creates a line only once. Hashcode, which is used for Maps, is evaluated only once for String, since it is immutable. But the method creates some StringBuilders to perform concatenation. The string representation itself is only created for the subsequent presentation of the hash code in HashMap. So this is a waste product without any value.

You can make it useful and use other purposes. Your MyKey class may also be immutable, so you can pre-compute your own hash code and cache it. This can improve performance when the HashMap can be accessed or changed very often because calls to equals () / hashCode () are faster.

A very proprietary key class sounds like the objectโ€™s best orientation, but of course introduces additional source code and clutter. But this probably better says what the key really is: is it just a string concatenation? I doubt it. It can be an identifier for a client or something like that, so why not call it that?

 public class CustomerIdentifier { public CustomerIdentifier(String tenantId, String customerName, String location) { // Validate tenantId, customerName, location // eg non-null, minimum length, uppercase/lowercase // pre-calculate hashCode // and throw away values if we don't need them } // equals() and hashCode() go here public long hashCode() { return precalc; }; } map.put(new CustomerIdentifier("a","b","c), customer); 

This approach allows you to determine in your project how the Customer object is identified. It encapsulates key generation - you are not dependent on whether to use it : as a delimiter or something else. Other clients (for example, if accessing the HashMap with other code is not under your control) may be easier because they simply use the CustomerIdentifier instead of duplicating the key generation code (this is a bit, but the problem can often arise later when someone changes the delimiter, but another code is not ...)

In addition, this approach allows you to put a check on x / y / z values โ€‹โ€‹in a class. Can they be empty? Can they be colons? How do you distinguish them?

 x=':' y='' z='' is :::: x='' y=':' z='' is :::: x='' y='' z=':' is :::: 

The disadvantage is that it is probably harder to debug. In such cases, you would like to implement toString () in this class, so that you can more quickly look at the hashmap in the debugger view and find the key you are looking for.

The second drawback is that you often have to create new key objects for code that accesses the HashMap but only has x / y / z values.

0
source

I have the feeling that the missing part of your sample code will be more useful than what you posted. Why exactly do you need the String key? These numbers x, y, z, if connected, will be better placed in some meaningful bean, which you can use as a card key (and which can also offer additional functionality).

If X, Y, Z really are coordinates, this means that you just need a Point3D key. Make it immutable and just use it, why create line patterns only for map keys? If you need this view {x}: {y}: {z} for something, you can return it from Point3D.toString (). And you can also restore a point from a string representation using some static valueOf (String) factory method.

0
source

Source: https://habr.com/ru/post/1334275/


All Articles