I have a base BusinessObject abstract class which implements Comparable by comparing their long id fields. Now imagine I extend it with, say, Person and then I extend Person with Worker. So we have:
BusinessObject < Person < Worker
So now I override the compareTo(BusinessObject) in business object in Person (compare the names) and Worker (compares the job name, then person name).
Now I do something like:
List<BusinessObject> collection = new ArrayList<>();
collection.add(new Worker(1L, "Steve", "janitor"));
collection.add(new Worker(2L, "Mark", "plumber"));
collection.add(new Person(3L, "Dave"));
Collections.sort(collection);
System.out.println(collection);
By logging I can see the calls that are made:
Worker.compareTo()
Person.compareTo()
So, it means sorting methods are mixed which is obviously not good. So what is the correct way to implement Comparable with inheritance so that the method called depends on the generic type of the collection:
if collection is a List then always use BusinessObject.compareTo()
if collection is a List then always use Person.compareTo()
if collection is a List then always use Worker.compareTo()
Here's my code:
public abstract class BusinessObject implements HasId, HasText, Comparable<BusinessObject> {
protected #Nullable Long id;
public BusinessObject(#Nullable Long id) {
this.id = id;
}
#Override
public #Nullable Long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
#Override
public int compareTo(#NonNull BusinessObject o) {
System.out.println("compareTo BusinessObject");
return Long.compare(id, o.id);
}
#Override
public String toString() {
return String.format("BusinessObject#%d", id);
}
}
public class Person extends BusinessObject {
protected final String name;
public Person(#Nullable Long id, String name) {
super(id);
this.name = name;
}
#NonNull
#Override
public String getText(Context context) {
return null;
}
#Override
public String toString() {
return String.format("Person#%d (%s)", id, name);
}
#Override
public int compareTo(#NonNull BusinessObject o) {
if (o instanceof Person) {
System.out.println("compareTo Person");
Person po = (Person) o;
return name.compareTo(po.name);
}
else {
return super.compareTo(o);
}
}
}
public class Worker extends Person {
protected final String job;
public Worker(#Nullable Long id, String name, String job) {
super(id, name);
this.job = job;
}
#Override
public String toString() {
return String.format("Worker#%d (%s-%s)", id, name, job);
}
#Override
public int compareTo(#NonNull BusinessObject o) {
if (o instanceof Worker) {
System.out.println("compareTo Worker");
Worker wo = (Worker) o;
return String.format("%s%s", name, job).compareTo(String.format("%s%s", wo.name, wo.job));
}
else {
return super.compareTo(o);
}
}
}
Your design is inherently flawed.
First, you have BusinessObject which implements Comparable<BusinessObject>. What this says is that business objects have a natural order, dictated by their ID.
Then you override the method in Person to compare the name. What you are saying here is that "well, if both business objects being compared are persons, then the natural order is different for them".
That doesn't make sense. Either sorting by ID is natural for business objects, or it isn't. But it can't be natural for some of them, but not for others.
More formally, your override breaks the transitivity requirement of Comparable. Imagine you have a list consisting of three business objects: Worker Alice (ID 3), Worker Bob (ID 1), and Company ACME (ID 2). Transitivity says that if x < y && y < z then x < z must also be true. However, your compare method will give Bob < ACME (compare by IDs) and ACME < Alice (compare by IDs), but the transitive Bob < Alice is false (compare by names). You have violated the requirement documented in the compareTo method:
The implementor must also ensure that the relation is transitive: (x.compareTo(y)>0 && y.compareTo(z)>0) implies x.compareTo(z)>0.
Bottom line: Comparable is not meant to work with open inheritance. Don't do that. Instead, use explicit Comparators for your collections where they make sense.
As a side note, and this may just be due to shortened code in your examples, but you should always override equals to be consistent with compareTo (i.e. x.equals(y) implies x.compareTo(y) == 0 and vice versa). Anything else means your objects behave in an unintuitive manner. (And then you need to override hashCode to fulfill its own contract w.r.t. equals. Overriding equals but not hashCode is a wonderful way to introduce really subtle bugs into Java programs.)
I think it is simply not possible to do, what you want to do.
The generic type does and can not have any effect on the methods used from the items.
But you might be able to solve your problem. Just do not use the compareTo methods but instead implement Comparators as needed.
There is a version of Collections.sort() which takes a Comparator as parameter.
Obviously you want to sort the items with different sort criterias depending on the list you are using. So I think the Comparator would be the best solution.
Related
I am trying to generate a HashSet containing unique Employee instances. Uniqueness should be established based on the object properties.
The problem is that I end up with having duplicates.
Note that Employee class is provided by a framework, and it's not possible to provide custom implementations for equals() and hashCode().
Employee class:
public class Employee {
private long employeeId;
private String name;
// getters, setters
#Override
public String toString() {
return "Employee{" +
"employeeId=" + employeeId +
", name='" + name + '\'' +
'}';
}
}
Map<String, Set<Employee>> ackErrorMap = new HashMap<>();
Employee emp = new Employee(1,"bon");
Employee emp2 = new Employee(1,"bon");
ackErrorMap.computeIfAbsent("key1",
x -> new HashSet<>()).add(emp);
ackErrorMap.computeIfAbsent("key1",
x -> new HashSet<>()).add(emp2);
This would result in a Set mapped to the Key "key1" containing both emp and emp2, although object attributes are same.
Is there any way to discard Employee with the same name? Or would it better to use ArrayList?
Possible example using Arraylist
ackErrorMap.computeIfAbsent("key1",
x -> new ArrayList<>()).add(emp);
You can override the equals and hashCode methods in the Employee class. The equals method should return true if two objects are considered equal, and the hashCode method should return the same value for two objects that are considered equal.
class Employee {
private int id;
private String name;
public Employee(int id, String name) {
this.id = id;
this.name = name;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return id == employee.id &&
Objects.equals(name, employee.name);
}
#Override
public int hashCode() {
return Objects.hash(id, name);
}
}
With these changes, when you add emp and emp2 to the HashSet, only one of them will be added, because they will be considered equal based on the equals method.
You can create a custom type wrapping the class coming from the framework and implement the equals/hashCode contract according to your requirements.
That's how such wrapper might look like (for the purpose of conciseness I'm using a Java 16 record, but it can be implemented as a class as well).
public record EmployeeWrapper(Employee employee) {
#Override
public boolean equals(Object o) {
return o instanceof EmployeeWrapper other
&& employee.getName().equals(other.employee.getName());
}
#Override
public int hashCode() {
return Objects.hash(employee.getName());
}
}
And you can use with a Map of type Map<String,Set<EmployeeWrapper>> to ensure uniqueness of the Employee based on the name property.
I would also advise to make one step further and encapsulate the Map into a class which would expose the methods covering all scenarios of interaction with the Map (like add new entry, get employees by key, etc.), so that your client would not dial wrappers, but only with employees and wrapping and unwrapping would happen within the enclosing class.
Here's how it might be implemented:
public class AcrErrors {
private Map<String, Set<EmployeeWrapper>> ackErrorMap = new HashMap<>();
public void addEmployee(String key, Employee employee) {
EmployeeWrapper wrapper = new EmployeeWrapper(employee);
ackErrorMap
.computeIfAbsent(key, x -> new HashSet<>())
.add(wrapper);
}
public List<Employee> getEmployees(String key) {
return ackErrorMap.getOrDefault(key, Collections.emptySet()).stream()
.map(EmployeeWrapper::employee)
.toList();
}
// other methods
}
You need to override equals() and hashCode() inside Employee class.
Or you can use lombok’s #EqualsAndHashCode annotation in your Employee class.
I have a custom class as Student which has two different equals methods (equals, equals1) and have two collections of Student object as list1 and list2. When I do use list1.retainAll(list2), it needs to be used equals1 mechanism instead of eqauls method.
Is this possible or Do we have any API to mention the equals method name when we do have multiple equals mechanism in our customer class.
Example:
class Student {
public String name;
public Student(String name) {
this.name = name;
}
public boolean equals(Object obj) {
return super.equals(obj);
}
public boolean equals1(Object obj) {
if(obj == null) return false;
if(!(obj instanceof Student)) return false;
Student student = (Student) obj;
if(this.name.equals(student.name)) {
return true;
} else {
return false;
}
}
}
public class Demo {
public static void main(String[] args) {
List<Student> list1 = new ArrayList<Student>();
list1.add(new Student("AAA"));
list1.add(new Student("BCD"));
List<Student> list2 = new ArrayList<Student>();
list2.add(new Student("AAA"));
list2.add(new Student("CDE"));
// Intersection of list1 and list2
list1.retainAll(list2);
for (Student student : list1) {
System.out.println(student.name);
}
}
}
The expected result is [AAA] in case if equals1 method is used but in this case, the default equals method is getting executed so that the result is empty.
How do we use custom equals method for collection algorithm.
equals() is special
remember that the equals() is special because the whole collections API relies on it.
Furthermore collections API (at least anything with "Hash" in its name like HashSet or HashTable) relies on the relationship between equals() and hashcode(): The deal is when ever equals() returns true both objects return the same value from hashcode(). On top of that the value retuned by hashcode() must not change during an objects lifetime.
Your implementation has the potential to break this rules forcing the collections API zu fail because your equals1() method uses the mutable field name.
You can create equals wrapper that wrap you entity and your equals
its will be something like :
public class EqulasWrapper<T extends CustomHashCode> {
T entity;
BiFunction<T, T, Boolean> customEquals;
public EqulasWrapper(T entityClass, BiFunction<T, T, Boolean> func){
this.entity = entityClass;
this.customEquals = func;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
EqulasWrapper<T> that = (EqulasWrapper<T>) o;
return this.customEquals.apply(this.entity, that.entity);
}
#Override
public int hashCode() {
return entity.getCustomHashCode();
}
public static class Person implements CustomHashCode {
private int age;
private String name;
public Person(int age, String name) {
this.age = age;
this.name = name;
}
#Override
public int getCustomHashCode() {
return age;
}
}
public T getEntity() {
return entity;
}
public BiFunction<T, T, Boolean> getCustomEquals() {
return customEquals;
}
public static void main(String[] args) {
Person person = new Person(10, "Dan");
BiFunction<Person, Person, Boolean> myCustomEqualsFunc =
(p1, p2) -> p1.age == p2.age;
EqulasWrapper<Person> personEqulasWrapper = new EqulasWrapper<>(person, myCustomEqualsFunc);
Set<EqulasWrapper> mySet = new HashSet<>();
mySet.add(personEqulasWrapper);
}
The CustomHashCode interface look like:
public interface CustomHashCode {
int getCustomHashCode();
}
short explanation
your set will use your custom Hash code that your entity implement and the custom equals your give him.
so you can create different collection with type EqulasWrapper class
and design your hash code and your equals function has you wish, the collection instead of using original hash code and equals will use with your wrapper
Hope I helped a bit
if you have any questions I would love to hear.
I'm wondering if there is some design pattern to help me with this problem.
Let's say I have a class Person which has three attributes: name, nickname and speaksEnglish and an Enum PersonType with TypeOne, TypeTwo and TypeThree.
Let's say if a Person has nickname and speaksEnglish it's a TypeOne. If it has nickame but doesn't speaksEnglish, it's a TypeTwo. If it does not have nickame, so it's TypeThree.
My first thought would have a method with some if-else and returning the related Enum. In the future I can have more attributes in Person and other types of PersonType to decide.
So, my first thought was create a method with a bunch of if (...) { return <PersonType> } or switch-case, but I was wondering if there is some design pattern I can use instead of ifs and switch-case.
I will recomend you to use just simple inheritance with immutable objects.
So, at first you have to create abstract class:
public abstract class AbstractPerson {
private final String name;
private final Optional<String> nickname;
private final boolean speaksEnglish;
private final PersonType personType;
protected AbstractPerson(final String name, final Optional<String> nickname, final boolean speaksEnglish, final PersonType personType) {
this.name = name;
this.nickname = nickname;
this.speaksEnglish = speaksEnglish;
this.personType = personType;
}
public String getName() {
return name;
}
public Optional<String> getNickname() {
return nickname;
}
public boolean getSpeaksEnglish() {
return speaksEnglish;
}
public PersonType getPersonType() {
return personType;
}
}
With PersonType enum:
public enum PersonType {
TypeOne, TypeTwo, TypeThree;
}
Now, we have three options with corresponding constructors in child classes:
public final class EnglishSpeakingPerson extends AbstractPerson {
public EnglishSpeakingPerson(final String name, final String nickname) {
super(name, Optional.of(nickname), true, PersonType.TypeOne);
}
}
public final class Person extends AbstractPerson {
public Person(final String name, final String nickname) {
super(name, Optional.of(nickname), false, PersonType.TypeTwo);
}
public Person(final String name) {
super(name, Optional.empty(), false, PersonType.TypeThree);
}
}
In this case, our concrete classes are immutable and its type is defined in moment of creation. You don't need to create if-else ladders - if you want to create new type, just create new class/constructor.
I don't think Type can really be an attribute of a Person. I am not against #ByeBye's answer but with that implementation you will still end up changing Person class when there are new types introduced.
X type of person is ultimately a person itself. Say a Manager or Developer are both employees of a company, so it makes a lot of sense to have them as specialized classes that derive from an Employee. Similarly in your case, having person type as an attribute and then doing all if-else stuff clearly violates SOLID.
I would instead have specific implementations of Person class and mark itself as an abstract one.
public abstract class Person {
public Person(string name) {
Name = name;
}
public abstract string Name { get; set; }
public abstract string NickName { get; set; }
public abstract bool SpeaksEnglish { get; set; }
}
public class TypeOnePerson : Person {
public TypeOnePerson(string name, string nickName) : base(name) {
NickName = nickName; // Validate empty/ null
}
SpeaksEnglish = true;
}
public class TypeTwoPerson : Person {
public TypeOnePerson(string name, string nickName) : base(name) {
NickName = nickName; // Validate empty/ null
}
SpeaksEnglish = false;
}
I also think that this question is language-agnostic, it is a pure design question. So please bear with me as the code above is in C#. That doesn't matter, however.
As far as OO principles are considered why to create object with combinations of optional attributes? If its question of one or two then Optional approach will remain maintainable, but type will be based on many combinations (in future code will be full of Boolean Algebra) and question also says "...In the future I can have more attributes in Person and other types of PersonType to decide.".
I would suggest approach of using Decorator pattern, which allows us to create customized objects with complete code reuse. Person will be Component and Optional attributes (they are types e.g NickName with validation as behavior) will be concrete decorators.
Any addition to Person and adding new Concrete Decorator type remain two separate concerns. Decorator Pattern is a best candidate for this kind of requirement. Its Intent from GOF book (by Erich gamma) pattern Catalog says - "Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality". [though for very small number of expected extensions earlier answers make more sense.]
I haven't overridden much of hashCode() and equals() methods so I may be wrong
My question is for the last line where
dep1.equals(emp2) is being compiled successfully(why) (I am expecting compilation error as they have different types) and after compiling I get following
15 15 false
where I am expecting 15 15 true since I am checking the hashcode in the equals method.
class Employee {
private String name;
private int id;
public Employee(String name, int id) {
this.name = name;
this.id = id;
}
public int hashCode() {
return this.id;
}
public boolean equals(Employee employee) {
return this.hashCode() == employee.hashCode();
}
public int getEmployeeId() {
return this.id;
}
}
class Department {
private String name;
private int id;
public Department(String name, int id) {
this.name = name;
this.id = id;
}
public int hashCode() {
return this.id;
}
public boolean equals(Department department) {
return this.hashCode() == department.hashCode();
}
public int getDepartmentId() {
return this.id;
}
}
public class JavaCollections {
public static void main(String args[]) {
Employee emp2 = new Employee("Second Employee", 15);
Department dep1 = new Department("Department One", 15);
System.out.println(dep1.hashCode()+" "+emp2.hashCode()+" " + dep1.equals(emp2));
}
}
First, for the reason why this compiles: all classes in Java inherit from java.lang.Object, which defines equals(Object) method, and provides a default implementation. This is the method that you call when you compare an Employee and a Department, not one of the overloads that you have provided.
Your equals code compiles fine, because the compiler does not know that you thought you were overriding equals when you actually didn't. The compiler thinks that you want to make a new method
public boolean equals(Department department)
to compare Department objects to other Department objects.
If you are writing a code that overrides a method of a superclass, add #Override annotation to it, like this:
#Override
public boolean equals(Department department)
Now the compiler will correctly complain to you that your method does not in fact override a method in its base class, alerting you to the problem at compile time.
To fix your code change the signatures of equals to take Object, add #Override, check for null and for the correct type, do the cast, and then do the actual comparison:
#Override
public boolean equals(Department obj) {
if (obj == null || !(obj instanceof Department)) {
return false;
}
Department dept = (Department)obj
return dept.id == id;
}
Note: Implementing equals like this
return this.hashCode() == department.hashCode();
is very fragile. Although it works in your case, when hash code is a unique ID of the object, this wouldn't survive a code refactoring when hashCode is replaced with some other implementation, for example, an implementation that considers both id and name. If you want to rely on comparing IDs, compare IDs directly, without calling hashCode to get them.
That's because both of classes Employee and Department still have not overriden methods public boolean equals(Object obj) inherited from Object class.
Exactly this method is invoked in dep1.equals(emp2), not public boolean equals(Department department).
More specifically, read JLS:
An instance method mC declared in or inherited by class C, overrides from C another method mA declared in class A, iff all of the following are true:
...
The signature of mC is a subsignature (§8.4.2) of the signature of mA.
In this case boolean equals(Department department) is not subsignature of boolean equals(Object obj).
First, this code dep1.equals(emp2) calls default implementation of Object class.
Second, U didnt overrides the default implementation in both of your class becoz u cant override equal method for specific customizied types.
If u need ur answer to be 15 15 true
replace
public boolean equals(Department department) {
return this.hashCode() == department.hashCode();
}
by
#override
public boolean equals(Object department) {
return this.hashCode() == department.hashCode();
}
In the collection test, I create a class named Name and override the equals method, like following,
class Name implements Comparable<Name>{
private String firstName, lastName;
Name(String firstName, String lastName){
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName(){
return firstName;
}
public String getLastName(){
return lastName;
}
public String toString(){
return firstName + " "+lastName;
}
public boolean equals(Name name){
return firstName.equals(name.firstName) && lastName.equals(name.lastName);
}
public int hashCode(){
return firstName.hashCode();
}
But When I test the remove() function in collection, it was false and the Name("Andy","Light") is still in the collection. What is the wrong with my code?
public static void main(String[] args){
Collection c = new HashSet();
c.add("hello");
c.add(new Name("Andy","Light"));
c.add(new Integer(100));
c.remove("hello");
c.remove(new Integer(100));
System.out.println(c.remove(new Name("Andy","Light")));
System.out.println(c);
}
There is a comment and an answer that says your hashCode() method is not consistent with equals() because you didn't include lastName in the hash code calculation. They are both wrong.
The hashCode() implementation is allowed to use a subset of the values used by equals(). It will cause more hashcode collisions that way, offsetting improved speed of hashCode() vs degraded performance of hash-buckets. A subset hashcode may be ok, it depends on likelihood of Name objects having same firstName.
Your problem is that the signature of equals() is wrong. It has to be boolean equals(Object).
boolean equals(Name) is not an override of boolean equals(Object), so you didn't actually override/implement the equals() method, and as such ended up with hashCode() being inconsistent with equals() (but not for the reason the others said).
If you add the #Override annotation, the compiler would have caught this problem. Always use the annotation.
Change to:
#Override
public boolean equals(Object obj) {
if (! (obj instanceOf Name))
return false;
Name that = (Name)obj;
return this.firstName.equals(that.firstName) && this.lastName.equals(that.lastName);
}
#Override
public int hashCode() {
return this.firstName.hashCode();
}
This of course assumes that neither can be null.
As #MickMnemonic says in a comment:
It's considered bad practice to leave out fields that are included in equals()
To include lastName in the calculation, use Objects.hash():
#Override
public int hashCode() {
return Objects.hash(this.firstName, this.lastName);
}
Also, as #StephenB said in a comment:
You also need to add a compareTo method because you are implementing Comparable<Name>.
Here you use Name as a parameter, not Object, because of the generic type argument to Comparable.
Example (if sorting by first name before last name):
#Override
public int compareTo(Name that) {
int cmp = this.firstName.compareTo(that.firstName);
if (cmp == 0)
cmp = this.lastName.compareTo(that.lastName);
return cmp;
}
That implements a firstName/lastName lexicographical ordering. You may want to use compareToIgnoreCase() or maybe a Collator for correct localized ordering.