Instance of class as key in hashmap - java

I have this class:
public class Offer {
private Integer id;
private String description;
private Double price;
private String currency;
private Date timeExpired;
public Offer(Integer id, String description, Double price, String currency, Date timeExpired){
this.id = id;
this.description = description;
this.price = price;
this.currency = currency;
this.timeExpired = timeExpired;
}
}
I want to create a hashmap with key that refers to id of the class Offer and value as Offer.
HashMap<id of Offer(?),Offer> repo = new HashMap<id of Offer(?),Offer>();
How can I do that?
How assign each Offer id as key and the Offer objects as values on Hashmap repo?
I mean method repo.put(?)

Because the id is an Integer your need a HashMap<Integer, Offer>:
public static void main(String[]args){
HashMap<Integer, Offer> map = new HashMap<Integer, Offer>();
// First way
map.put(1038, new Offer(1038, "foo", 10.20, "bar", new Date()));
// Second way
Offer o1 = new Offer(1038, "foo", 10.20, "bar", new Date());
map.put(o1.getId(), o1);
}
Tips :
use int and double rather than Integer or Double if you don't really need the objects (int vs Integer)
use LocalDate instead of Date it's the latest version, and easier to use

Related

Adding an object to a List of another type

I'm trying to return the record that I got from my database. But I'm having a problem on how I can do that because the data than I retrieved from the database is in a different class from the return parameter.
public List<Record> getRecord(List<Request> requests) {
List<Record> records = new ArrayList<>();
for (Request request : requests) {
Billing billing = billingRepository
.findByBillingCycleAndStartDateAndEndDate(
request.getBillingCycle()
, request.getStartDate()
, request.getEndDate());
if (billing != null) {
// Need to add "billing" to "records" list here
}
}
return records;
}
Record.class
public class Record {
private int billingCycle;
private LocalDate startDate;
private LocalDate endDate;
private String accountName;
private String firstName;
private String lastname;
private double amount;
public Record() {
}
//Getters and setters
Billing.class
public class Billing {
private int billingId;
private int billingCycle;
private String billingMonth;
private Double amount;
private LocalDate startDate;
private LocalDate endDate;
private String lastEdited;
private Account accountId;
public Billing() {
}
//Getters and setters
What can I do? and please explain the answer so I can understand it. I really want to learn
You can use DozerMapper. It will map the object to another object having same name properties or you have to write the mapping in the dozer-mapping xml.
Lets come to your question. Here you are trying to convert your entity to another object.
For that you have to write mapping code. It will be something like this and it is very common practice to convert entity objects to another object before using them.
Record toRecord(Billing billing) {
if(billing == null) {
return null;
}
Record record = new Record();
record.setBillingCycle = billing.getBillingCycle();
...
...
// other properties
...
return record;
}

Direct self-reference leading to cycle

I'm trying to send a request to get back an array of an object - Coupon when I submit the request I get the answer-
Direct self-reference leading to cycle (through reference chain:
java.util.HashSet[0] => model.Coupon["emptyCoupon"] => model.Coupon["emptyCoupon"])
The model.Coupon probably does the problem.
empty coupon is intended to be returned if the requested coupon does not exist.
public static final int NO_ID = -1;
private static final Coupon EMPTY_COUPON = new Coupon(NO_ID, null, null, null, NO_ID, NO_ID, null, NO_ID, null);
private long id = NO_ID;
private String title;
private LocalDate startDate;
private LocalDate endDate;
private int amount;
private int category;
private String message;
private double price;
private String image;
public Coupon() {
}
private Coupon(long id, String title, LocalDate start_date, LocalDate end_date, int amount, int category,
String message, double price, String image) {
this.id = id;
this.title = title;
this.startDate = start_date;
this.endDate = end_date;
this.amount = amount;
this.category = category;
this.message = message;
this.price = price;
this.image = image;
}
public Coupon getEmptyCoupon() {
return EMPTY_COUPON;
}
Before I added the EMPTY_COUPON I had no problems with the requests.
I want the emptyCoupon in the code, and I'll be happy to help
Since you are serializing to JSON or XML with Jersey, you may not have cycles in your object graph.
Jersey doesn't have a #JsonBackReference like Jackson does, so you might consider to move the EMPTY_COUPON in a separate class (something like Constants.java) and obtain it from there.
Other options are to add #XmlIgnore to your field or to switch to another JSON serializer like Jackson.

How to collect properties of List<Map> by unique property using MultiMap?

I have List of stories. Using unique property(id) I want to collect keyword and targeting as list of values. Can I do this with MultiMap? Or is there other library for this?
[{
id = 1,
title = Onboarding,
keyword = new joinee,
targeting = finance
}, {
id = 1,
title = Onboarding,
keyword = training,
targeting = HR
}]
The Desired output must like this :
{
id = 1,
title = Onboarding,
keyword = [new joinee,training], //may be keywords - plural
targeting = [HR,finance]
}
Sample my tried Code as follows:
package prac;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class JavaPrac {
public static void main(String[] args) {
Multimap<Integer, Map> multiMap = ArrayListMultimap.create();
List<Map> stories=new ArrayList();
Map story1=new HashMap();
story1.put("id", 1);
story1.put("title", "Onboarding");
story1.put("keyword","new joinee");
story1.put("targeting","finance");
Map story2=new HashMap();
story2.put("id", 1);
story2.put("title", "Onboarding");
story2.put("keyword","training");
story2.put("targeting","HR");
stories.add(story1);
stories.add(story2);
System.out.println(stories);
stories.forEach((story) -> {
multiMap.put((Integer) story.get("id"), story);
});
}
}
A multimap can only store multiple values per key but what you want is to combine those multiple values so that you get one element that has the same id and title as well as a collection of keywords and targeting information. Thus it would probably be best to either have something like MultiStory or already have Story contain those collections.
I'd suggest using proper objects instead of just maps but with maps and Java 8 lambdas you could use compute() etc. to build maps that contain collections and combine maps that don't.
Here's an example of how you'd do it with maps. Note that this is very bad style and an example using proper pojos will follow:
Disclaimer: example based on the OP's code, not recommended (read text above)
//Problem 1: we don't know the type of the values, i.e. we could put anything for "id" etc.
Map<String, Object> story1=new HashMap<>();
story1.put("id", 1);
story1.put("title", "Onboarding");
story1.put("keyword","new joinee");
story1.put("targeting","finance");
Map<String, Object> story2=new HashMap<>();
story2.put("id", 1);
story2.put("title", "Onboarding");
story2.put("keyword","training");
story2.put("targeting","HR");
List<Map<String, Object>> stories=new ArrayList<>();
stories.add(story1);
stories.add(story2);
Map<Integer, Map<String, Object>> combined = new HashMap<>();
stories.forEach((story) -> {
//Problem 2: because we don't know the type of the values we need a lot of nasty casts
Map<String, Object> combinedStory = combined.computeIfAbsent( (Integer)story.get( "id" ), k -> new HashMap<String, Object>() );
combinedStory.put("id", story.get( "id" ) );
combinedStory.put("title", story.get( "title" ) );
//Problem 3: the combined map would look a lot like your "story" maps but would contain different types
((List<String>)combinedStory.computeIfAbsent( "keyword", v -> new List<String>() )).add( (String)story.get("keyword") );
((List<String>)combinedStory.computeIfAbsent( "targeting", v -> new List<String>() )).add( (String)story.get("targeting") );
});
Using POJOs
Here's a greatly simplified example of how you'd do it with proper Java objects (POJOs). Note that those are meant to resemble your code as much as possible and there are a lot of other issues but addressing those would be way too much here and better designed code would be a lot larger and probably harder to understand - after all it's just meant to show you a difference.
First let's define our classes (for simplicity I made the fields public, you'd normally not do that):
class Story {
public final int id;
public String title;
public String keyword;
public String targeting;
public Story(int storyId) {
id = storyId ;
}
}
class MultiStory {
public final int id;
public String title;
public Set<String> keywords = new HashSet<>();
public Set<String> targetingInfo = new HashSet<>();
public MultiStory( int storyId ) {
id = storyId ;
}
}
Then let's reiterate the code above:
Story story1=new Story( 1 );
story1.title = "Onboarding";
story1.keyword = "new joinee";
story1.targeting = "finance";
Story story2=new Story( 1 );
story2.title = "Onboarding";
story2.keyword = "training";
story2.targeting = "HR";
List<Story> stories=new ArrayList<>();
stories.add(story1);
stories.add(story2);
Map<Integer, MultiStory> combined = new HashMap<>();
stories.forEach((story) -> {
MultiStory multiStory = combined.computeIfAbsent( story.id, v -> new MultiStory( story.id ) );
multiStory.title = story.title;
multiStory.keywords.add( story.keyword );
multiStory.targetingInfo.add( story.targeting );
});
As you can see, there are no casts needed and it's clear what fields are available (though not necessarily filled) which makes it easier to reason about the code and spot errors (the compiler can help a lot here which it couldn't to in the example that uses maps).
Here is a solution using classes to represent the story and tags:
public static void main(String[] args) {
TagsCollector app = new TagsCollector();
app.go();
}
private void go() {
List<Story> stories = createStories();
System.out.println(stories);
Map<Long, Tags> tagsById = collectTags(stories);
tagsById.forEach((aLong, tags) -> System.out.println(tags));
}
private List<Story> createStories() {
return Arrays.asList(
new Story(1, "Onboarding", "new joinee", "finance"),
new Story(1, "Onboarding", "training", "HR")
);
}
private Map<Long, Tags> collectTags(List<Story> stories) {
Map<Long, Tags> tagsById = new HashMap<>();
stories.forEach(s -> {
Tags tags = tagsById.computeIfAbsent(s.id, v -> new Tags(s));
tags.getKeywords().add(s.getKeyword());
tags.getTargetings().add(s.getTargeting());
});
return tagsById;
}
Class used to represent the Story:
public class Story {
private final long id;
private final String title;
private final String keyword;
private final String targeting;
public Story(long id, String title, String keyword, String targeting) {
this.id = id;
this.title = title;
this.keyword = keyword;
this.targeting = targeting;
}
public long getId() {
return id;
}
public String getTitle() {
return title;
}
public String getKeyword() {
return keyword;
}
public String getTargeting() {
return targeting;
}
#Override
public String toString() {
return String.format("Story %s, title=%s, keyword=%s, targeting=%s", id, title, keyword, targeting);
}
}
Class used to represent the Tags:
public class Tags {
private final long id;
private final String title;
private final List<String> keywords = new ArrayList<>();
private final List<String> targetings = new ArrayList<>();
Tags(Story story) {
this.id = story.id;
this.title = story.title;
}
public List<String> getKeywords() {
return keywords;
}
public List<String> getTargetings() {
return targetings;
}
#Override
public String toString() {
return String.format("Tags for id %s, title:%s: keywords=%s, targetings=%s", id, title, keywords, targetings);
}
}
Output
[Story 1, title=Onboarding, keyword=new joinee, targeting=finance, Story 1, title=Onboarding, keyword=training, targeting=HR]
Tags for id 1, title:Onboarding: keywords=[new joinee, training], targetings=[finance, HR]
Yes, you can do that with a Multimap. First I would define a pojo for Story in order to make things clearer:
public class Story {
private int id;
private String title;
private String keyword;
private String targeting;
//getters setters
}
Second you need to define a key with hashcode and equals.
public static class StoryKey {
private final int id;
private final String title;
public StoryKey(int id, String title) {
this.id = id;
this.title = title;
}
//getters
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
StoryKey storyKey = (StoryKey) o;
if (id != storyKey.id) return false;
return title != null ? title.equals(storyKey.title) : storyKey.title == null;
}
#Override
public int hashCode() {
int result = id;
result = 31 * result + (title != null ? title.hashCode() : 0);
return result;
}
The code will look like:
ArrayListMultimap<StoryKey, Story> multiMap = ArrayListMultimap.create();
List<Story> stories = new ArrayList();
Story story1 = new Story();
story1.setId(1);
story1.setTitle("Onboarding");
story1.setKeyword("training");
story1.setTargeting("HR");
Story story2 = new Story();
story2.setId(1);
story2.setTitle("Onboarding");
story2.setKeyword("new joinee,");
story2.setTargeting("finance");
stories.add(story1);
stories.add(story2);
System.out.println(stories);
stories.
forEach((story) -> {
multiMap.put(new StoryKey(story.getId(), story.getTitle()), story);
});
multiMap.keys().forEach(key ->
System.out.println(
"id =" + key.getId() +
" title =" + key.getTitle()+
"keyword =" + multiMap.get(key).stream().map(story->story.getKeyword()).collect(Collectors.toList()).toString()+
"targeting ="+ multiMap.get(key).stream().map(story->story.getTargeting()).collect(Collectors.toList()).toString())
);

