I have two methods for looking up the index of a particular element in a linked list. I'm trying to use contais() and indexOf() to find the index. I am unsure how to override equals() to suit my need. One method finds the index based on surname and initials the other just the telephone number. Here are my methods:
#Override
public int lookupNumber(String surname, String initials) {
Entry entry1 = new Entry(surname, initials);
if (listDirectory.contains(entry1)) {
int index = listDirectory.indexOf(entry1);
return index;
}
else {
return -1;
}
}
#Override
public int lookupName(int extension) {
Entry entry1 = new Entry(Integer.toString(extension));
if (listDirectory.contains(entry1)) {
int index = listDirectory.indexOf(entry1);
return index;
}
else {
return -1;
}
}
I've tried to use the equals() and hashCode() that eclipse provides:
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Entry other = (Entry) obj;
if (extension == null) {
if (other.extension != null)
return false;
} else if (!extension.equals(other.extension))
return false;
if (initals == null) {
if (other.initals != null)
return false;
} else if (!initals.equals(other.initals))
return false;
if (surname == null) {
if (other.surname != null)
return false;
} else if (!surname.equals(other.surname))
return false;
return true;
}
But it won't work for both my methods, as it compares all the variables in the objects not just the ones I need it to compare. What is the correct logic for this?
Help much appreciated, thanks.
Update - here is my full Entry class -
public class Entry {
private String surname;
private String initals;
private String extension;
public Entry(String surname, String initals,String extension) {
this.surname = surname;
this.initals = initals;
this.extension = extension;
}
public Entry(String surname,String initals){
this.surname = surname;
this.initals = initals;
}
public Entry(String extension) {
this.extension = extension;
}
public String getInitals(){
return initals;
}
public String getSurname(){
return surname;
}
public String getExtension(){
return extension;
}
public void setExtension(String extension) {
this.extension = extension;
}
public String toString(){
return surname + "\t " + initals + "\t" + extension;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((extension == null) ? 0 : extension.hashCode());
result = prime * result + ((initals == null) ? 0 : initals.hashCode());
result = prime * result + ((surname == null) ? 0 : surname.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Entry other = (Entry) obj;
if (extension == null) {
if (other.extension != null)
return false;
} else if (!extension.equals(other.extension))
return false;
if (initals == null) {
if (other.initals != null)
return false;
} else if (!initals.equals(other.initals))
return false;
if (surname == null) {
if (other.surname != null)
return false;
} else if (!surname.equals(other.surname))
return false;
return true;
}
}
Update - My problem is in my lookupNumber() method I want to find elements by (initials,surname) whereas in my lookupNumber() I want to find elements by (extension). How would I do this?
The Java Object class contains an equals() method of its own, and the Entry class should be the blueprint for objects of type Entry. Java inheritance is such that all classes inherit from the Object class, which is of little use to many of us. Really all you would need is
#Override
public boolean equals(Entry otherEntry)
{
if(!(this.surname.equals(otherEntry.getSurname()))
return false;
else if(!(this.initials.equals(otherEntry.getInitials()))
return false;
else if(!(this.extension.equals(otherEntry.getExtension()))
return false;
else
return true;
}
The above code just checks for inequality amongst the instance variables, returning false upon any one not equal.
Related
I need to loop through an ArrayList and look for a particular "keys" HashMap and return the corresponding "params" HashMap as shown in the screenshot.
This is what I have so far but it's not working
private void getParam() {
List<Map<String, Object>> matrix = transactionInfoMatrix.getMatrixTransactionInfo();
MatrixTransactionInfoKeys key = new MatrixTransactionInfoKeys("OP/OP", "2777", "CT", "NBCTRANSFER", "AMT");
for (Map<String, Object> entry : matrix) {
if (entry.containsValue(key)) {
System.out.println("Found it");
}
}
}
Here is the MatrixTransactionInfoKeys class, but I have removed the getters and setters for the purposes of this post.
public class MatrixTransactionInfoKeys {
private String accountType;
private String applicationSourceCode;
private String operationType;
private String service;
private String transactionType;
public MatrixTransactionInfoKeys(String accountType, String applicationSourceCode, String operationType, String service, String transactionType) {
this.accountType = accountType;
this.applicationSourceCode = applicationSourceCode;
this.operationType = operationType;
this.service = service;
this.transactionType = transactionType;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((accountType == null) ? 0 : accountType.hashCode());
result = prime * result + ((applicationSourceCode == null) ? 0 : applicationSourceCode.hashCode());
result = prime * result + ((operationType == null) ? 0 : operationType.hashCode());
result = prime * result + ((service == null) ? 0 : service.hashCode());
result = prime * result + ((transactionType == null) ? 0 : transactionType.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
MatrixTransactionInfoKeys other = (MatrixTransactionInfoKeys) obj;
if (accountType == null) {
if (other.accountType != null) {
return false;
}
} else if (!accountType.equals(other.accountType)) {
return false;
}
if (applicationSourceCode == null) {
if (other.applicationSourceCode != null) {
return false;
}
} else if (!applicationSourceCode.equals(other.applicationSourceCode)) {
return false;
}
if (operationType == null) {
if (other.operationType != null) {
return false;
}
} else if (!operationType.equals(other.operationType)) {
return false;
}
if (service == null) {
if (other.service != null) {
return false;
}
} else if (!service.equals(other.service)) {
return false;
}
if (transactionType == null) {
return other.transactionType == null;
} else
return transactionType.equals(other.transactionType);
}
#Override
public String toString() {
return "MatrixTransactionInfoKeys [service=" + service + ", applicationSourceCode=" + applicationSourceCode
+ ", transactionType=" + transactionType + ", operationType=" + operationType + ", accountType=" + accountType + "]";
}
}
Remaking this answer, since I originally misinterpreted the question.
Based on the code supplied, assuming the MatrixTransactionInfoKeys in the map actually match and are created similarly, I get "Found it" in the output of this:
public class MatrixCheck {
public TransactionInfoMatrix transactionInfoMatrix;
private void getParam() {
List<Map<String, Object>> matrix = transactionInfoMatrix.getMatrixTransactionInfo();
MatrixTransactionInfoKeys key = new MatrixTransactionInfoKeys("OP/OP", "2777", "CT", "NBCTRANSFER", "AMT");
for (Map<String, Object> entry : matrix) {
if (entry.containsValue(key)) {
System.out.println("Found it");
}
}
}
public static void main( String[] args ) {
MatrixCheck matrixCheck = new MatrixCheck();
matrixCheck.transactionInfoMatrix = new TransactionInfoMatrix();
matrixCheck.transactionInfoMatrix.transactionInfo = new ArrayList<>();
matrixCheck.transactionInfoMatrix.transactionInfo.add( new HashMap<>() );
// Add the object to the map exactly as how it is
// constructed in the "getParam" portion
matrixCheck.transactionInfoMatrix.transactionInfo.get(0).put( "MyTest", new MatrixTransactionInfoKeys("OP/OP", "2777", "CT", "NBCTRANSFER", "AMT") );
matrixCheck.getParam();
}
static class TransactionInfoMatrix {
List<Map<String,Object>> transactionInfo;
public List<Map<String,Object>> getMatrixTransactionInfo() {
return transactionInfo;
}
}
static class MatrixTransactionInfoKeys {
private String accountType;
private String applicationSourceCode;
private String operationType;
private String service;
private String transactionType;
public MatrixTransactionInfoKeys(String accountType, String applicationSourceCode, String operationType, String service, String transactionType) {
this.accountType = accountType;
this.applicationSourceCode = applicationSourceCode;
this.operationType = operationType;
this.service = service;
this.transactionType = transactionType;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((accountType == null) ? 0 : accountType.hashCode());
result = prime * result + ((applicationSourceCode == null) ? 0 : applicationSourceCode.hashCode());
result = prime * result + ((operationType == null) ? 0 : operationType.hashCode());
result = prime * result + ((service == null) ? 0 : service.hashCode());
result = prime * result + ((transactionType == null) ? 0 : transactionType.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
MatrixTransactionInfoKeys other = (MatrixTransactionInfoKeys) obj;
if (accountType == null) {
if (other.accountType != null) {
return false;
}
} else if (!accountType.equals(other.accountType)) {
return false;
}
if (applicationSourceCode == null) {
if (other.applicationSourceCode != null) {
return false;
}
} else if (!applicationSourceCode.equals(other.applicationSourceCode)) {
return false;
}
if (operationType == null) {
if (other.operationType != null) {
return false;
}
} else if (!operationType.equals(other.operationType)) {
return false;
}
if (service == null) {
if (other.service != null) {
return false;
}
} else if (!service.equals(other.service)) {
return false;
}
if (transactionType == null) {
return other.transactionType == null;
} else
return transactionType.equals(other.transactionType);
}
#Override
public String toString() {
return "MatrixTransactionInfoKeys [service=" + service + ", applicationSourceCode=" + applicationSourceCode
+ ", transactionType=" + transactionType + ", operationType=" + operationType + ", accountType=" + accountType + "]";
}
}
}
I have 3 entities, student, grade and class. Code shows below. It is just a sample.
Student class
public class Student implements Serializable{
private static final long serialVersionUID = 1L;
private String fullName;
private long studentId;
//omit getter/setter column mapped to db
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((getFullName() == null) ? 0 : getFullName().hashCode());
result = prime * result + (int) (getStudentId() ^ (getStudentId() >>> 32));
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (obj instanceof Student)
return false;
test other = (test) obj;
if (getFullName() == null) {
if (other.getFullName() != null)
return false;
} else if (!getFullName().equals(other.getFullName()))
return false;
if (getStudentId() != other.getStudentId())
return false;
return true;
}
}
SchoolClass class:
public class SchoolClass implements Serializable{
private static final long serialVersionUID = 1L;
private String className;
private long classId;
//omit getter/setter column mapped to db
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (int) (getClassId() ^ (getClassId() >>> 32));
result = prime * result + ((getClassName() == null) ? 0 : getClassName().hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (obj instanceof SchoolClass)
return false;
SchoolClass other = (SchoolClass) obj;
if (getClassId() != other.getClassId())
return false;
if (getClassName() == null) {
if (other.getClassName() != null)
return false;
} else if (!getClassName().equals(other.getClassName()))
return false;
return true;
}
}
Grade Class:
public class Grade implements Serializable{
private static final long serialVersionUID = 1L;
private SchoolClass schoolClass;
private Student student;
//omit getter/setter column mapped to db
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((getSchoolClass() == null) ? 0 : getSchoolClass().hashCode());
result = prime * result + ((getStudent() == null) ? 0 : getStudent().hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (obj instanceof Grade)
return false;
Grade other = (Grade) obj;
if (getSchoolClass() == null) {
if (other.getSchoolClass() != null)
return false;
} else if (!getSchoolClass().equals(other.getSchoolClass()))
return false;
if (getStudent() == null) {
if (other.getStudent() != null)
return false;
} else if (!getStudent().equals(other.getStudent()))
return false;
return true;
}
}
So I checked hibernate doc for hashcode and equals, it works perfectly fine for entity that exists in DB. The problem I have is for new transient entity objects before save to db. I did separate tests specifically on Student and SchoolClass using HashSet, the size of set won't increase if it tries to add same object.
Student s1 = studentRepo.findById(studentId).get();
SchoolClass sc = scRepo.findById(classId).get();
Grade grade = new Grade();
grade.setStudent(s1);
grade.setSchoolClass(sc);
grades.add(grade);
logger.info(grades.size());
Here I have a new Set of grades and preparing this set and save to db. Here comes the problem, this set will contains duplicate grade object. Meaning there will be 2 entries with same student and same class. In grade class, I override its hashcode and equals to Student and SchoolClass, it should NOT have duplicate entries. I figure it probably because the new Grade object is in transient state? Not really sure what is the cause.
Of course I can do unique check for grade in an manual way, but hashcode and equals should be the right way to go, isn't it?
So how to solve this? Need some help.
Thanks to #samabcde. He is right, I missed ! on the condition check.
I have a HashMap where the key is a class and value is an integer. I need to check if an object of the class already exists in the map. I use containsKey(), but for some reason it does not work when I include attribute sideDish in the equals() and hashCode(). Here is my code for the classes:
OrderItem class:
#ToString
#Entity
#Table(name="OrderItem")
public class OrderItem implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Getter #Setter
private Long id;
#ManyToOne
#Getter #Setter
private Food food;
#ManyToMany
#Getter #Setter
private List<SideDish> sideDishes;
public OrderItem() {}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((food == null) ? 0 : food.hashCode());
result = prime * result + ((sideDishes == null) ? 0 : sideDishes.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
OrderItem other = (OrderItem) obj;
if (food == null) {
if (other.food != null)
return false;
} else if (!food.equals(other.food))
return false;
if (sideDishes == null) {
if (other.sideDishes != null)
return false;
} else if (!sideDishes.equals(other.sideDishes))
return false;
return true;
}
}
Food class:
#ToString
#Entity
#Table(name="Food")
public class Food implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Getter #Setter
private Long id;
#Column(nullable = false, unique = true)
#NotNull(message = "Name cannot be null.")
#Getter #Setter
private String name;
#ManyToMany
#Getter #Setter
private List<SideDish> sidedishes;
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((foodtype == null) ? 0 : foodtype.hashCode());
result = prime * result + ((id == null) ? 0 : id.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Food other = (Food) obj;
if (foodtype == null) {
if (other.foodtype != null)
return false;
} else if (!foodtype.equals(other.foodtype))
return false;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
SideDish class:
#Entity
#ToString(exclude= {"id","dishtype"})
#Table(name="SideDish")
public class SideDish implements Serializable, Comparable<SideDish>{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Getter #Setter
private Long id;
#Getter #Setter
#Column(nullable = false, unique = true)
private String name;
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
SideDish other = (SideDish) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
For some reason, if I removethe sideDish attribute from equals() and hashCode() in the OrderItem class, it works perfectly.
But I also need sideDish to be checked as part of the object identity.
Here is how I use it:
HashMap<OrderItem, Integer> orderItemsToSend = new HashMap<OrderItem, Integer>();
for (Order order : orders) {
for (OrderItem orderItem : order.getOrderItems()) {
int numSimilarOrders = getNumOfSimilarOrders(orderItem, orders);
if(!orderItemsToSend.containsKey(orderItem)) {
orderItemsToSend.put(orderItem, numSimilarOrders);
}else {
System.out.println("Vec je dodat item koji isti kao: " + orderItem.getFood().getName());
}
}
}
In your OrderItem class, both your hashCode() and equals() depend on the property List<SideDish> sideDishes.
Thus, if sideDishes changes, so does the hashCode() (and so does equality).
A HashMap uses both hashCode() and equals() to store and find the object which is the key. It uses a concept called "hash buckets". If you put a key into a HashMap, and then the hashCode() changes, that object will be in the wrong hash bucket, and you won't be able to find it again.
A key is something which is used for lookup purposes - that's what the word "key" means. An important quality of a key, whether in a database, or a hashmap, is immutability. So in Java, that means an object which changes its hashCode() makes for a bad key.
It's a bit like if a file system did lookups by the hash of the filename, but then you changed the filename, but it didn't update the hash. You'd only find the file by doing a lookup with the old name.
This simple test program will illustrate the point.
We store 2 objects in a HashMap, and then change the hashCode(). The map still contains both objects, but now one of them cannot be found or used for lookup.
The solution is use some simple immutable object as the key, such as a Long of its database ID.
Sample output is below the code.
public class HashTest {
static class Hashable {
String name;
#Override
public int hashCode() {
return ((name == null) ? 0 : name.hashCode());
}
#Override
public boolean equals(Object object) {
return (object instanceof Hashable) && equals((Hashable) object);
}
private boolean equals(Hashable that) {
return Objects.equals(this.name, that.name);
}
#Override
public String toString() {
// Use identityHashCode() so we can really see which object is which
return "[" + name + ":" + System.identityHashCode(this) + "]";
}
}
public static void main(String[] args) {
Hashable one = new Hashable();
one.name = "one";
Hashable two = new Hashable();
two.name = "one";
print(one, two);
two.name = "two";
print(one, two);
HashMap<Hashable, Integer> map = new HashMap<>();
map.put(one, 1);
map.put(two, 2);
find(map, one, two);
one.name = "two"; // Let's confuse things
print(one, two);
find(map, one, two);
}
private static void print(Hashable one, Hashable two) {
System.out.print("Names:" + one.name + ":" + two.name);
System.out.print("\tHashcodes:" + one.hashCode() + ":" + two.hashCode());
System.out.println("\tEquals:" + one.equals(two));
}
private static void find(HashMap<Hashable, Integer> map, Hashable one, Hashable two) {
System.out.print(map);
System.out.print("\tFound: " + map.get(one));
System.out.println("\tFound: " + map.get(two));
}
}
Sample output:
Names:one:one Hashcodes:110182:110182 Equals:true
Names:one:two Hashcodes:110182:115276 Equals:false
{[one:366712642]=1, [two:1829164700]=2} Found: 1 Found: 2
Names:two:two Hashcodes:115276:115276 Equals:true
{[two:366712642]=1, [two:1829164700]=2} Found: 2 Found: 2
I have a program which gets list of java object from db and compares it to the old list that have already been retrieved, and finds the delta (difference) elements in it and returns.
I am wondering if there is best way to do this rather than just using Set methods Union(), Intersection() etc., and avoiding out of memory errors?
Size of the list can be 200k.
I am using Spring 3.2.8.RELEASE version in my project.
public class Tester {
private List<AddressInfo> oldListOfAddresses;
#Scheduled(cron="0 1 6 * * ?") // 6 AM everyday
public Map<String, AddressInfo> getCompany() {
try {
Map<String, AddressInfo> companyMap = new HashMap<>();
String sql = "Some sql query which return Address Info.";
List<AddressInfo> newListOfAddresses = jdbcTemplate.query(sql, new Object[0],
new FacilityNewMapper());
if (newListOfAddresses == null || newListOfAddresses.size() = 0) {
throw new FacilityLookUpException("List of clinic Info from facilities is empty...");
} else {
// I have to find the delta of new list and old list here.
// I need an efficient (Space and Time) way of finding delta.
List<AddressInfo> deltaList = newListOfAddresses - oldListOfAddresses; //Something like this
for (AddressInfo comp : deltaList) {
if (comp != null) {
companyMap.put(comp.getLocationId(), comp);
}
}
oldListOfAddresses = newListOfAddresses;
}
return companyMap;
} catch (Exception e) {
throw new CompanyLookUpException(
"List of company addresses is empty..." + e.getMessage());
}
}
}
AddressInfo bean.
public class AddressInfo{
private String locationId;
private String streetName;
private String city;
private String state;
private String country;
public String getLocationId() {
return locationId;
}
public void setLocationId(String locationId) {
this.locationId = locationId;
}
public String getStreetName() {
return streetName;
}
public void setStreetName(String streetName) {
this.streetName = streetName;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((city == null) ? 0 : city.hashCode());
result = prime * result + ((country == null) ? 0 : country.hashCode());
result = prime * result + ((locationId == null) ? 0 : locationId.hashCode());
result = prime * result + ((state == null) ? 0 : state.hashCode());
result = prime * result + ((streetName == null) ? 0 : streetName.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
AddressInfo other = (AddressInfo) obj;
if (city == null) {
if (other.city != null)
return false;
} else if (!city.equals(other.city))
return false;
if (country == null) {
if (other.country != null)
return false;
} else if (!country.equals(other.country))
return false;
if (locationId == null) {
if (other.locationId != null)
return false;
} else if (!locationId.equals(other.locationId))
return false;
if (state == null) {
if (other.state != null)
return false;
} else if (!state.equals(other.state))
return false;
if (streetName == null) {
if (other.streetName != null)
return false;
} else if (!streetName.equals(other.streetName))
return false;
return true;
}
}
I don't think so (Note: I assume that the order of the list has no importance). For example, the fastest way to do this without using the set is to sort both of the lists which will cost you O(nlogn) and then iterate over them comparing each element and save those that don't have a pair. In case of Set, you will basically iterate over each element and look for it in the second set so the iteration is O(n) and the search is O(1). In the end we have O(nlogn) > O(n) the set wins
This should work fine for creating difference between two list.
Here I am creating a set and adding all elements of newList.
Then whichever elements are part of oldList I am removing them.
Set< AddressInfo > findDiffOfTwoList( List< AddressInfo > newList, List< AddressInfo > oldList) {
Set<AddressInfo> set = new HashSet<>();
set.addAll(newList);
for(AddressInfo address:oldList){
set.remove(address);
}
return set;
}
Assuming AddressInfo implements equals and hashCode properly, and items in each list are unique, the following function can find the delta in linear time:
Set< AddressInfo > findDiff(final List< AddressInfo > newListOfAddresses, final List< AddressInfo > oldListOfAddresses) {
Map< AddressInfo, Boolean > map = new HashMap<>(newListOfAddresses.size());
for ( AddressInfo addressInfo : newListOfAddresses ) {
map.put( addressInfo, TRUE );
}
for ( AddressInfo addressInfo : oldListOfAddresses ) {
map.remove( addressInfo );
}
return map.keySet();
}
I have newly started off with Java 8 and I'm trying out some examples on Collectors. I'm following the book, Java 8 in action. I have got a doubt regarding type inference :
I have used basic model classes Student, Name to create examples. PFB the details:
package com.learning.fundamentals;
import java.util.ArrayList;
import java.util.List;
public class Student {
public enum Gender {
MALE, FEMALE;
}
private String id;
private Name name;
private Gender gender;
public Student(String id, Name name, Gender gender) {
this.id = id;
this.name = name;
this.gender = gender;
}
public String getId() {
return id;
}
public Name getName() {
return name;
}
public Gender getGender() {
return gender;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((gender == null) ? 0 : gender.hashCode());
result = prime * result + ((id == null) ? 0 : id.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Student other = (Student) obj;
if (gender != other.gender)
return false;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
#Override
public String toString() {
return "Student [id=" + id + ", name=" + name + ", gender=" + gender
+ "]";
}
}
package com.learning.fundamentals;
public class Name {
private String firstName;
private String middleName;
private String lastName;
public Name(String firstName, String middleName, String lastName) {
this.firstName = firstName;
this.middleName = middleName;
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public String getMiddleName() {
return middleName;
}
public String getLastName() {
return lastName;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((firstName == null) ? 0 : firstName.hashCode());
result = prime * result
+ ((lastName == null) ? 0 : lastName.hashCode());
result = prime * result
+ ((middleName == null) ? 0 : middleName.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Name other = (Name) obj;
if (firstName == null) {
if (other.firstName != null)
return false;
} else if (!firstName.equals(other.firstName))
return false;
if (lastName == null) {
if (other.lastName != null)
return false;
} else if (!lastName.equals(other.lastName))
return false;
if (middleName == null) {
if (other.middleName != null)
return false;
} else if (!middleName.equals(other.middleName))
return false;
return true;
}
#Override
public String toString() {
return "Name [firstName=" + firstName + ", middleName=" + middleName
+ ", lastName=" + lastName + "]";
}
}
Now I have a list of Students like this
List<Student> studentList = getStudents(); //some method to create the list
Now, let's try out an grouping example. This is a multilevel grouping, first I'm grouping it using Gender and then some criteria on first name (not important). Here is the code :
Map<Student.Gender, Map<String, List<Student>>> studentsByGenderName =
studentList.stream()
.collect(Collectors.groupingBy(Student::getGender,
Collectors.groupingBy(std -> std.getName().getFirstName().substring(0, 4))));
This gives me an error at the second collector, stating "The method getName() is undefined for the type Object". Now, I was able to resolve the issue by providing the type of 'std' like this:
Map<Student.Gender, Map<String, List<Student>>> studentsByGenderName =
studentList.stream()
.collect(Collectors.groupingBy(Student::getGender,
Collectors.groupingBy((Student std) -> std.getName().getFirstName().substring(0, 4))));
My question is why can't java infer the type of the argument in lambda expression used in the second collector while it's able to do that for the first collector?