Can we use custom equals method for Collection Algorithm - java

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.

Related

Add unique elements in HashSet based on attributes of framework provided non-editable object classes

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.

TreeMap returns null while using map.get(Object) [duplicate]

This question already has answers here:
TreeMap.get() return Null even key exists
(5 answers)
Closed 5 years ago.
TreeMap prints value as null while fetching value using get method whereas it is working fine with HashMap(). Please find below the sample code and provide inputs to this.
It's working fine for Hashmap as it uses equals()/hashcode() methods whereas TreeMap are SortedMap, it doesn't use equals method to compare two objects. Instead, it uses comparator/comparable to compare the objects but while fetching the object using get method, it's giving null as response. Please provide some clarity here.
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
class Employees implements Comparable<Employees>, Comparator<Employees> {
public Employees(String name, int id) {
super();
this.name = name;
this.id = id;
}
private String name;
private int id;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof Employees))
return false;
Employees other = (Employees) obj;
if (id != other.id)
return false;
return true;
}
#Override
public int hashCode() {
return 1;
}
#Override
public int compareTo(Employees o) {
return 1;
}
#Override
public int compare(Employees o1, Employees o2) {
return 1;
}
}
public class Employee {
public static void main(String[] args) {
// Map<Employees, Integer> m = new HashMap<>(); // equals will be used here.
Map<Employees, Integer> m = new TreeMap<Employees, Integer>(); // no equals/hashcode used here as we use comparator/comparable to compare objects.
Employees e1 = new Employees("abc", 11);
Employees e2 = new Employees("abc", 12);
System.out.println(m.put(e1, 1));
System.out.println(m.put(e2, 2));
**System.out.println(m.get(e2));**
}
}
Your compareTo method always returns 1, which means no Employees object is equal to any other Employees object (not even to itself) as far as compareTo is concerned.
Only when compareTo returns 0 the two compared instances are considered identical by TreeMap.
BTW, your hashCode implementation of always returning 1 is also bad. While it works (for HashMap), it causes all the entries to be stored in the same bucket, which destroys the performance of the HashMap.

Find element matching in 2 lists using java 8 stream

