0

I have one bean class Hotel in which data members are HotelName, HotelCity, HotelContact.

Now I want to save that data in Hashmap in a way where map Key should be City and all the hotel names should be which belongs to city should be in map values. I am able to do that using Collectors.groupby() . However I am not able to get the HotelName, instead I am getting Hotel object address. Please help where I am mistaking. Here is code which I attempted:

class Hotel {
    private String hotelName;
    private String hotelCity;
    private String hotelPhNo;
    /*their getter setter methods*/
}

public static void main(String[] args) {
            
    List<Hotel> ol = new ArrayList<Hotel>();
    Hotel h1 = new Hotel("H1","Pune","123");
    Hotel h2 = new Hotel("H2","Mumbai","109");
    Hotel h3 = new Hotel("H3","Pune","1123");
    Hotel h4 = new Hotel("H4","Mumbai","123");
    Hotel h5 = new Hotel("H5","Ujjain","123");
    Hotel h6 = new Hotel("H6","Indore","123");  
    Hotel h7 = new Hotel("H7","Sehore","123");  
    Hotel h8 = new Hotel("H8","Pune","123");    
    ol.add(h1);
    ol.add(h2);
    ol.add(h3);
    ol.add(h4);
    ol.add(h5);
    ol.add(h6);
    ol.add(h7);
    ol.add(h8);
        
    Map<String, List<Hotel>> res = ol.stream().collect(Collectors.groupingBy(Hotel:: getHotelCity));
            
    res.forEach((a,b)->System.out.println("City:"+a+" with Hotels: "+b));

}

With this I am getting output like:

City:Ujjain with Hotels: [Hotel@7ba4f24f]
City:Pune with Hotels: [Hotel@3b9a45b3, Hotel@7699a589, Hotel@58372a00]
City:Indore with Hotels: [Hotel@4dd8dc3]
City:Mumbai with Hotels: [Hotel@6d03e736, Hotel@568db2f2]
City:Sehore with Hotels: [Hotel@378bf509]

Desired output I want:

City:Ujjain with Hotels: [H5]
City:Pune with Hotels: [H1, H3, H8]
City:Indore with Hotels: [H6]
City:Mumbai with Hotels: [H4]
City:Sehore with Hotels: [H7]

Please help. I have a requirement where I don't want to alter bean class by overriding equals, hashcode and tostring method. Overriding these method would have been simpler to resolve this issue but looking for alternatives.

  • Alternatively to all the below, could you just update the `toString` method of your `Hotel` `class` to print the `hotelName`? – BeUndead Feb 19 '21 at 16:05
  • @BeUndead I already mentioned in question itself that overriding these methods would work but I don't want to alter bean class. – gaurav kulkarni Feb 19 '21 at 16:12
  • Technically you said 'overriding equals, hashcode _and_ tostring', I'm suggesting editing just _one_ of those. But fair enough if that's not for you. :) – BeUndead Feb 19 '21 at 16:17

3 Answers3

3

For such output, you need Map<String, List<String>> data strcture. To achieve that, use Collectors.mapping downstream collector within the Collectors.groupingBy.

Map<String, List<String>> res = ol.stream()
        .collect(Collectors.groupingBy(Hotel:: getHotelCity, 
                Collectors.mapping(Hotel::getHotelName, Collectors.toList())));

Note you need to provide additional downstream collector Collectors.toList() as long as Collectors.mapping needs it everytime.

The result:

res.forEach((a,b)->System.out.println("City:"+a+" with Hotels: "+b));
City:Ujjain with Hotels: [H5]
City:Pune with Hotels: [H1, H3, H8]
City:Indore with Hotels: [H6]
City:Mumbai with Hotels: [H2, H4]
City:Sehore with Hotels: [H7]

If you still want to keep the original Map<String, List<Hotel>> structure BUT print out the map values in a different way, you need to change the way you print out the result:

res.forEach((city, list) -> {
     String hotels = list.stream()
                         .map(Hotel::getHotelName)
                         .collect(Collectors.joining(", ", "[", "]"));
     System.out.println("City:" + city + " with Hotels: " + hotels);
});

Finally, few notes:

  • Name the class fields with a lower-case first letter: private String hotelName;

  • A List can be created in a shorter way:

    List<Hotel> ol = new ArrayList<>(Arrays.asList(h1, h2, h3, h4, h5, h6, h7, h8));
    
Nikolas Charalambidis
  • 40,893
  • 16
  • 117
  • 183
1

Problem here is b stands for List<Hotel>. To print the hotel names you should traverse the list b with b.forEach(...) then print each hotel information individually.

You can change the print statement like this:

res.forEach((a, b) -> {
    System.out.println("City:" + a + " with Hotels: ");
    b.forEach(h -> System.out.println("\t" + h.getHotelName()));
});

Output:

City:Ujjain with Hotels: 
    H5
City:Pune with Hotels: 
    H1
    H3
    H8
City:Indore with Hotels: 
    H6
City:Mumbai with Hotels: 
    H2
    H4
City:Sehore with Hotels: 
    H7
Hülya
  • 3,353
  • 2
  • 12
  • 19
0

You have 2 options. I think option #2 is by far superior, here.

  1. Instead of ending up with a Map<String /* City */, List<Hotel>>, end up with a Map<String /* City */, List<String /* Name of hotel */>>. During your stream op, map the 'value' part using a downstream mapper: Collectors.mapping(Hotel::getHotelName, Collectors.toList()).

  2. Keep the data structure exactly the same, but have your hotels print useful things when printed; they currently render as Hotel@7ba4f24f, because your Hotel class lacks a toString implementation and that's a bad state of affairs. To fix it, simply add one: Add a @Override public String toString() { ... } method to your Hotel.java file, which prints whatever you want to print. Perhaps as simple as: return this.hotelName;.

NB: To make such things easy, you could look into using Project Lombok. Then all you'd need to write is:

@lombok.Data public class Hotel {
    String name, city, phoneNumber;
}

and the getters, setters, but also equals, hashCode, and toString are all just silently there, with sensible implementations.

DISCLAIMER: I contribute to Project Lombok.

rzwitserloot
  • 85,357
  • 5
  • 51
  • 72
  • I appreciate your suggestion but I have a different requirement where I don't want to edit my bean class in any manner. So I can not use any overridden method over there in Hotel. I already highlighted that in my question. – gaurav kulkarni Feb 21 '21 at 15:41