i have a problem with groupingBy method.
I use an example:
Suppose to have a List<Person> where Person.class and City.class are like this
class Person{
String name;
String surname;
City city;
}
class City{
String name;
String postalCode;
}
I would group the list by city.postalcode and do somthing like this:
List<Person> myList = ...some persons here...
Map<City,List<Person>> groupMap = myList.stream().collect(
Collectors.groupingBy(person -> person));
Obviously this not work because groupingBy don't know how to group by an object.
I would do something like this Collectors.groupingBy(person -> person.city.postalcode) but with this produces a map of type Map<String,List<Person>> and not Map<City,List<Person>>
Thanks for any help.
UPDATE
For use groupingBy on a class this must simply implement correctly equals and hashcode
Collectors.groupingBy(person -> person)
Collectors.groupingBy(person -> person.city.postalcode)
You want to group by city? Don't group by person, or by postal code. Group by the city, exactly as you've stated.
Collectors.groupingBy(person -> person.city)
As you state in your question, City should override equals and hashCode.
class City {
String name;
String postalCode;
#Override public boolean equals(Object obj) {
if (!(obj instanceof City)) {
return false;
}
City that = (City) obj;
return Objects.equals(this.name, that.name)
&& Objects.equals(this.postalCode, that.postalCode)
}
#Override public int hashCode() {
return Objects.hash(name, postalCode);
}
}
Related
I'd like group by a field a select
Guess this class:
public class Person {
private Name name;
private Address address;
...
}
public class Name {
private String name;
private String fatherName;
private String motherName;
}
I'd like to group by Person.getName().getMotherName() and select Person.getName() as grouping object key.
Collection<Person> persons = new ...
Map<Name, List<Person>> group = persons.stream()
.collect(groupingby(p -> p.getName().getMotherName());
As you can see, I'd like to get a Map<Name, List<Person>>, instead of a Map<String, List<Person>>
Any ideas?
As you can't override equals() I can think of this way:
Create a wrapper around Name which overrides equals() and hashCode() for Name.getMotherName():
public class NameWrapper {
private final Name name;
public NameWrapper(Name name) {
this.name = name;
}
#Override
public int hashcode() {
return name.getMotherName().hashCode();
}
#Override
public boolean equals(Object obj) {
if(obj == this) return true;
if(!(obj instanceof NameWrapper)) return false;
NameWrapper other = (NameWrapper) obj;
return name.getMotherName().equals(other.name.getMotherName());
}
}
Then you can map the Person#getName() to a NameWrapper and group by that:
Map<NameWrapper, List<Person>> group = persons.stream()
.collect(Collectors.groupingBy(p -> new NameWrapper(p.getName())));
You can use :
Map<Name, List<Person>> group = persons.stream()
.collect(Collectors.groupingBy(p -> p.getName()));
And you have to Overide the hashCode and equald in your Name class.
#Override
public boolean equals(Object o) {
// your equals code
}
#Override
public int hashCode() {
// your hash code
}
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;
}
I want to know how to check if two objects of the same class have the same values in each attribute.
For example:
public class Person {
String name;
String surname;
String country;
int age;
public Person(String name, String surname, String country, int age) {
this.name = name;
this.surname = surname;
this.country = country;
this.age = age;
}
public boolean samePerson(Person person){
//CODE
}
Person person1 = new Person("Abel", "Smith", "EEUU", 26);
Person person2 = new Person("Alexa", "Williams", "Canada", 30);
Person person3 = new Person("Abel", "Smith", "EEUU", 26);
Person person4 = new Person("Alexa", "Williams", "EEUU", 30)
person1.samePerson(person2) // return false
person1.samePerson(person3) // return true
person2.samePerson(person3) // return false
person2.samePerson(person4) // return false
The only thing I can think of is to compare the attributes one to one. Is there a simpler way?
Sorry for my english
Thanks in advance
The only thing I can think of is to compare the attributes one to one. Is there a simpler way?
Unfortunately not. You'll have to write code to do just that. And if you do, consider putting that code in equals and hashCode methods.
There is no simpler way. You have to implement your own way of doing this because it is a class you made yourself.
You are off to a good start. You can use your samePerson() method to provide this
functionality, or, like #Thilo said, use the equals and hashCode methods.
It should go along the lines of:
public boolean samePerson(Person person){
return this.name.equals(person.name) &&
this.surname.equals(person.surname) &&
this.country.equals(person.country) &&
this.age == person.age;
}
With perhaps some sanity null checking and whatever else you require.
The correct answer is using equals() and hashCode() but if you are looking for something else, you could consider introducing a id for each person - maybe a social security number. Then the comparsion of persons could be implemented in the public boolean isSamePersonAs(Person person) and compare only that.
You need to specify how the object must be compared
and to have it compared properly implement hashcode method.
code is as below, add this in your class and you wil get desired o/p.
#Override
public int hashCode() {
int prime=31;
int sum = prime*this.name.hashCode();
sum=sum+this.surname.hashCode();
sum=sum+this.country.hashCode();
sum=sum+this.age;
return sum;
}
#Override
public boolean samePerson(Object p) {
if(p==null)
return false;
if(! (p instanceof Person))
return false;
Person person = (Person)p;
return this.name.equals(person.name) && this.surname.equals(person.surname) && this.country.equals(person.country) && this.age == person.age;
}
Folks try this one i guess it will work for you
***********This is class one "Person"*********
public class Person {
String name;
String surname;
String country;
int age;
public Person(String name, String surname, String country, int age) {
this.name = name;
this.surname = surname;
this.country = country;
this.age = age;
}
}
********This is main class*******
public class mainclass {
public static void main(String arg[])
{
Person person1 = new Person("Abel", "Smith", "EEUU", 26);
Person person2 = new Person("Alexa", "Williams", "Canada", 30);
Person person3 = new Person("Abel", "Smith", "EEUU", 26);
Person person4 = new Person("Alexa", "Williams", "EEUU", 30);
System.out.println(samePerson(person1,person2));
System.out.println(samePerson(person1,person3));
System.out.println(samePerson(person2,person4));
System.out.println(samePerson(person3,person4));
System.out.println(samePerson(person1,person4));
}
static boolean samePerson(Person personA,Person personB)
{
if(personA.name.equals(personB.name) && personA.country.equals(personB.country)&& personA.surname.equals(personB.surname )&& personA.age==personB.age )
return true;
else
return false;
}
}
Thanks and Regards.
Usually you would use getter for each field. You should use a generic way here and call all the getter via reflection on each instance, after that compare them with equals.
#Edit
See here: Java Reflection: How can i get the all getter methods of a java class and invoke them
and here: http://tutorials.jenkov.com/java-reflection/getters-setters.html
how to get a specific java object from java.util.List<Object> using equals method
exemple :
Class Person{
String name;
int age;
//...
#Override
public boolean equals(String str) {
return (this.name.equals(str.name));
}
}
main{
List<Person> list = personDao.findAll();
if(list.contain("joe")){
// how to get the Person named joe ????
}
}
If you want to get a reference to the specific Person in the List:
Person foo = null;
for (Person p : list) {
if (p.getName().equals("joe")) {
foo = p;
break;
}
}
// now foo is your person (if he exists) or null (if he doesnt exist);
This will only find the first Person named joe.
The signature of your Person.equals() method is incorrect... argument type should always be "Object", like this:
#Override
public boolean equals(Object obj) {
return (obj instanceof Person) && ((Person) obj).name.equals(this.name);
}
Then jlordoless's suggestion of using list.contains(new Person("name") will work.
Simply iterate. There's no method on the List interface that returns an object from the list.
Person joe = null;
List<Person> persons = personDao.findAll();
for (Person thisPerson : persons) {
if ("joe".equals(thisPerson.getName())) {
joe = thisPerson;
break;
}
}
This will set Person joe to the first person named joe. If you're looking for the last one in the collection, remove the break statement.
I think that you have a design flaw here. What happens if there are 2 joe-s?
If you don't have duplicate names you can create a getter for the name in Person:
public String getName() {
return name;
}
and after that you can do this:
Person joe = null;
for(Person person : list) {
if("joe".equals(person.getName()) {
joe = person;
break;
}
}
This way you avoid creating unnecessary instances of Person.
You should not use equals like that. Overriding equals is somewhat complex, you have to verride hashcode too, for example. A list-based solution with minimal changes:
Class Person{
String name;
int age;
//...
public String getName() {
return name;
}
}
main {
List<Person> list = personDao.findAll();
Person foundPerson = null;
for(Person person : list) {
if (person.getName().equals("joe")) {
foundPerson = person;
break;
}
}
if (foundPerson != null) {
// do something
}
}
However, I would consider using a Map<String, Person>, with name as key. Overriding equals in Person while ignoring age does not sound very good, really.
#Hyde: Can not answer on your answer. Overriding "hashCode" is required if and only if Hashing is used. If person wants to use HashMap or Hashing related other APIs than only he must override hashCode and equals... Otherwise "equals" works fine.
I'm looking for a Collection type data structure to implement the following. Say I have a class like this:
class Person() {
String homeTown; // key
String sex; // key
String eyeColour; // key
String name;
long height;
// other stuff....
}
I am processing multiple Person objects. I want to organise them into sets whereby each set contains Person objects with the same homeTown, sex and eyeColour. At the moment I am implementing something like this:
Map<String, HashSet<Person>> = new HashMap<String, HashSet<Person>>;
where the key is a concatanation of the homeTown, sex and eyeColour. This works but seems a bit untidy - can anyone suggest a more elegant solution or a better data structure to use, thanks?
You could restructure your class to make the key explicit. This is more robust than simply concatenating the key values and avoids any additional object creation overhead at the point when you wish to store your Person instance in a Map, because you've eagerly created the key in advance.
public class Person {
public class Key {
private final String homeTown;
private final String sex;
private final String eyeColour;
public Key(String homeTown, String sex, String eyeColour) { ... }
public boolean equals(Object o) { /* Override to perform deep equals. */ }
public int hashCode() { /* Could pre-compute in advance if the key elements never change. */ }
}
private final Key key;
private final String name;
private final long height;
public Person(String homeTown, String sex, String eyeColour, String name, long height) {
this.key = new Key(homeTown, sex, eyeColour);
this.name = name;
this.height = height;
}
public Key getKey() {
return key;
}
public String getName() { return name; }
public long getHeight() { return height; }
}
Create an object to model your key. For example class PersonKey { String homeTown, sex, eyeColour } (getters and setters omitted for brevity)
Implement the equals and hashCode method for this object.
Use this object as the key in your Map.
Either remove the attributes from your Person object or replace them with a reference to your PersonKey object.
In addition, consider making the type of your map the following i.e. you don't need to specify what type of Set you are using as the key to your map.
Map<String, Set<Person>> = new HashMap<String, Set<Person>>();
And, if you are using a Set<Person> then you'll need to override equals and hashCode for the Person as well, otherwise the Set cannot correctly determine if two Person objects represent the same person or not, which is needed to make sure the collection contains only unique elements.
org.apache.commons.collections.map.MultiValueMap
You can use guava's Sets.filter method to filter the person objects.
Example:
Person class:
public class Person {
String name;
String hometown;
int age;
public Person(String name, String hometown, int age) {
this.name = name;
this.age = age;
this.hometown = hometown;
}
#Override
public int hashCode() {
int hash = 17;
hash = 37 * hash + name.hashCode();
hash = 37 * hash + hometown.hashCode();
hash = 37 * hash + age;
return hash;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
Person p;
if (obj instanceof Person)
p = (Person) obj;
else
return false;
if (this.name.equals(p.name) && this.hometown.equals(p.hometown)
&& this.age == p.age)
return true;
return false;
}
#Override
public String toString() {
StringBuilder b = new StringBuilder();
b.append("[name = ").append(name).append("\n");
b.append("home town = ").append(hometown).append("\n");
b.append("age = ").append(age).append("]");
return b.toString();
}
}
TestGuavaFilter class:
public class TestGuavaFilter {
public static void main(String[] args) {
Set<Person> set = new HashSet<Person>();
set.add(new Person("emil", "NY", 24));
set.add(new Person("Sam", "NY", 50));
set.add(new Person("george", "LA", 90));
System.out.println(Sets.filter(set, new FilterHomeTown("NY")));
}
}
class FilterHomeTown implements Predicate<Person> {
String home;
public FilterHomeTown(String home) {
this.home = home;
}
#Override
public boolean apply(Person arg0) {
if (arg0.hometown.equals(this.home))
return true;
return false;
}
}
The advantage of using a filter is that you can filter Person object in any way ,suppose you want to filter only using home-town and not the other 2 attributes this will helpful.More over since guava's filter only produces a view of the the real Set,you can save memory.