问题
I have a Set<DateCount>
which I create using the following code. This creates a set of 7 DateCount
objects with an initial count of 0, one for each day of the current week starting from the current day.
// Create an initial Set of DateCount objects for the current week with counts of 0
Set<DateCount> dateCounts = IntStream.range(0, DAYS_IN_WEEK)
.mapToObj(idx -> new DateTime().withTimeAtStartOfDay().plusDays(idx))
.map(dt -> new DateCount(dt.toDate(), 0L))
.collect(toSet());
I have a List<Object[]>
which is returned from a database repository. Each Object[]
in the list has a BigDecimal
at index 0 and a Long
at index 1. The BigDecimal
is actually a date, something like 20141001
. What I'm wondering is if there is a way I can update the dateCounts
set using this data in a Stream fashion. Right now, I'm doing the following which just iterates over the list of object arrays and creates new DateCount
objects that are then added to the dateCounts
set. The DateCount
class has a custom equals()
and hashCode()
method to ensure the dateCounts
set contains only a single DateCount
instance for any given date.
data.stream()
.forEach(obj -> {
DateTime date = null;
try {
date = (sdf == null) ? new DateTime(obj[0]) : new DateTime(sdf.parse(obj[0].toString()));
dateCounts.add(new DateCount(date.toDate(), (Long) obj[1]));
} catch (ParseException e) {
e.printStackTrace();
}
});
On a side note, I'm wondering if there's also a way to avoid doing a try/catch in my lambda expression just for parsing a String
to a Date
.
Update -- This is what I've come up with so far. It doesn't seem natural though to stream over the List<Object[]>
within the .map call, not sure if there's a better way. I introduced my own Tuple<X, Y>
class because working with an Object[]
in streams doesn't work very well given there's no accessor methods. Also in the last 2 lines, I'm creating a TreeSet
with a Comparator
so that I can sort the data by date. I don't feel that's the best approach, but I tried calling sorted()
and making DateCount
implement Comparable
which seems to work fine, but as soon as the collect(toSet())
method is called, the sorting goes out the window. I assume that's the nature of it being a streamed call. I'm curious if there's a way to sort it though before the collect method call and retain the sort after calling collect.
Set<DateCount> dateCounts = IntStream.range(0, DAYS_IN_WEEK)
.mapToObj(idx -> new Tuple<>(new DateTime().withTimeAtStartOfDay().plusDays(idx).toDate(), 0L))
.map(t -> {
Tuple<String, Long> d = data.stream()
.map(arr -> new Tuple<>(arr[0].toString(), (Long) arr[1]))
.filter(tuple -> sdf.format(t.getX()).equals(tuple.getX()))
.findFirst().orElse(new Tuple<>(sdf.format(t.getX()), 0L));
return new DateCount(DateTime.parse(d.getX(), DateTimeFormat.forPattern("yyyyMMdd")).toDate(), d.getY());
})
.collect(toSet());
TreeSet<DateCount> set = new TreeSet<>((a, b) -> a.compareTo(b));
set.addAll(dateCounts);
回答1:
You can provide a Supplier
for the collection. Just replace
.collect(toSet());
with
.collect(toCollection(() -> new TreeSet<>((a, b) -> a.compareTo(b))));
But note, that specifying (a, b) -> a.compareTo(b)
for the Comparator
implies natural ordering. If your elements implement Comparable
, it is unnecessary to provide such a comparator. You can simply use
.collect(toCollection(TreeSet::new));
If DateCount
has a compareTo
method but does not implement Comparator
you could also specify DateCount::compareTo
rather than (a, b) -> a.compareTo(b)
.
Note that in your first mapToObj
operation it is unnecessary to wrap the DateTime
in a Tuple
. You can simply map to DateTime
and use that value in the next map
operation (as I see only t.getX()
there, the second value which is 0L
is not used at all).
After all, I’m not sure what you are trying to achieve but I have the strong feeling, that you might have a look at Collectors.groupingBy…
回答2:
You can map your database data to a Map<DateTime,Long>
for quick look-ups so you only have to process it once. Then as you stream through your Set<DateCount>
you can pull in updated values using peek()
. Here's an example. I used Date
in place of DateTime
just to make it easier to code the example.
Instant baseDate = Instant.now();
Date date1 = new Date(baseDate.plus(1, ChronoUnit.DAYS).toEpochMilli());
Date date2 = new Date(baseDate.plus(2, ChronoUnit.DAYS).toEpochMilli());
Date date3 = new Date(baseDate.plus(3, ChronoUnit.DAYS).toEpochMilli());
Set<DateCount> dateCounts = new TreeSet<DateCount>(); // Our mock Set
dateCounts.add(new DateCount(date1, 0L));
dateCounts.add(new DateCount(date2, 0L));
dateCounts.add(new DateCount(date3, 0L));
List<Object[]> data = new ArrayList<Object[]>(); // Our mock database Data
data.add(new Object[]{date1.toInstant().toEpochMilli(), 5L});
data.add(new Object[]{date2.toInstant().toEpochMilli(), 3L});
data.add(new Object[]{date3.toInstant().toEpochMilli(), 2L});
//Map our data so we only have to process it once, and then we can look it up
Map<Date,Long> mappedData = data.stream()
.collect(Collectors.toConcurrentMap(k->new Date((long) k[0]), v->(Long)v[1]));
//Update the data using peek as we stream through it
dateCounts.stream()
.peek(dc->dc.setCount(mappedData.get(dc.getDate())))
.forEachOrdered(System.out::println);
When run, here's the output, so that you can see the dateCounts are updated:
DateCount: Thu Dec 25 11:25:56 EST 2014 - 5
DateCount: Fri Dec 26 11:25:56 EST 2014 - 3
DateCount: Sat Dec 27 11:25:56 EST 2014 - 2
And here's the implementation of DateCount I used to make the above code work... I imagine it's not too dissimilar to yours:
public class DateCount implements Comparable<DateCount>{
private Date date = null;
private Long count = 0L;
public DateCount(Date datetime, Long count){
this.date = datetime;
this.count = count;
}
public void setDateTime(Date date){
this.date = date;
}
public Date getDate(){
return date;
}
public void setCount(long count){
this.count = count;
}
public long getCount(){
return count;
}
@Override
public int hashCode(){
return date.hashCode() + 459;
}
@Override
public boolean equals(Object o){
boolean result = false;
if (o instanceof DateCount){
DateCount other = (DateCount) o;
result = this.compareTo(other) == 0;
}
return result;
}
@Override
public int compareTo(DateCount other) {
if (this.date == null){
return other.getDate() == null ? 0 : -1;
} else {
return date.compareTo(other.getDate());
}
}
@Override
public String toString(){
return "DateCount: " + date.toString() + " - " + count;
}
}
来源:https://stackoverflow.com/questions/26152692/how-to-combine-unlike-streams-in-java-8