For example, I have a list of students with their semesters and subjects.
Subject Semester Attendee
---------------------------------
ITB001 1 John
ITB001 1 Bob
ITB001 1 Mickey
ITB001 2 Jenny
ITB001 2 James
MKB114 1 John
MKB114 1 Erica
When I need to group them by one value with Stream api, I can make something like that;
Map<String, List<Student>> studlistGrouped =
studlist.stream().collect(Collectors.groupingBy(w -> w.getSubject()));
and it does work. However, when I want to group them by two or more values (like the Sql handles), I cannot figure out how I need to do.
One approach is to group by a class that holds all the fields you want to group by. This class must implement hashCode and equals using all these fields in order to work as expected.
In your example this would be:
public class SubjectAndSemester {
private final String subject;
private final int semester; // an enum sounds better
public SubjectAndSemester(String subject, int semester) {
this.subject = subject;
this.semester = semester;
}
// TODO getters and setters, hashCode and equals using both fields
}
Then, you should create this method in your Student class:
public SubjectAndSemester bySubjectAndSemester() {
return new SubjectAndSemester(subject, semester);
}
Now, you can group as follows:
Map<SubjectAndSemester, List<Student>> studlistGrouped = studlist.stream()
.collect(Collectors.groupingBy(Student::bySubjectAndSemester));
This creates a one level grouping of students.
There's another option that doesn't require you to use a Tuple or to create a new class to group by. I mean you can have an n-level grouping of students, by using downstream groupingBy collectors:
Map<String, Map<Integer, List<Student>>> studlistGrouped = studlist.stream()
.collect(Collectors.groupingBy(Student::getSubject,
Collectors.groupingBy(Student::getSemester)));
This creates a 2-level grouping, so the returned structure is now a map of maps of lists. If you can live with this, even for grouping of more levels, then you can avoid creating a class that acts as the key of the map.
You could create a calculated field that is combination of the two columns
Map<String, List<Student>> studlistGrouped =
studlist.stream().collect(Collectors.groupingBy(w -> w.getSubject() + "-" w.getAttendee()));
EDIT:
Another more "appropriate" solution: Accoridng to this blog post , you need to define a Tuple class that can hold the two columns:
class Tuple {
String subject;
String attendee;
}
Map<String, List<Student>> studlistGrouped =
studlist.stream().collect(Collectors.groupingBy(w -> new Tuple(w.getSubject(), w.getAttendee())));
Related
I have the following domain classes Trip and Employee:
#Data
#NoArgsConstructor
#AllArgsConstructor
public class Trip {
private Date startTime;
private Date endTime;
List<Employee> empList;
}
#Data
#NoArgsConstructor
#AllArgsConstructor
public class Employee {
private String name;
private String empId;
}
I have a list of Trip instances. And I want to create a map of type Map<String,List<Trip>> associating id of each employee empId with a list of trips using Stream API.
Here's my attempt:
public static void main(String[] args) {
List<Trip> trips = new ArrayList<>();
Map<Stream<String>, List<Trip>> x = trips.stream()
.collect(Collectors.groupingBy(t -> t.getEmpList()
.stream().map(Employee::getEmpId)
));
}
How can I generate the map of the required type?
When the type of map is Map<String,List<Trip>> it gives me a compilation error:
Unresolved compilation problem: Type mismatch:
cannot convert from Map<Object,List<Trip>> to Map<String,List<Trip>>
To group the data by the property of a nested object and at the same time preserve a link to the enclosing object, you need to flatten the stream using an auxiliary object that would hold references to both employee id and enclosing Trip instance.
A Java 16 record would fit into this role perfectly well. If you're using an earlier JDK version, you can implement it a plain class (a quick and dirty way would be to use Map.Entry, but it decreases the readability, because of the faceless methods getKey() and getValue() require more effort to reason about the code). I will go with a record, because this option is the most convenient.
The following line is all we need (the rest would be automatically generated by the compiler):
public record TripEmployee(String empId, Trip trip) {}
The first step is to flatten the stream data and turn the Stream<Trip> into Stream<TripEmployee>. Since it's one-to-many transformation, we need to use flatMap() operation to turn each Employee instance into a TripEmployee.
And then we need to apply collect. In order to generate the resulting Map, we can make use of the collector groupingBy() with collector mapping() as a downstream. In collector mapping always requires a downstream collector and this case we need to provide toList().
List<Trip> trips = // initializing the list
Map<String, List<Trip>> empMap = trips.stream()
.flatMap(trip -> trip.getEmpList().stream()
.map(emp -> new TripEmployee(emp.getEmpId(), trip))
)
.collect(Collectors.groupingBy(
TripEmployee::empId,
Collectors.mapping(TripEmployee::trip,
Collectors.toList())
));
A Java 8 compliant solution is available via this Link
Not sure which Java version you are using but since you have mentioned Stream, I will assume Java 8 at least.
Second assumption, not sure why but looking at your code (using groupingBy ) you want the whole List<Trip> which you get against an empId in a Map.
To have the better understanding first look at this code (without Stream):
public Map<String, List<Trip>> doSomething(List<Trip> listTrip) {
List<Employee> employeeList = new ArrayList<>();
for (Trip trip : listTrip) {
employeeList.addAll(trip.getEmployee());
}
Map<String, List<Trip>> stringListMap = new HashMap<>();
for (Employee employee : employeeList) {
stringListMap.put(employee.getEmpId(), listTrip);
}
return stringListMap;
}
You can see I pulled an employeeList first , reason being your use case. And now you can see how easy was to create a map out of it.
You may use Set instead of List if you're worried about the duplicates.
So with StreamApi above code could be:
public Map<String, List<Trip>> doSomethingInStream(List<Trip> listTrip) {
List<Employee> employeeList = listTrip.stream().flatMap(e -> e.getEmployee().stream()).collect(Collectors.toList());
return employeeList.stream().collect(Collectors.toMap(Employee::getEmpId, employee -> listTrip));
}
You can take care of duplicates while creating map as well, as:
public Map<String, List<Trip>> doSomething3(List<Trip> listTrip) {
List<Employee> employeeList = listTrip.stream().flatMap(e -> e.getEmployee().stream()).collect(Collectors.toList());
return employeeList.stream().collect(Collectors.toMap(Employee::getEmpId, employee -> listTrip, (oldValue, newValue) -> newValue));
}
Like the first answer says, if you are Java 16+ using record will ease your task a lot in terms of model definition.
Using Java 8 stream
You can use the below approach to get the desired results using stream function groupingBy.
Since you have mentioned above to use java 8, so my solution is inclined to java 8 itself.
Logic:
Here,
First I have created an additional list of EmployeeTripMapping object
with Trip data corresponding to the empId by iterating the
listOfTrips.
I have used Collectors.groupingBy on the List<EmployeeTripMapping>
and grouped the data based on the empId and using Collectors.mapping
collect the list of Trip corresponding to the empId.
Few Suggestions:
Records in java 14 : As I can see in your problem statement, you
are using lombok
annotations to create getters, setters and constructors, so instead of
that we can replace our data classes
with records. Records are immutable classes that require only the type
and name of fields. We do not need to create constructor, getters,
setters, override toString() methods, override hashcode and equals
methods. Here
JavaTimeAPI in java 8: Instead of Date, you can use LocalDateTime available in java time API in java 8. Here
Code:
public class Test {
public static void main(String[] args) {
Trip t1 = new Trip(LocalDateTime.of(2022,10,28,9,00,00),
LocalDateTime.of(2022,10,28,18,00,00),
Arrays.asList(new Employee("emp1","id1")));
Trip t2 = new Trip(LocalDateTime.of(2021,10,28,9,00,00),
LocalDateTime.of(2021,10,28,18,00,00),
Arrays.asList(new Employee("emp1","id1")));
Trip t3 = new Trip(LocalDateTime.of(2020,10,28,9,00,00),
LocalDateTime.of(2020,10,28,18,00,00),
Arrays.asList(new Employee("emp2","id2")));
Trip t4 = new Trip(LocalDateTime.of(2019,10,28,9,00,00),
LocalDateTime.of(2019,10,28,18,00,00),
Arrays.asList(new Employee("emp2","id2")));
List<Trip> listOfTrips = Arrays.asList(t1,t2,t3,t4);
List<EmployeeTripMapping> empWithTripMapping = new ArrayList<>();
listOfTrips.forEach(x -> x.getEmpList().forEach(y ->
empWithTripMapping.add(new EmployeeTripMapping(y.getEmpId(),x))));
Map<String,List<Trip>> employeeTripGrouping = empWithTripMapping.stream()
.collect(Collectors.groupingBy(EmployeeTripMapping::getEmpId,
Collectors.mapping(EmployeeTripMapping::getTrip,
Collectors.toList())));
System.out.println(employeeTripGrouping);
}
}
EmployeeTripMapping.java
public class EmployeeTripMapping {
private String empId;
private Trip trip;
//getters and setters
}
Output:
{emp2=[Trip{startTime=2020-10-28T09:00, endTime=2020-10-28T18:00, empList=[Employee{empId='emp2', name='id2'}]}, Trip{startTime=2019-10-28T09:00, endTime=2019-10-28T18:00, empList=[Employee{empId='emp2', name='id2'}]}],
emp1=[Trip{startTime=2022-10-28T09:00, endTime=2022-10-28T18:00, empList=[Employee{empId='emp1', name='id1'}]}, Trip{startTime=2021-10-28T09:00, endTime=2021-10-28T18:00, empList=[Employee{empId='emp1', name='id1'}]}]}
I want to start from a collection of diploma projects and by using stream I want to get an arrayList of diploma project titles, from the students that have taken a course identified by courseId. They will also need to have passed the course with grade of 2 or higher.
I have this DiplomaProject class:
public class DiplomaProject{
String title;
ArrayList<Student> authors
}
Each diplomaProject can have multiple authors.
I have this Course class:
public class Course{
String courseName;
String courseId;
}
This Student class:
public class Student{
String name;
HashMap<Course, Integer> courseList;
DiplomaProject diplomaProject;
}
The grade of the course is the Integer value of courseList.
This is my current solution, but I know it does not do what I want. I can't find a way to filter based on the value of the courseList, and I do not know how I can get the the diplomaProject titles at the end of the streams (only at the top level).
public static List<String> diplomaProjectTitle(List<DiplomaProject> diplomaProjects) {
return diplomaProjects.stream()
.map(diplomaProject -> diplomaProject.authors)
.flatMap(students -> students.stream())
.filter(student -> student.courseList.keySet().equals("math1001"))
.flatMap(student -> student.courseList.keySet().stream())
.map(student -> student.courseName)
.collect(Collectors.toList());
You are losing the info on the diploma projects with the the .map functions. What you want to do is operate within the .filter() functions of the first diplomaproj stream.
Therefore
public List<String> diplomaProjectTitles(List<DiplomaProject> diplomaProjects) {
return diplomaProjects.stream()
.filter(projects -> projects.getAuthors().stream().map(Student::getCourseList)
//get the course matching this one (both name and id)
.map(c -> c.get(new Course("math101", "1")))
//check if that course has grade greater than the minimum
.anyMatch(grade -> grade>=2))
.map(DiplomaProject::getTitle)
.collect(Collectors.toList());
}
For this to work though you would need to modify your Course class. Since you are using it within a hash map as a key, and want to get it through a custom query you will need to add the hashCode() function.
public class Course {
private String courseName;
private String courseId;
#Override
public int hashCode() {
return courseName.hashCode() + courseId.hashCode();
}
#Override
public boolean equals(Object o) {
if(o instanceof Course oc) {
return oc.getCourseName().equals(this.getCourseName()) && oc.getCourseId().equals(this.getCourseId());
}
return false;
}
//getters and setters
}
In order to test it I created a simple method that prepares a test case
public void filterStudents() {
List<DiplomaProject> diplomaProjects = new ArrayList<>();
List<Course> courses = new ArrayList<>();
courses.add(new Course("math101", "1"));
courses.add(new Course("calc101", "2"));
courses.add(new Course("calc102", "3"));
List<Student> students = new ArrayList<>();
Map<Course, Integer> courseMap = Map.of(courses.get(0), 3, courses.get(1), 1);
students.add(new Student("TestSubj", courseMap));
Map<Course, Integer> courseMap2 = Map.of(courses.get(0), 1, courses.get(1), 3);
students.add(new Student("TestSubj2", courseMap2));
diplomaProjects.add(new DiplomaProject("Project1", students));
diplomaProjects.add(new DiplomaProject("Project2", List.of(students.get(1))));
log.info("Dimploma projects are " + diplomaProjectTitles(diplomaProjects));
}
this way Project 1 will have a student with math101 with grade 3 and one with grade 1, and Project2 will have a student with math101 with grade 1. As expected, the result of the filtering method is only project1
I want to get a List of diploma project titles, from the students that have taken a Course identified by the given courseId. They will also need to have passed the course with grade of 2 or higher.
In your method diplomaProjectTitle you're actually losing the access to the titles of the diploma projects at the very beginning of the stream pipe-line because the very first operation extracts authors from the stream element.
You need to need the stream to of type Stream<DiplomaProject> in order to get a list of diploma project titles as a result. Therefore, all the logic needed to filter the desired diploma project should reside in the filter() operation.
That's how it might be implemented:
public static List<String> diplomaProjectTitle(List<DiplomaProject> diplomaProjects,
String courseId,
Integer grade) {
return diplomaProjects.stream()
.filter(diplomaProject -> diplomaProject.getAuthors().stream()
.anyMatch(student ->
student.getCourseList().getOrDefault(courseId, 0) >= grade
)
)
.map(DiplomaProject::getTitle)
.toList(); // or .collect(Collectors.toList()) for JDK version earlier than 16
}
A couple of important notes:
Avoid using public fields and accessing the fields from outside the class directly rather than via getters.
Pay attention to the names of your method, variables, etc. The name courseList is confusing because it's actually not a List. This map should rather be named gradeByCourse to describe its purpose in a clear way.
Leverage abstractions - write your code against interfaces. See What does it mean to "program to an interface"?
Pay attention to the types you're working with keySet().equals("math1001") even IDE is capable to tell you that something is wrong here because Set can never be equal to a String.
A step-by-step way of thinking:
We need to filter projects based on the criteria that these have at least one author (student) who has passed a specific course (courseId) with a grade >= 2 (another filter).
dipProjects.stream().filter(p->p.getAuthors().stream().anyMatch(s->s.getCourseList().getOrDefault(courseId,0) >= grade)).map(p->p.getTitle()).collect(Collectors.toList());
I have a class Agent, which has following members:
class Agent{
String name;
long funds;
//... getters and setters, parameterized constructor
}
Now, I have a list of Agent class objects.
ArrayList<Agent> listAgents=new ArrayList<Agent>();
I want to give stars to the top performers, like 5 star for someone who has funds of more than 100000, 4 stars to someone with fund of 80000, and so on.
I want to store this record in Map<String,String> like
<ABC,*****> <PQR,****>
I tried the following code:
Map<String,String> star=listAgents
.stream()
.collect(Collectors.groupingBy(agn->giveStars(agn.getGeneratedFund())));
Here giveStars is function defined as below:
public static String giveStars(long generatedFund) {
if(generatedFund>=100000)
return "*****";
else if(generatedFund<100000&& generatedFund>=80000)
return "****";
else if(generatedFund<80000 && generatedFund>=60000)
return "***";
return "";
}
This didn't work. Also, I don't think this is the right way to do this.
How to do this using Stream API's functions?
Any help would be appreciated. Thanks in advance.
Assuming <ABC,*****> <PQR,****> means you have a map of agent name to its star rating, you don't want grouping, you want to map your existing items to different ones.
Here's one way how:
Map<String,String> star = listAgents.stream()
.collect(Collectors.toMap(Agent::getName, agn->giveStars(agn.getGeneratedFund())));
If you group items, you don't change them but assign to different groups (doh). Mapping is a process where you change an object to be something different.
First, there is an issue in the method giveStars which should be rewritten:
public static String giveStars(long generatedFund) {
if (generatedFund >= 1_000_000)
return "*****";
else if (generatedFund >=800_000)
return "****";
else if (generatedFund >= 600_000)
return "***";
return "";
}
Next, the code Collectors.groupingBy(agn->giveStars(agn.getGeneratedFund())) contradicts the intention to have a map Map<String, String> - it will create Map<String, List<Agent>> where the key is the star rating. So maybe your real intention is to change the type of the map.
Map<String, List<Agent>> groupByStars = listAgents.stream()
.collect(Collectors.groupingBy(agent -> giveStars(agent.getGeneratedFund())));
Such map will let to get top agents immediately.
If Map<String, String> (agent name to stars rating) is required, toMap collector may be used as suggested earlier, but this would work only if agent names are unique. If some duplicate values may occur, a merge function is required (e.g. to keep the agent with a better rating):
Map<String, String> groupByAgentName = listAgents.stream()
.collect(Collectors.toMap(
Agent::getName,
agent -> giveStars(agent.getGeneratedFund()),
(rating1, rating2) -> rating1.length() > rating2.length() ? rating1 : rating2,
LinkedHashMap::new // keep insertion order
));
I have a bunch of objects stored in hashMap<Long,Person> i need to find the person object with a specific attribute without knowing its ID.
for example the person class:
public person{
long id;
String firstName;
String lastName;
String userName;
String password;
String address;
..
(around 7-10 attributes in total)
}
lets say i want to find the object with username = "mike". Is there any method to find it without actually iterating on the whole hash map like this :
for (Map.Entry<Long,Person> entry : map.entrySet()) {
if(entry.getValue().getUserName().equalsIgnoreCase("mike"));
the answers i found here was pretty old.
If you want speed and are always looking for one specific attribute, your best bet is to create another 'cache' hash-map keyed with that attribute.
The memory taken up will be insignificant for less than a million entries and the hash-map lookup will be much much faster than any other solution.
Alternatively you could put all search attributes into a single map (ie. names, and ids). Prefix the keys with something unique if you're concerned with collisions. Something like:
String ID_PREFIX = "^!^ID^!^";
String USERNAME_PREFIX = "^!^USERNAME^!^";
String FIRSTNAME_PREFIX = "^!^FIRSTNAME^!^";
Map<String,Person> personMap = new HashMap<String,Person>();
//add a person
void addPersonToMap(Person person)
{
personMap.put(ID_PREFIX+person.id, person);
personMap.put(USERNAME_PREFIX+person.username, person);
personMap.put(FIRSTNAME_PREFIX+person.firstname, person);
}
//search person
Person findPersonByID(long id)
{
return personMap.get(ID_PREFIX+id);
}
Person findPersonByUsername(String username)
{
return personMap.get(USERNAME_PREFIX+username);
}
//or a more generic version:
//Person foundPerson = findPersonByAttribute(FIRSTNAME_PREFIX, "mike");
Person findPersonByAttribute(String attr, String attr_value)
{
return personMap.get(attr+attr_value);
}
The above assumes that each attribute is unique amongst all the Persons. This might be true for ID and username, but the question specifies firstname=mike which is unlikely to be unique.
In that case you want to abstract with a list, so it would be more like this:
Map<String,List<Person>> personMap = new HashMap<String,List<Person>>();
//add a person
void addPersonToMap(Person person)
{
insertPersonIntoMap(ID_PREFIX+person.id, person);
insertPersonIntoMap(USERNAME_PREFIX+person.username, person);
insertPersonIntoMap(FIRSTNAME_PREFIX+person.firstname, person);
}
//note that List contains no duplicates, so can be called multiple times for the same person.
void insertPersonIntoMap(String key, Person person)
{
List<Person> personsList = personMap.get(key);
if(personsList==null)
personsList = new ArrayList<Person>();
personsList.add(person);
personMap.put(key,personsList);
}
//we know id is unique, so we can just get the only person in the list
Person findPersonByID(long id)
{
List<Person> personList = personMap.get(ID_PREFIX+id);
if(personList!=null)
return personList.get(0);
return null;
}
//get list of persons with firstname
List<Person> findPersonsByFirstName(String firstname)
{
return personMap.get(FIRSTNAME_PREFIX+firstname);
}
At that point you're really getting into a grab-bag design but still very efficient if you're not expecting millions of entries.
The best performance-wise method I can think of is to have another HashMap, with the key being the attribute you want to search for, and the value being a list of objects.
For your example this would be HashMap<String, List<Person>>, with the key being the username. The downside is that you have to maintain two maps.
Note: I've used a List<Person> as the value because we cannot guarantee that username is unique among all users. The same applies for any other field.
For example, to add a Person to this new map you could do:
Map<String, List<Person>> peopleByUsername = new HashMap<>();
// ...
Person p = ...;
peopleByUsername.computeIfAbsent(
p.getUsername(),
k -> new ArrayList<>())
.add(p);
Then, to return all people whose username is i.e. joesmith:
List<Person> matching = peopleByUsername.get("joesmith");
Getting one or a few entries from a volatile map
If the map you're operating on can change often and you only want to get a few entries then iterating over the map's entries is ok since you'd need space and time to build other structures or sort the data as well.
Getting many entries from a volatile map
If you need to get many entries from that map you might get better performance by either sorting the entries first (e.g. build a list and sort that) and then using binary search. Alternatively you could build an intermediate map that uses the attribute(s) you need to search for as its key.
Note, however, that both approaches at least need time so this only yields better performance when you're looking for many entries.
Getting entries multiple times from a "persistent" map
If your map and its valuies doesn't change (or not that often) you could maintain a map attribute -> person. This would mean some effort for the initial setup and updating the additional map (unless your data doesn't change) as well as some memory overhead but speeds up lookups tremendously later on. This is a worthwhile approach when you'd do very little "writes" compared to how often you do lookups and if you can spare the memory overhead (depends on how big those maps would be and how much memory you have to spare).
Consider one hashmap per alternate key.
This will have "high" setup cost,
but will result in quick retrieval by alternate key.
Setup the hashmap using the Long key value.
Run through the hashmap Person objects and create a second hashmap (HashMap<String, Person>) for which username is the key.
Perhaps, fill both hashmaps at the same time.
In your case,
you will end up with something like HashMap<Long, Person> idKeyedMap and HashMap<String, Person> usernameKeyedMap.
You can also put all the key values in the same map,
if you define the map as Map<Object, Person>.
Then,
when you add the
(id, person) pair,
you need to also add the (username, person) pair.
Caveat, this is not a great technique.
What is the best way to solve the problem?
There are many ways to tackle this as you can see in the answers and comments.
How is the Map is being used (and perhaps how it is created). If the Map is built from a select statement with the long id value from a column from a table we might think we should use HashMap<Long, Person>.
Another way to look at the problem is to consider usernames should also be unique (i.e. no two persons should ever share the same username). So instead create the map as a HashMap<String, Person>. With username as the key and the Person object as the value.
Using the latter:
Map<String, Person> users = new HashMap<>();
users = retrieveUsersFromDatabase(); // perform db select and build map
String username = "mike";
users.get(username).
This will be the fastest way to retrieve the object you want to find in a Map containing Person objects as its values.
You can simply convert Hashmap to List using:
List list = new ArrayList(map.values());
Now, you can iterate through the list object easily. This way you can search Hashmap values on any property of Person class not just limiting to firstname.
Only downside is you will end up creating a list object. But using stream api you can further improve code to convert Hashmap to list and iterate in single operation saving space and improved performance with parallel streams.
Sorting and finding of value object can be done by designing and using an appropriate Comparator class.
Comparator Class : Designing a Comparator with respect to a specific attribute can be done as follows:
class UserComparator implements Comparator<Person>{
#Override
public int compare(Person p1, Person p2) {
return p1.userName.compareTo(p2.userName);
}
}
Usage : Comparator designed above can be used as follows:
HashMap<Long, Person> personMap = new HashMap<Long, Person>();
.
.
.
ArrayList<Person> pAL = new ArrayList<Person>(personMap.values()); //create list of values
Collections.sort(pAL,new UserComparator()); // sort the list using comparator
Person p = new Person(); // create a dummy object
p.userName="mike"; // Only set the username
int i= Collections.binarySearch(pAL,p,new UserComparator()); // search the list using comparator
if(i>=0){
Person p1 = pAL.get(Collections.binarySearch(pAL,p,new UserComparator())); //Obtain object if username is present
}else{
System.out.println("Insertion point: "+ i); // Returns a negative value if username is not present
}
I have for example these pairs:
key => name:
"name" => "myname1"
"height" => "myheight1"
"name" => "myname2"
"height" => "myheight2"
Now if I put all of these into Hashtable, then second name and height keys, will overwrite first key name and height key names. I could add it into two different Hashtables, but before runtime, I don't know how many such pairs will be, so I would not know the number Hashtables I would need. So How Can I add these paris into the lists (without knowing before runtime exact number of such pairs (for example some times there could be three names keys and two height keys with their values etc.).
So when I add all these values, I need in a list to appear like this:
list<list<Object>> = [[{name=myname1, height=myheight1}], [{name=myname2, height=myheight2}]]
And for example if there would be different number of such pairs like two name and three height pairs:
list<list<Object>> = [[{name=myname1, height=myheight1}], [{name=myname2, height=myheight2}], [{height=myheight3}]]
Note: I get these pairs from xml file, so they are somewhat grouped, like every iteration I get one group of such pairs. For example
<main>
<group>
<name>myname1</name>
<height>myheight1</height>
</group>
<group>
<name>myname2</name>
<height>myheight2</height>
</group>
</main>
So how could I properly add such pairs into list, so keys would not overwrite each other and later I could get any pair I want from the list?
Why not just use a List:
List<Group> groups = new ArrayList<Group>();
where Group is defined as
public class Group {
private final String name;
private final int height;
public Group(String name, int height) {
this.name = name;
this.height = height;
}
// getters are left as an exercise to the reader
}
(you may need to use another type than int depending on the representation of height).
EDIT
Another option would be to use a dynamic language such as Groovy which make such tasks much easier (as you don't have to keep track of your XML document structure). Here's an example:
http://groovyconsole.appspot.com/edit/1445001
Generally, you can use a Map<String,List<String>> (or a Set for the second parameter, if you don't care about order).
More specifically, if your data is structured, so should your code and classes be. In this case, consider creating a Group class to keep your (semi-)structured data:
public class Group {
String name , height;
// constructor, getters, setters
}
and stuff objects into a List<Group> (or Set as per above). To retrieve the pairs, you would just iterate over the List:
List<Group> groups = ....
for ( Group g : groups ) {
System.out.println( "name=>" + g.getName() + ", height=>" + g.getHeight() );
}
Cheers,
As some other answers have said, you probably need to have your 'value' type as a List.
class MyClass {
....
Map<String, List<String>> myMap = new HashMap<String, List<String>>();
....
public void addPair(final String key, final String value) {
if (myMap.containsKey(key)) {
myMap.get(key).add(value);
} else {
final List<String> newList = new ArrayList<String>();
newList.add(value);
myMap.put(key, newList);
}
}
}
I would prefer to go with new class say
Class Entry
{
String name;
String height
}
And now add entries to List<Entry>