Sergio Obici
Sergio Obici

Reputation: 41

Java HashSet unique elements

About HashSet .... The method add() returns true if this set did not already contain the specified element. But if I write:

HashSet<Car> hash = new HashSet<Car>();
Car a = new Car("Ferrari","F40",1987);
Car b = new Car("Ferrari","F40",1987);
System.out.println("Hashcode di a:"+a.hashCode());
System.out.println("Hashcode di b:"+b.hashCode());
System.out.println(hash.add(a));
System.out.println(hash.add(b));

I obtain this:

Hashcode di a: 1824327938 
Hashcode di b: 1824327938
true
true

... and I have a set of two elements. I'd expect just one. What's there I don't understand? How I can avoid the second add?

Thank in advance.

Sergio

PS I add my equals method:

public boolean equals(Car c) {
      if (brand.equals(c.getBrand()) && model.equals(c.getModel()) && version == c.getVersion())
        return true;
      else
        return false;
    }
}

and that's it. It seems that I wrote equals method in a wrong way not considering that equals argoument must be Object and not directly a Car.

Upvotes: 1

Views: 296

Answers (2)

Mr. Polywhirl
Mr. Polywhirl

Reputation: 48713

Update

After seeing that your method signature was incorrect, I would suggest the following:

If you had placed @Override on your public boolean equals(Car c), you would have had a compile-time error.

@Override
public boolean equals(Car c) { // ERROR
    // ...            ^ expected Object here
}

This would be caused because the parameter in the method signature was incorrect. The compiler did not know that you mean public boolean equals(Object c). When you override a method, always add the annotation. This way you do not experience any undesired behavior.


Original answer

If you use an IDE like IntelliJ or VSCode (with the "Java Code Generators" extension), you can have them generate constructors, accessors (getters), mutators (setters), toString(), and even override the equals(Object) and hashCode() methods.

If you use Lombok, you can even have it generate overrides via @EqualsAndHashCode.

User k314159 mentioned pointed out that you can use the record keyword to define a standard POJO object. For information on this new construct check out: Java 14 Record vs. Lombok. Please keep in mind that this is a feature of JDK 14.

Generate methods with IDE

Here is an example of overriding the Object methods equals, hashCode, and toString:

import java.util.Objects;

public class Car {
    private String make;
    private String model;
    private int year;

    public Car() {}

    public Car(String make, String model, int year) {
        this.make = make;
        this.model = model;
        this.year = year;
    }

    @Override
    public boolean equals(Object other) {
        if (other == this) return true;
        if (!(other instanceof Car)) return false;
        Car car = (Car) other;
        return Objects.equals(make, car.make) && Objects.equals(model, car.model) && year == car.year;
    }

    @Override
    public int hashCode() {
        return Objects.hash(make, model, year);
    }

    @Override
    public String toString() {
        return "{" +
                " make='" + getMake() + "'" +
                ", model='" + getModel() + "'" +
                ", year='" + getYear() + "'" +
                "}";
    }

    public String getMake() {
        return this.make;
    }

    public void setMake(String make) {
        this.make = make;
    }

    public String getModel() {
        return this.model;
    }

    public void setModel(String model) {
        this.model = model;
    }

    public int getYear() {
        return this.year;
    }

    public void setYear(int year) {
        this.year = year;
    }
}
import java.util.HashSet;

public class CarDriver {
    public static void main(String[] args) {
        HashSet<Car> hashSet = new HashSet<Car>();

        Car a = new Car("Ferrari", "F40", 1987);
        Car b = new Car("Ferrari", "F40", 1987);

        System.out.println(hashSet.add(a)); // true
        System.out.println(hashSet.add(b)); // false

        System.out.println(hashSet);
    }
}

Output:

true
false
[{ make='Ferrari', model='F40', year='1987'}]

Lombok

Here is the latest dependency version for Maven (as of today):

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.28</version>
    <scope>provided</scope>
</dependency>

The same Car class above can be compiled using the following annotations:

import lombok.*;

@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
@Getter
@Setter
@ToString
public class Car {
    private String make;
    private String model;
    private int year;
}

With the @Data annotation, we can eliminate @ToString, @EqualsAndHashCode, @Getter, and @Setter. Keep in mind that this annotation will also use the @RequiredArgsConstructor as well.

import lombok.*;

@NoArgsConstructor
@AllArgsConstructor
@Data
public class Car {
    private String make;
    private String model;
    private int year;
}

Minimal POM:

<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <properties>
    <maven.compiler.target>1.8</maven.compiler.target>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven-compiler-plugin.version>3.11.0</maven-compiler-plugin.version>
    <lombok.version>1.18.28</lombok.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>${lombok.version}</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>${maven-compiler-plugin.version}</version>
        <configuration>
          <annotationProcessorPaths>
            <path>
              <groupId>org.projectlombok</groupId>
              <artifactId>lombok</artifactId>
              <version>${lombok.version}</version>
            </path>
          </annotationProcessorPaths>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

Java 14+

The following record will generate the following methods for you:

public record Car(String make, String model, int year) {}
  • Constructor
    • Public constructor, with an argument for each field
  • Getters
    • These match the names of the fields (does not include the common "get" bean method prefix e.g. car.make() instead of car.getMake().
  • Common Object method overrides:
    • equals, hashCode, and toString

See also: Java 14 Record Keyword

Upvotes: 4

Rahul
Rahul

Reputation: 31

If a.equals(b) is true then there hashcode() must also be same.

Override both .equals() and .hashCode() in your custom class.

Use the same fields of your custom class to calculate hashCode which you used to check equality in .equals().

Yes it'll make sure that there are only unique instances of your customClass in hash-set. So go for it.

Upvotes: 0

Related Questions