How can I convert these streamed Map keys from Longs to Objects? - java

I've current got a method which looks like:
public Map<Long, List<ReferralDetailsDTO>> getWaiting() {
return referralDao.findAll()
.stream()
.map(ReferralDetailsDTO::new)
.collect(Collectors.groupingBy(ReferralDetailsDTO::getLocationId, Collectors.toList()));
}
}
It returns me a Map of location IDs to ReferralDetailsDTO objects. However, I'd like to swap out the location ID for the LocationDTO object.
I'd have naively imagined something like this might work:
public Map<Long, List<ReferralDetailsDTO>> getWaiting() {
return referralDao.findAll()
.stream()
.map(ReferralDetailsDTO::new)
.collect(Collectors.groupingBy(locationDao.findById(ReferralDetailsDTO::getLocationId), Collectors.toList()));
}
Obviously, I'm here because it doesn't - Java complains the findById method is expecting a Long value, not the method reference. Any suggestions for how I can neatly address this? Thanks in advance.

First of all, change the key type of the Map from Long to your relevant class (is it LocationDTO or some other class?)
Second of all, use a lambda expression instead of method reference for the lookup :
public Map<LocationDTO, List<ReferralDetailsDTO>> getWaiting() {
return referralDao.findAll()
.stream()
.map(ReferralDetailsDTO::new)
.collect(Collectors.groupingBy(r -> locationDao.findById(r.getLocationId()));
}

Related

Nested lambda foreach with two different lists (one of the list must be final?)

Could you help me with writing two nested foreach loops for two different lists where I need to compare specific object attribut stored in both lists?
List<Object1> listOfObject1 = mapper.selectObject1ByName(value1);
List<Object2> listOfObject2 = mapper.selectObject2ByName(value2);
listOfObject1.stream().forEach(object1 -> {
listOfObject2.stream().forEach(object2 -> {
if (object1.getCode() == object2.getCode()) {
//do something
}
});
});
Of course I got this error: "Variable used in lambda expression should be final or effectively final" but I can't make any of the list final, because there is some decision logic above where I fill the lists with the correct values and I am writing to the list on more places.
If implementation of // do something part requires either of object1 or object2, then appropriate codes could be collected into a separate set, which is used later to filter the entries in the other list.
For example, if object2 has to be consumed:
class MyClass {
static void process(List<Object1> listOfObject1, List<Object2> listOfObject2) {
// set of codes for Object1 instances
Set<Integer> codes = listOfObject1.stream()
.map(Object1::getCode)
.collect(Collectors.toSet());
listOfObject2.stream()
.filter(o2 -> codes.contains(o2.getCode()))
.forEach(MyClass::unarySomething); // method reference
}
static void unarySomething(Object2 o2) {
}
}
If both object1 and object2 are consumed in a method like:
static void binarySomething(Object1 o1, Object2 o2) {
}
a map of codes Map<Integer, List<Object1>> mapCodes can be created using groupingBy and then apply filter and forEach:
Map<Integer, List<Object1>> mapCodes = listOfObject1
.stream()
.collect(Collectors.groupingBy(Object1::getCode));
listOfObject2.stream()
.filter(o2 -> mapCodes.containsKey(o2.getCode()))
.forEach(o2 -> mapCodes.get(o2.getCode())
.forEach(o1 -> binarySomething(o1, o2)));
We can simply use hashMap to keep all elements of list2.Then we can iterate list1's elements and check whether it exists in hashMap or not.
final Map<String,Object> mapOfElements= new HashMap<>();
Then you can perform required opertaion.
list2.forEach(e->mapOfElements.put(e.getCode(),e));
list1
.stream()
.filter(e->mapOfElements.get(e.getCode())
.collect(Collectors.toList());
I think you can define a compare function and use it to compare the data in your lists. I suggest using java Comparator class. That way the user must define compare(T o1, T o2) and equals(Object obj) functions.

Iterating using ForEach in Spring -boot

I'm trying to iterate over a repository of database table and once there, access one of the row to finally retrieve the information i need.
Thus I first went to my repository over all those elements in the database, applied findAll() method; then being there I used stream().foreach(), which eventually positioned me inside each item being able to retrieve any kind of information, like accessing its lists and other things.
But that throws an exception
Required type:Object
Provided:void
here is the function :
public ResponseEntity<Map<String, Object>> allshots(Authentication authentication) {
Map<String, Object> dto = new HashMap<>();
dto.put("player_shots_detail", playerCrabRepository.findAll().stream().forEach(playerCrab -> { playerCrab.getDiceCrabList().stream()
.map(diceCrab -> makeDiceCrabMiniDto(diceCrab)).collect(Collectors.toList());}));
return new ResponseEntity<>(dto, HttpStatus.CREATED);
}
Does that mean I should return something instead of void?
I appreciate any help, and thanks in advance
forEach return void but dto.put requird an object. try replace forEach with map
dto.put("player_shots_detail", playerCrabRepository.findAll().stream().map(playerCrab -> playerCrab.getDiceCrabList()).flatMap(diceCrab -> makeDiceCrabMiniDto(diceCrab)).collect(Collectors.toList()));
forEach a sentinel operation on Stream doesn't return anything as it's return type is void. Use collect instead. Your Map needs an object as value while your operation will return nothing due to forEach.
Java Docs for your reference. https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#forEach-java.util.function.Consumer-
Return type for foreach in java in void. You can use map and flatmap functions of streams for the above use case.
Error denotes that your function is type void which response you try to place as value in map.
In short foreach block return nothing. In such case you have to store response in following way.
Current code is :
List<T> data = playerCrabRepository.findAll().stream()
.foreach(playerCrab -> {
playerCrab.getDiceCrabList.stream()
.map(makeDirceCrabMiniDto(diceCrab))
.collect(Collectors.toList())
})
dto.put("player_shots_detail", data);
Use map instead of foreach and return them & collect it.
In such condition you have to store your result in list,
List<T> data = playerCrabRepository.findAll().stream()
.map(playerCrab -> {
return playerCrab.getDiceCrabList.stream()
.map(makeDirceCrabMiniDto(diceCrab))
.collect(Collectors.toList())
}).collect(Collectors.toList());
dto.put("player_shots_detail", data);

Java8 lambda approach

I have this piece of code that filters from a list of objects based on a set of String identifiers passed in and returns a map of string-id and objects. Something similar to follows:
class Foo {
String id;
String getId() {return id};
};
// Get map of id --> Foo objects whose string are in fooStr
Map<String,Foo> filterMethod (Set<String> fooStr) {
List<Foo> fDefs; // list of Foo objects
Map<String,Foo> fObjMap = new HashMap<String, Foo>(); // map of String to Foo objects
for (Foo f : fDefs) {
if (fooStr.contains(f.getId()))
fObjMap.put(f.getId(),f);
}
return (fObjMap);
}
Is there a better Java8 way of doing this using filter or map?
I could not figure it out and tried searching on stackoverflow but could not find any hints, so am posting as a question.
Any help is much appreciated.
~Ash
Just use the filter operator with the same predicate as above and then the toMap collector to build the map. Also notice that your iterative solution precludes any possibility of key conflict, hence, I have omitted that, too.
Map<String, Foo> idToFooMap = fDefs.stream()
.filter(f -> fooStr.contains(f.getId()))
.collect(Collectors.toMap(Foo::getId, f -> f));
When including items conditionally in the final output use filter and when going from stream to a map use Collectors.toMap. Here's what you end up with:
Map<String,Foo> filterMethod (final Set<String> fooStr) {
List<Foo> fDefs; // list of Foo objects
return fDefs.stream()
.filter(foo -> fooStr.contains(foo.getId()))
.collect(Collectors.toMap(Foo::getId, Function.identity()));
}
Though ggreiner has already provided a working solution, when there are duplicates you'd better handle it including a mergeFunction.
Directly using Collectors.toMap(keyMapper, valueMapper), one or another day you will encounter this following issue.
If the mapped keys contains duplicates (according to Object.equals(Object)), an IllegalStateException is thrown when the collection operation is performed. If the mapped keys may have duplicates, use toMap(Function, Function, BinaryOperator) instead.
Based on the OP's solution, I think it would be better using
import static java.util.stream.Collectors.*; // save some typing and make it cleaner;
fDefs.stream()
.filter(foo -> fooStr.contains(foo.getId()))
.collect(toMap(Foo::getId, foo -> foo, (oldFoo, newFoo) -> newFoo));
Maybe something like this?
Map<String,Foo> filterMethod (Set<String> fooStr) {
List<Foo> fDefs; // get this list from somewhere
Map<String, Foo> fObjMap = new HashMap<> ();
fDefs.stream()
.filter(foo -> fooStr.contains(foo.getId()))
.forEach(foo -> fObjMap.put(foo.getId(), foo))
return fObjMap;
}

How to do additional processing in Java Stream API near the "::" method reference operator

Using the Java Stream API, is there a way to do additional processing to adjust the value of whatever is passed to a method reference?
I'll give two examples.
Example 1.
In the first example, I start with a Stream<Path>, and I want to return a Map<String, Path> in which the keys in the map are processed version of the filename using another function that takes a String filename (not a Path). Specifically:
public Map<String, Path> createMap(Path sourceFolder, PathMatcher filter) {
return stream.filter(filter::matches)
.collect(Collectors.toMap(FilenameHelper::parseFilename, Function.identity()));
parseFilename(String filename) takes a String filename, but of course the method reference gets a Path. I'd like to say something like, FilenameHelper::parseFilename(((Path)Function.identity()).toFile().getName()) but that doesn't work (Eclipse says: "The left-hand side of an assignment must be a variable"). I can work around it by creating a new method that takes a Path and just does return parseFilename(path.toFile().toName()) but that's not cool.
Example 2.
In the second example, I have rows, a List<List<String>>> that represents a data table (rows, then columns). I have a method that should return a List<String> consisting of a specific column in that table for every nth row. I want to do something like:
public List<String> getDataFromColumn(String columnName, int nth) {
/// Without a clause at ???, this returns a List<List<String>>
return IntStream.range(0, rows.size())
.filter(n -> n % nth == 0) // Get every nth row
.mapToObj(rows::get)
.???
.collect(Collectors.toList());
}
Where "???" should be something like map(ArrayList::get(headers.indexOf(columnName))) (where headers is a List<String> containing the column headers) but if I put that in, I get an AssignmentOperator syntax error in the get part of this clause. Replacing map with forEach doesn't help here. In other words, I don't want rows.get(n), I want rows.get(n).get(headers.indexOf(columnName).
Question
In both of these examples, I want to do something additional to the value that is being passed to the method pointed to with the method reference operator (::). Is there a "Java Stream-ic" way to do additional processing to the thing being passed to the method reference?
Method references are essentially a convenient substitute for lambdas where the function signature is an exact match to the method signature. In your case you can just use regular lambdas:
public Map<String, Path> createMap(Path sourceFolder, PathMatcher filter) {
return stream.filter(filter::matches)
.collect(Collectors.toMap(path -> FilenameHelper.parseFilename(path.toFile().getName()), Function.identity()));
}
public List<String> getDataFromColumn(String columnName, int nth) {
return IntStream.range(0, rows.size())
.filter(n -> n % nth == 0)
.mapToObj(rows::get)
.map(row -> row.get(headers.indexOf(columnName)))
.collect(Collectors.toList());
}
How about Function.compose? Of course you cannot use FilenameHelper::parseFilename.compose, but you can easily write a static helper method to work around it:
static <T, V, R> Function<T, R> compose(Function<T, V> f, Function<V, R> g) {
return g.compose(f);
}
Now we can compose method references:
return stream.filter(filter::matches)
.collect(Collectors.toMap(
compose(
compose(Path::getFileName, Path::toString),
FilenameHelper::parseFilename),
Function.identity()));
This is actually not very readable but an alternative to writing a full lambda.
No, this functionality is currently not provided.
The usual way would be to just not use a method reference and instead call the method the "usual" way using a lambda expression:
stream.filter(filter::matches)
.collect(Collectors.toMap(p -> FilenameHelper.parseFilename(p.getFileName()), Function.identity()));
No, there is not. There is no syntax to do that.
And if you wanted such a thing then lambda expression is what you want.
Method reference or lambda, under the hood you are still going to get a class that actually implements the Predicate/Function so it does not matter.
And that argument but that's not cool, to me under the conditions that there is no syntax for that, it's the best option you have.
Underneath the actual calls that you there is a MethodHandle (introduced in jdk-7) and MethodHandles do not have a way to achieve what you want. I think the same restriction exists in C++ with method pointers.

Java: how to convert a List<?> to a Map<String,?> [duplicate]

This question already has answers here:
How to convert List to Map?
(20 answers)
Closed 7 years ago.
I would like to find a way to take the object specific routine below and abstract it into a method that you can pass a class, list, and fieldname to get back a Map.
If I could get a general pointer on the pattern used or , etc that could get me started in the right direction.
Map<String,Role> mapped_roles = new HashMap<String,Role>();
List<Role> p_roles = (List<Role>) c.list();
for (Role el : p_roles) {
mapped_roles.put(el.getName(), el);
}
to this? (Pseudo code)
Map<String,?> MapMe(Class clz, Collection list, String methodName)
Map<String,?> map = new HashMap<String,?>();
for (clz el : list) {
map.put(el.methodName(), el);
}
is it possible?
Using Guava (formerly Google Collections):
Map<String,Role> mappedRoles = Maps.uniqueIndex(yourList, Functions.toStringFunction());
Or, if you want to supply your own method that makes a String out of the object:
Map<String,Role> mappedRoles = Maps.uniqueIndex(yourList, new Function<Role,String>() {
public String apply(Role from) {
return from.getName(); // or something else
}});
Here's what I would do. I am not entirely sure if I am handling generics right, but oh well:
public <T> Map<String, T> mapMe(Collection<T> list) {
Map<String, T> map = new HashMap<String, T>();
for (T el : list) {
map.put(el.toString(), el);
}
return map;
}
Just pass a Collection to it, and have your classes implement toString() to return the name. Polymorphism will take care of it.
Java 8 streams and method references make this so easy you don't need a helper method for it.
Map<String, Foo> map = listOfFoos.stream()
.collect(Collectors.toMap(Foo::getName, Function.identity()));
If there may be duplicate keys, you can aggregate the values with the toMap overload that takes a value merge function, or you can use groupingBy to collect into a list:
//taken right from the Collectors javadoc
Map<Department, List<Employee>> byDept = employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment));
As shown above, none of this is specific to String -- you can create an index on any type.
If you have a lot of objects to process and/or your indexing function is expensive, you can go parallel by using Collection.parallelStream() or stream().parallel() (they do the same thing). In that case you might use toConcurrentMap or groupingByConcurrent, as they allow the stream implementation to just blast elements into a ConcurrentMap instead of making separate maps for each thread and then merging them.
If you don't want to commit to Foo::getName (or any specific method) at the call site, you can use a Function passed in by a caller, stored in a field, etc.. Whoever actually creates the Function can still take advantage of method reference or lambda syntax.
Avoid reflection like the plague.
Unfortunately, Java's syntax for this is verbose. (A recent JDK7 proposal would make it much more consise.)
interface ToString<T> {
String toString(T obj);
}
public static <T> Map<String,T> stringIndexOf(
Iterable<T> things,
ToString<T> toString
) {
Map<String,T> map = new HashMap<String,T>();
for (T thing : things) {
map.put(toString.toString(thing), thing);
}
return map;
}
Currently call as:
Map<String,Thing> map = stringIndexOf(
things,
new ToString<Thing>() { public String toString(Thing thing) {
return thing.getSomething();
}
);
In JDK7, it may be something like:
Map<String,Thing> map = stringIndexOf(
things,
{ thing -> thing.getSomething(); }
);
(Might need a yield in there.)
Using reflection and generics:
public static <T> Map<String, T> MapMe(Class<T> clz, Collection<T> list, String methodName)
throws Exception{
Map<String, T> map = new HashMap<String, T>();
Method method = clz.getMethod(methodName);
for (T el : list){
map.put((String)method.invoke(el), el);
}
return map;
}
In your documentation, make sure you mention that the return type of the method must be a String. Otherwise, it will throw a ClassCastException when it tries to cast the return value.
If you're sure that each object in the List will have a unique index, use Guava with Jorn's suggestion of Maps.uniqueIndex.
If, on the other hand, more than one object may have the same value for the index field (which, while not true for your specific example perhaps, is true in many use cases for this sort of thing), the more general way do this indexing is to use Multimaps.index(Iterable<V> values, Function<? super V,K> keyFunction) to create an ImmutableListMultimap<K,V> that maps each key to one or more matching values.
Here's an example that uses a custom Function that creates an index on a specific property of an object:
List<Foo> foos = ...
ImmutableListMultimap<String, Foo> index = Multimaps.index(foos,
new Function<Foo, String>() {
public String apply(Foo input) {
return input.getBar();
}
});
// iterate over all Foos that have "baz" as their Bar property
for (Foo foo : index.get("baz")) { ... }

Categories

Resources