Skip to content

Instantly share code, notes, and snippets.

@Jannyboy11
Last active April 27, 2020 14:53
Show Gist options
  • Save Jannyboy11/067f5966afc6130711b2035795a45ea5 to your computer and use it in GitHub Desktop.
Save Jannyboy11/067f5966afc6130711b2035795a45ea5 to your computer and use it in GitHub Desktop.
Records implementation oversight
package com.example;
import java.util.Arrays;
/*
Hi all,
While experimenting with the new records, I found what I believe is an oversight in the implementation.
The generated equals method for records is based on equality of the components, but when the record component is an array, it still uses regular Object#equals(Object) method.
This is not what Java developers would normally write (or generate using IDEs) for their data carrier classes, as instead they would use Arrays.equals or Arrays.deepEquals.
I believe that in order for records to succeed, they need to be truly boilerplate-reducing, so I'm suggesting the following changes to the default equals, hashCode and toString implementations:
IF the field in the record is statically known to be a one-dimensional array:
  THEN java.util.Arrays.equals(array1, array2) is used to compare the component of two record instances     (applies to <recordclass>#equals(Object))
  AND java.util.Arrays.hashCode(array) is used to compute the hashcode of the component                     (applies to <recordclass>#hashCode())
  AND java.util.Arrays.toString(array) is used to compute the string representation of the component        (applies to <recordclass>#toString())
ELSE IF the field in the record is statically known to be a multidimensional array:
  THEN java.util.Arrays.deepEquals(array1, array2) is used to compare the component of two record instances     (applies to <recordclass>#equals(Object))
  AND java.util.Arrays.deepHashCode(array) is used to compute the hashcode of the component                     (applies to <recordclass>#hashCode())
  AND java.util.Arrays.deepToString(array) is used to compute the string representation of the component        (applies to <recordclass>#toString())
ELSE
  just use the old code generation algorithm from Java 14
I am not familiar with the compiler implementation details, but I imagine this is a trivial change.
There is a cost to compilation speed for doing these extra checks, so some performance testing would be required.
One might say that therefore developers shouldn't use arrays for record components, but a data carrier class with a constructor with a varargs parameter is a common-enough scenario
so I'd like this to be supported properly.
The example code (written by me, public domain licensed) below illustrates the issue.
I did not include anything related to hashCode, but one can easily infer what the issues there would be.
Kind regards,
Jan Boerman
*/
public class RecordTest {
private static record Messages(String... messages) {
public void println() {
System.out.println("Messages[messages=" + Arrays.toString(messages) + "]");
}
}
private static record JaggedArrayRecord(String[][] world) {
public void println() {
System.out.println("JaggedArrayRecord[world=" + Arrays.deepToString(world) + "]");
}
}
public static void main(String[] args) {
//single array example
System.out.println();
System.out.println("--- Example using a record with a field that is a one dimensional array ---");
System.out.println();
String[] m1 = new String[] { new String("Hello") };
String[] m2 = new String[] { new String("Hello") };
Messages messages1 = new Messages(m1);
Messages messages2 = new Messages(m2);
messages1.println();
System.out.println(messages1); //expecting the same output as the output from messages1.println()
boolean arraysEquals = Arrays.equals(m1, m2);
boolean objectEquals = messages1.equals(messages2); //expecting the same boolean value as Arrays.equals(m1, m2)
System.out.println("equal according to Messages#equals(Object) ?: " + objectEquals);
System.out.println("equal according to Arrays#equals(Object[], Object[]) ?: " + arraysEquals);
if (arraysEquals != objectEquals) {
System.out.println("These equality implementations don't yield the same value, which is not desirable");
} else {
System.out.println("These equality implementations yield the same value, which is desirable");
}
//jagged array example
System.out.println();
System.out.println("--- Example using a record with a field that is a multidimensional array ---");
System.out.println();
String[][] world1 = new String[][] { new String[] { new String("World!") } };
String[][] world2 = new String[][] { new String[] { new String("World!") } };
JaggedArrayRecord jagged1 = new JaggedArrayRecord(world1);
JaggedArrayRecord jagged2 = new JaggedArrayRecord(world2);
jagged1.println();
System.out.println(jagged1); //expecting the same output as the output from jar1.println()
boolean jaggedArraysDeepEquals = Arrays.deepEquals(world1, world2);
boolean jaggedObjectEquals = jagged1.equals(jagged2); //expecting the same boolean value as Arrays.deepEquals(world1, world2)
System.out.println("equal according to JaggedArrayRecord#equals(Object) ?: " + jaggedObjectEquals);
System.out.println("equal according to Arrays#deepEquals(Object[], Object[]) ?: " + jaggedArraysDeepEquals);
if (jaggedArraysDeepEquals != jaggedObjectEquals) {
System.out.println("These equality implementations don't yield the same value, which is not desirable");
} else {
System.out.println("These equality implementations yield the same value, which is desirable");
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment