Java arrays pose several challenges for records, and these added a number of constraints to the design. Arrays are mutable, and their equality semantics (inherited from Object) is by identity, not contents.
The basic problem with your example is that you wish that equals() on arrays meant content equality, not reference equality. The (default) semantics for equals() for records is based on equality of the components; in your example, the two Foo records containing distinct arrays are different, and the record is behaving correctly. The problem is you just wish the equality comparison were different.
That said, you can declare a record with the semantics you want, it just takes more work, and you may feel like is is too much work. Here's a record that does what you want:
record Foo(String[] ss) {
Foo { ss = ss.clone(); }
String[] ss() { return ss.clone(); }
public boolean equals(Object o) {
return o instanceof Foo f && Arrays.equals(f.ss, ss);
}
public int hashCode() { return Objects.hash(Arrays.hashCode(ss)); }
}
What this does is a defensive copy on the way in (in the constructor) and on the way out (in the accessor), as well as adjusting the equality semantics to use the contents of the array. This supports the invariant, required in the superclass java.lang.Record, that "taking apart a record into its components, and reconstructing the components into a new record, yields an equal record."
You might well say "but that's too much work, I wanted to use records so I didn't have to type all that stuff." But, records are not primarily a syntactic tool (though they are syntactically more pleasant), they are a semantic tool: records are nominal tuples. Most of the time, the compact syntax also yields the desired semantics, but if you want different semantics, you have to do some extra work.
Answer from Brian Goetz on Stack OverflowVideos
Java arrays pose several challenges for records, and these added a number of constraints to the design. Arrays are mutable, and their equality semantics (inherited from Object) is by identity, not contents.
The basic problem with your example is that you wish that equals() on arrays meant content equality, not reference equality. The (default) semantics for equals() for records is based on equality of the components; in your example, the two Foo records containing distinct arrays are different, and the record is behaving correctly. The problem is you just wish the equality comparison were different.
That said, you can declare a record with the semantics you want, it just takes more work, and you may feel like is is too much work. Here's a record that does what you want:
record Foo(String[] ss) {
Foo { ss = ss.clone(); }
String[] ss() { return ss.clone(); }
public boolean equals(Object o) {
return o instanceof Foo f && Arrays.equals(f.ss, ss);
}
public int hashCode() { return Objects.hash(Arrays.hashCode(ss)); }
}
What this does is a defensive copy on the way in (in the constructor) and on the way out (in the accessor), as well as adjusting the equality semantics to use the contents of the array. This supports the invariant, required in the superclass java.lang.Record, that "taking apart a record into its components, and reconstructing the components into a new record, yields an equal record."
You might well say "but that's too much work, I wanted to use records so I didn't have to type all that stuff." But, records are not primarily a syntactic tool (though they are syntactically more pleasant), they are a semantic tool: records are nominal tuples. Most of the time, the compact syntax also yields the desired semantics, but if you want different semantics, you have to do some extra work.
List< Integer > workaround
Workaround: Use a List of Integer objects (List< Integer >) rather than array of primitives (int[]).
In this example, I instantiate an unmodifiable list of unspecified class by using the List.of feature added to Java 9. You could just as well use ArrayList, for a modifiable list backed by an array.
record Foo( List < Integer >integers ) {}
List< Integer > integers = List.of( 1 , 2 );
var foo = new Foo( integers );
System.out.println( foo ); // Foo[integers=[1, 2]]
System.out.println( new Foo( List.of( 1 , 2 ) ).equals( new Foo( List.of( 1 , 2 ) ) ) ); // true
System.out.println( new Foo( integers ).equals( new Foo( integers ) ) ); // true
System.out.println( foo.equals( foo ) ); // true
I highly recommend Project Lombok (site here https://projectlombok.org/). From the example you listed
public record Person(String name, String gender, int age) {}
That can be done via Lombok like this
import lombok.Data
@Data
public class Person {
private String name;
private String gender;
private int age;
}
Lombox creates getters and setters, toString, hashCode, and a default constructor, as well as one with all the orgs.
UPDATE: It was pointed out in the comments that records are immutable. You can easily achieve this with lombok in a couple of ways. Option 1:
import lombok.Data
@Data
public class Person {
private final String name;
private final String gender;
private final int age;
}
That will add, again, the required-args constructor. @Data does not, for relatively obvious reasons, create setters for final fields.
Option 2 is a little more explicit:
import lombok.*;
@RequiredArgsConstructor
@EqualsAndHashCode
@Getter
public class Person {
private String name;
private String gender;
private int age;
}
You can use code generation. It's not as concise as Java 14, but better than nothing. AutoValue is pretty amazing.
import com.google.auto.value.AutoValue;
@AutoValue
abstract class Animal {
static Animal create(String name, int numberOfLegs) {
return new AutoValue_Animal(name, numberOfLegs);
}
abstract String name();
abstract int numberOfLegs();
}