How to use Java 8 Streams groupingBy to map multiple database records to a single object with a List<String> property for one column

My problem essentially comes down to this simplified example. I have data coming back from a DB which has some duplicate information in the rows.
In this example I have a list of TeamRow objects that come back from the DB. I can easily group these using Collectors.groupingBy:
public class TeamRow {
private int id;
private String name;
private String player;
public TeamRow(int id, String name, String player) {
this.id = id;
this.name = name;
this.player = player;
}
public int getId() {return id;}
public String getName() { return name; }
public String getPlayer() {return player;}
}
public class Team {
private int id;
private String name;
private List<String> players;
public Team(int id, String name, List<String> players) {
this.id = id;
this.name = name;
this.players = new ArrayList<String>(players);
}
}
List<TeamRow> dbTeams = new ArrayList<TeamRow>();
dbTeams.add(new TeamRow(1, "Team1", "Jonny"));
dbTeams.add(new TeamRow(1, "Team1", "Rob"));
dbTeams.add(new TeamRow(1, "Team1", "Carlos"));
dbTeams.add(new TeamRow(2, "Team2", "Shane"));
dbTeams.add(new TeamRow(2, "Team2", "Lucas"));
dbTeams.add(new TeamRow(3, "Team3", "Geraint"));
dbTeams.add(new TeamRow(3, "Team3", "Rocky"));
dbTeams.add(new TeamRow(3, "Team3", "Wayne"));
dbTeams.add(new TeamRow(3, "Team3", "Dwayne"));
dbTeams.add(new TeamRow(3, "Team3", "Lester"));
Map<Integer, List<TeamRow>> myMap = dbTeams.stream().collect(Collectors.groupingBy(TeamRow::getId));
However, what I'm actually trying to achieve is to convert the TeamRows to Teams. So that the id and name are only represented once and the players are stored in a List in the Team object. I can achieve this by adding a forEach over the map as shown below.
But I've been trying to figure out if there is a way I can achieve the same result by adding some sort of mapper or downstream collector. Would this even offer any benefit over adding a subsequent forEach ?? Eg:
List<Team> teams = dbTeams.stream().collect(Collectors.groupingBy(TeamRow::getId, ???), ???).???;
Conversion using forEach:
List<Team> teams = new ArrayList<>();
myMap.forEach((id, teamRows) -> {
if (teamRows.size() > 0) {
TeamRow tr = teamRows.get(0);
List<String> players = teamRows.stream().map(TeamRow::getPlayer).collect(Collectors.toList());
teams.add(new Team(id, tr.getName(), players));
}
});
Previously I said I would do it by creating an atomic transformer function like this:
Function<TeamRow, Team> getTeamRowTransformer() {
final Map<Integer, Team> map = new ConcurrentHashMap<Integer, Team>();
return (teamRow)->{
Team result = map.computeIfAbsent(teamRow.getId(), id->new Team(id, teamRow.getName(), Collections.emptyList()));
result.players.add(teamRow.getPlayer());
return result;
};
}
It handles the mapping and your stream code becomes one very legible step:
Set<Team> finalTeams = dbTeams.stream()
.map(getTeamRowTransformer())
.collect(Collectors.toSet());
HOWEVER, I realized, you can also do this:
List<Team> teams = dbTeams.stream()
.map(tr->new Team(tr.getId(), tr.getName(), Arrays.asList(tr.getPlayer())))
.collect(Collectors.collectingAndThen(
Collectors.groupingBy(t->t.id,
Collectors.reducing((Team a, Team b)->{
a.players.addAll(b.players);
return (Team)a;
})
), m->m.values().stream()
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList())
)
);
This way you never have an accessible mutable collection until List<Team> teams is assigned.
You may use toMap collector with custom merge function. It's probably a good idea to add merge method to the Team class:
public class Team {
private final int id;
private final String name;
private final List<String> players;
public Team(int id, String name, List<String> players) {
this.id = id;
this.name = name;
this.players = new ArrayList<>(players);
}
// merges other team into this team, returning this team
public Team merge(Team other) {
assert id == other.id; // remove asserts if you don't like them
assert name.equals(other.name);
players.addAll(other.players);
return this;
}
}
Now you can solve your problem this way:
Collection<Team> teams = dbTeams.stream()
.map(tr -> new Team(tr.id, tr.name, Arrays.asList(tr.player)))
.collect(Collectors.toMap(t -> t.id, t -> t, Team::merge)).values();
You could try something like
List<Team> teamList = dbTeams.stream().collect(Collectors.collectingAndThen(Collectors.groupingBy(TeamRow::getId),
(m -> m.entrySet().stream().map(
e -> {
List<TeamRow> l = e.getValue();
return new Team(l.get(0).getId(), l.get(0).getName(), l.stream().map(TeamRow::getPlayer).collect(Collectors.toList()));
}
).collect(Collectors.toList()))));
Using collectingAndThen() you can use a function which maps the entries of the map to Teams. l.get(0) should not fail as there is always at least one entry in the list.
I am not sure if this is more concise, but at least it does not use foreach.

Use Java8 stream to reduce Object to a Map

If I have a class like
public class Property {
private String id;
private String key;
private String value;
public Property(String id, String key, String value) {
this.id = id;
this.key = key;
this.value = value;
}
//getters and setters
}
and I have a Set<Property> properties of a few properties that I would like to reduce into a Map of just the key and values from these Property objects.
Most of my solutions ended up being not so suave. I know there's a handy way to do these with a Collector but I'm not that familiar with Java8 yet. Any tips?
Set<Property> properties = new HashSet<>();
properties.add(new Property("0", "a", "A"));
properties.add(new Property("1", "b", "B"));
Map<String, String> result = properties.stream()
.collect(Collectors.toMap(p -> p.key, p -> p.value));
System.out.println(result);

Categories