My case is:
class Person {
String id ;
String name;
String age;
}
List<Person> list1 = {p1,p2, p3};
List<Person> list2 = {p4,p5, p6};
I want to know if there is person in list1 that has the same name and age in list2 but don't mind about id.
What is best and fast way?
Define yourself a key object that holds and compares the desired properties. In this simple case, you may use a small list whereas each index corresponds to one property. For more complex cases, you may use a Map (using property names as keys) or a dedicated class:
Function<Person,List<Object>> toKey=p -> Arrays.asList(p.getName(), p.getAge());
Having such a mapping function. you may use the simple solution:
list1.stream().map(toKey)
.flatMap(key -> list2.stream().map(toKey).filter(key::equals))
.forEach(key -> System.out.println("{name="+key.get(0)+", age="+key.get(1)+"}"));
which may lead to poor performance when you have rather large lists. When you have large lists (or can’t predict their sizes), you should use an intermediate Set to accelerate the lookup (changing the task’s time complexity from O(n²) to O(n)):
list2.stream().map(toKey)
.filter(list1.stream().map(toKey).collect(Collectors.toSet())::contains)
.forEach(key -> System.out.println("{name="+key.get(0)+", age="+key.get(1)+"}"));
In the examples above, each match gets printed. If you are only interested in whether such a match exists, you may use either:
boolean exists=list1.stream().map(toKey)
.anyMatch(key -> list2.stream().map(toKey).anyMatch(key::equals));
or
boolean exists=list2.stream().map(toKey)
.anyMatch(list1.stream().map(toKey).collect(Collectors.toSet())::contains);
A simple way to do that is to override equals and hashCode. Since I assume the equality between Person must also consider the id field, you can wrap this instance into a PersonWrapper which will implement the correct equals and hashCode (i.e. only check the name and age fields):
class PersonWrapper {
private Person person;
private PersonWrapper(Person person) {
this.person = person;
}
public static PersonWrapper wrap(Person person) {
return new PersonWrapper(person);
}
public Person unwrap() {
return person;
}
#Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
PersonWrapper other = (PersonWrapper) obj;
return person.name.equals(other.person.name) && person.age.equals(other.person.age);
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + person.name.hashCode();
result = prime * result + person.age.hashCode();
return result;
}
}
With such a class, you can then have the following:
Set<PersonWrapper> set2 = list2.stream().map(PersonWrapper::wrap).collect(toSet());
boolean exists =
list1.stream()
.map(PersonWrapper::wrap)
.filter(set2::contains)
.findFirst()
.isPresent();
System.out.println(exists);
This code converts the list2 into a Set of wrapped persons. The goal of having a Set is to have a constant-time contains operation for better performance.
Then, the list1 is filtered. Every element found in set2 is kept and if there is an element left (that is to say, if findFirst() returns a non empty Optional), it means an element was found.
Brute force, but pure java 8 solution will be this:
boolean present = list1
.stream()
.flatMap(x -> list2
.stream()
.filter(y -> x.getName().equals(y.getName()))
.filter(y -> x.getAge().equals(y.getAge()))
.limit(1))
.findFirst()
.isPresent();
Here, flatmap is used to join 2 lists. limit is used as we are interested in first match only, in which case, we do not need to traverse further.
Well if you don't care about the id field, then you can use the equals method to solve this.
Here's the Person class code
public class Person {
private String id ;
private String name;
private String age;
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person sample = (Person) o;
if (!name.equals(sample.name)) return false;
return age.equals(sample.age);
}
#Override
public int hashCode() {
int result = name.hashCode();
result = 31 * result + age.hashCode();
return result;
}
}
Now, you can use stream to get the intersection like so. common will contain all Person objects where name and age are the same.
List<Person> common = list1
.stream()
.filter(list2::contains)
.collect(Collectors.toList());
<h3>Find List of Object passing String of Array Using java 8?</h3>
[Faiz Akram][1]
<pre>
public class Student {
private String name;
private Integer age;
public Student(String name, Integer age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
</pre>
// Main Class
<pre>
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class JavaLamda {
public static void main(String[] k)
{
List<Student> stud = new ArrayList<Student>();
stud.add(new Student("Faiz", 1));
stud.add(new Student("Dubai", 2));
stud.add(new Student("Akram", 5));
stud.add(new Student("Rahul", 3));
String[] name= {"Faiz", "Akram"};
List<Student> present = Arrays.asList(name)
.stream()
.flatMap(x -> stud
.stream()
.filter(y -> x.equalsIgnoreCase(y.getName())))
.collect(Collectors.toList());
System.out.println(present);
}
}
</pre>
OutPut //[Student#404b9385, Student#6d311334]
[1]: http://faizakram.com/blog/find-list-object-passing-string-array-using-java-8/
This would work:
class PresentOrNot { boolean isPresent = false; };
final PresentOrNot isPresent = new PresentOrNot ();
l1.stream().forEach(p -> {
isPresent.isPresent = isPresent.isPresent || l2.stream()
.filter(p1 -> p.name.equals(p1.name) && p.age.equals(p1.age))
.findFirst()
.isPresent();
});
System.err.println(isPresent.isPresent);
Since forEach() takes Consumer, we have no way of returning and PresentOrNot {} is a workaround.
Aside : Where did you get such a requirement ? :)
You need to iterate over the two lists and compare the atributtes.
for(Person person1 : list1) {
for(Person person2 : list2) {
if(person1.getName().equals(person2.getName()) &&
person1.getAge().equals(person2.getAge())) {
//your code
}
}
}
public static void main(String[] args) {
OTSQuestions ots = new OTSQuestions();
List<Attr> attrs = ots.getAttrs();
List<String> ids = new ArrayList<>();
ids.add("101");
ids.add("104");
ids.add("102");
List<Attr> finalList = attrs.stream().filter(
attr -> ids.contains(attr.getId()))
.collect(Collectors.toList());
}
public class Attr {
private String id;
private String name;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
private List<Attr> getAttrs() {
List<Attr> attrs = new ArrayList<>();
Attr attr = new Attr();
attr.setId("100");
attr.setName("Yoga");
attrs.add(attr);
Attr attr1 = new Attr();
attr1.setId("101");
attr1.setName("Yoga1");
attrs.add(attr1);
Attr attr2 = new Attr();
attr2.setId("102");
attr2.setName("Yoga2");
attrs.add(attr2);
Attr attr3 = new Attr();
attr3.setId("103");
attr3.setName("Yoga3");
attrs.add(attr3);
Attr attr4 = new Attr();
attr4.setId("104");
attr4.setName("Yoga4");
attrs.add(attr4);
return attrs;
}

Java Efficient Way to sort a collection of bean

I have a list of bean coming from the db as a result of querying.
The bean is as follows:
public class Employee implements Comparator<Employee> {
protected String empId; //alphanumeric e.g.abc123
protected String empFullName;
protected String empAddress;
protected String dept;
protected String project;
public String getEmpId() {
return empId;
}
public void setEmpId(String empId) {
this.empId = empId;
}
public String getEmpFullName() {
return empFullName;
}
public void setEmpFullName(String empFullName) {
this.empFullName = empFullName;
}
public String getEmpAddress() {
return empAddress;
}
public void setEmpAddress(String empAddress) {
this.empAddress = empAddress;
}
public String getDept() {
return dept;
}
public void setDept(String dept) {
this.dept = dept;
}
public String getProject() {
return project;
}
public void setProject(String project) {
this.project = project;
}
#Override
public int compare(Employee e1, Employee e2) {
if (e1 == null && e2 == null) return 0;
if (e1 != null && e2 == null) return -1;
if (e1 == null && e2 != null) return 1;
return e1.empId.compareTo (e2.empId);
}
}
I have comparator which sorts by empId which is alphanumeric.
I want to know what is the best way to sort it by empId,dept,project.
In the code if I do as follows, it sorts by empId.
List<Employee> empList = someDao.getEmpList();
Collections.sort(empList, new Employee());
Any suggestions.
This is an odd declaration in itself:
public class Employee implements Comparator<Employee>
It would be much more common to have:
public class Employee implements Comparable<Employee>
and then maybe:
public class EmployeeByIdComparator implements Comparator<Employee>
and
public class EmployeeByNameComparator implements Comparator<Employee>
etc.
An instance of a class implementing Comparable knows how to compare itself with another instance; typically this is reserved for a "natural" ordering. Compare that with an instance of Comparator, which knows how to compare two instances usually of a different type (the one specified as the type argument for Comparator).
So if you want multiple types of comparison, create multiple comparators. You may then want to chain them together - Guava provides an easy way of doing this. (See ComparisonChain.) For convenience, you may want to implement the comparators as private static classes within Employee, then expose single instances of them via public static final fields:
public class Employee
{
public static final Comparator<Employee> BY_ID_COMPARATOR
= new ByIdComparator();
private static final class ByIdComparator : Comparator<Employee>
{
...
}
}
public class Employee implements Comparator<Employee>
This is a wrong (probably not intended) use of the Comparator interface. I think you want Comparable here.
For sorting by empId, dept and project create a custom comparator that does just that and pass it to the sort() method. This comparator would then just check the properties you want to compare by in the order you need and return the result when it is not 0 for the first time - or at the end if all properties are equal.
Example:
new Comparator<Employee>() {
public int compare(Employee e1, Employee e2) {
//check if both are not null
int result = e1.empId.compareTo( e2.empId );
if( result == 0) {
result = e1.dept .compareTo( e2.dept );
}
...
return result;
}
}

Java: After adding 2 identical objects to a Set, it contains the 2 elements

After adding two identical objects to a Set, I would expect the set to contain only one element.
public void addIdenticalObjectsToSet(){
Set<Foo> set = new HashSet<Foo>();
set.add(new Foo("totoro"));
set.add(new Foo("totoro"));
Assert.assertEquals(1, set.size()); // PROBLEM: SIZE=2
}
private class Foo {
private String id;
public Foo(String id) {
this.id = id;
}
public String getId() {
return id;
}
public boolean equals(Object obj) {
return obj!= null && obj instanceof Foo &&
((Foo)obj).getId().equals(this.getId());
}
public int hashcode() {
return this.getId().hashCode();
}
}
I consider two objects as identical if they have the same id (String).
Other strange thing: Neither Foo.equals nor Foo.hashcode are accessed, as far as I can tell using debug/breakpoints. What am I missing?
public int hashcode() {
return this.getId().hashCode();
}
should be
#Override
public int hashCode() {
return this.getId().hashCode();
}
The annotation would have told you about the spelling mistake.
There should also be a (missing) little triangle symbol in your IDE on the method to indicate if an interface is being implemented or a parent method overridden.

Categories