Using switch patterns to rewrite Arrays.deepHashCode
Tagir Valeev
amaembo at gmail.com
Sat Sep 11 06:28:07 UTC 2021
Hello!
I just was thinking about good samples where switch patterns could be
useful. One idea I have is Arrays.deepHashCode. The current
implementation uses a helper method and queries getClass():
public static int deepHashCode(Object a[]) {
if (a == null)
return 0;
int result = 1;
for (Object element : a) {
final int elementHash;
final Class<?> cl;
if (element == null)
elementHash = 0;
else if ((cl = element.getClass().getComponentType()) == null)
elementHash = element.hashCode();
else if (element instanceof Object[])
elementHash = deepHashCode((Object[]) element);
else
elementHash = primitiveArrayHashCode(element, cl);
result = 31 * result + elementHash;
}
return result;
}
private static int primitiveArrayHashCode(Object a, Class<?> cl) {
return
(cl == byte.class) ? hashCode((byte[]) a) :
(cl == int.class) ? hashCode((int[]) a) :
(cl == long.class) ? hashCode((long[]) a) :
(cl == char.class) ? hashCode((char[]) a) :
(cl == short.class) ? hashCode((short[]) a) :
(cl == boolean.class) ? hashCode((boolean[]) a) :
(cl == double.class) ? hashCode((double[]) a) :
// If new primitive types are ever added, this method must be
// expanded or we will fail here with ClassCastException.
hashCode((float[]) a);
}
It can be simplified with patterns:
public static int deepHashCode(Object[] a) {
if (a == null)
return 0;
int result = 1;
for (Object element : a) {
final int elementHash = switch(element) {
case null -> 0;
case Object[] arr -> deepHashCode(arr);
case byte[] arr -> hashCode(arr);
case short[] arr -> hashCode(arr);
case char[] arr -> hashCode(arr);
case int[] arr -> hashCode(arr);
case long[] arr -> hashCode(arr);
case float[] arr -> hashCode(arr);
case double[] arr -> hashCode(arr);
default -> element.hashCode();
};
result = 31 * result + elementHash;
}
return result;
}
Here we see good use of case null. One problem is the possible (though
unlikely) appearance of new primitive types. In this case, the default
branch will be silently taken. Well, we can add an assert:
default -> {
assert !element.getClass().isArray();
yield element.hashCode();
}
Probably we need a sealed supertype for all arrays that permits all
final primitive subtypes and non-sealed Object[] subtype? In this
case, we could make a sub-switch and rely on the compiler:
final int elementHash = switch(element) {
case null -> 0;
case AnyArray anyArray -> switch(anyArray) {
case Object[] arr -> deepHashCode(arr);
case byte[] arr -> hashCode(arr);
case short[] arr -> hashCode(arr);
case char[] arr -> hashCode(arr);
case int[] arr -> hashCode(arr);
case long[] arr -> hashCode(arr);
case float[] arr -> hashCode(arr);
case double[] arr -> hashCode(arr);
// no default case!
};
default -> element.hashCode();
};
Well, this is probably a huge amount of work with little benefit,
though probably something similar is being baked in Valhalla?
In any case, I like the new version more than the original one.
With best regards,
Tagir Valeev.
More information about the amber-spec-experts
mailing list