Related
I have a usecase where client is sending a List<Function>. Task is to iterate and execute this function and keep it in a TypedSafeMap.
pseudo client code:
Function<String, Integer> firstFn = x -> x.length();
Function<String, String> secondFn = x -> x.substring(0);
client.runTheseFunctions(Arrays.asList(firstFn, secondFn));
Inside runtTheseFunctions in the code, task is to execute these functions and keep it in a TypedSafeMap where the key is the datatype of the type of the result of the function and value is the return of functions.apply();
The code below
public static void runTheseFunctions(List<Function<Employee, ?>> lst, Employee o) {
lst.stream().forEach( x -> {
typedSafeMap.put(????, x.apply(o));
//The key is nothing but the datatype of the x.apply(o).
//How do I add this in runtime here. Generics is all compile time safety.
});
}
public static void runTheseFunctions(List<Function<Employee, ?>> lst, Employee o) {
lst.stream().collect(Collectors.toMap(f -> f.apply(o).getClass(), f -> f.apply(o)));
}
You can implement your "runTheseFunctions" method as shown below:
public static void runTheseFunctions(List<Function<Employee, ?>> lst, Employee o) {
Map<Class<?>, Object> typedSafeMap = new HashMap<>();
lst.stream().forEach(x -> {
Object value = x.apply(o);
typedSafeMap.put(value.getClass(), value);
});
System.out.println(typedSafeMap);
}
In case the List of Functions contains two or more Functions with the same outputtype (for instance: String getFirstName, String getLastName, toMap will fail. So an alternative is:
var map = list.stream().collect(groupingBy(
f -> f.apply(e).getClass(),
mapping(f -> f.apply(e), toList())
));
Here is an example of what you want to achieve, and you can use for your tests. I assumed an trivial implementation of Employee class, just to give you an idea:
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
class Employee {
String name;
public Employee(String name) {
this.name = name;
}
public int length() {
return name.length();
}
public String substring(int index) {
return name.substring(index);
}
}
public class Test {
public static void main(String[] args) {
Employee e = new Employee("Marco");
Function<Employee, Integer> firstFn = x -> x.length();
Function<Employee, String> secondFn = x -> x.substring(0);
runTheseFunctions(Arrays.asList(firstFn, secondFn), e);
}
public static void runTheseFunctions(List<Function<Employee, ?>> lst, Employee o) {
Map<Class, Object> typedSafeMap = new HashMap<>();
lst.stream().forEach(x -> {
Object result = x.apply(o);
typedSafeMap.put(x.apply(o).getClass(), x.apply(o));
// The key is nothing but the datatype of the x.apply(o).
// How do I add this in runtime here. Generics is all compile time safety.
});
typedSafeMap.entrySet().forEach(entry -> System.out.println(entry.getKey() + " - " + entry.getValue()));
}
}
And here is the output:
class java.lang.String - Marco
class java.lang.Integer - 5
Enhancing #Yonas answer:
private static Map<?, ? extends Object> runTheseFunctions(List<Function<String, ? extends Object>> list, String o) {
return list.stream()
.map(f -> f.apply(o))
.collect(Collectors.toMap(result -> result.getClass(), Function.identity()));
}
This will call the f.apply(o) only once.
I am learning some cool stuff about Java StreamAPI and got stuck'd into one problem:
I have a use case where I want to return newly create hashmap using stream. I am using the traditional way of defining a HashMap in the function and adding up values to it.
I was more interested in knowing some better ways to achieve so
public Map<String,String> constructMap(List<CustomObject> lists){
Map<String,String> newMap = new HashMap<>();
lists.stream().filter(x->x!=null).forEach(map -> newMap.putAll(map.getSomeMapping(studentId));
return newMap;
}
Can I achieve this using reduceAPI or any other way without having to create a custom hashmap (directly return the stream one liner)?
Edit:
for Example:
CustomObject c1 = new CustomObject("bookId1", "book1");
CustomObject c2 = new CustomObject("bookId2", "book2");
List<CustomObject> lists = new ArrayList();
lists.add(c1); lists.add(c2);
The getter in class CustomObject is: getSomeMapping(input)
which return Map<BookID, Book>
Expected output:
{"bookId1" : "book1", "bookId2" : "book2"}
Edit2:
One more thing to clarify, the CustomObject class does not have any other getters defined. The only function I have access to is getSomeMapping(input) which returns a mapping
thank you for any help.
Assuming CustomObject has the following structure and getter getSomeMapping which returns a map:
class CustomObject {
private Map<String, String> someMapping;
public CustomObject(String key, String value) {
this.someMapping = new HashMap<>();
someMapping.put(key, value);
}
public Map<String, String> getSomeMapping() {
return someMapping;
}
}
Then constructMap will use already mentioned Collectors.toMap after flattening the entries in someMapping:
public static Map<String, String> constructMap(List<CustomObject> list) {
return list.stream()
.filter(Objects::nonNull)
.map(CustomObject::getSomeMapping)
.flatMap(map -> map.entrySet().stream())
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(v1, v2) -> v1, // merge function to handle possible duplicates
LinkedHashMap::new
));
}
Test
CustomObject c1 = new CustomObject("bookId1", "book1");
CustomObject c2 = new CustomObject("bookId2", "book2");
List<CustomObject> lists = Arrays.asList(c1, c2);
Map<String, String> result = constructMap(lists);
System.out.println(result);
Output:
{bookId1=book1, bookId2=book2}
You can use Collectors#toMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper, BinaryOperator<U> mergeFunction, Supplier<M> mapSupplier) to create a LinkedHashMap using the bookId as the key, and bookName as the value.
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
class CustomObject {
private String bookId;
private String bookName;
public CustomObject(String bookId, String bookName) {
this.bookId = bookId;
this.bookName = bookName;
}
public String getBookId() {
return bookId;
}
public String getBookName() {
return bookName;
}
// Other stuff e.g. equals, hashCode etc.
}
public class Main {
public static void main(String[] args) {
List<CustomObject> list = List.of(new CustomObject("bookId1", "book1"), new CustomObject("bookId2", "book2"));
System.out.println(constructMap(list));
}
public static Map<String, String> constructMap(List<CustomObject> list) {
return list.stream()
.filter(Objects::nonNull)
.collect(Collectors.toMap(CustomObject::getBookId, CustomObject::getBookName, (a, b) -> a, LinkedHashMap::new));
}
}
Output:
{bookId1=book1, bookId2=book2}
Note: The mergeFunction, (a, b) -> a resolves the collision between values associated with the same key e.g. in this case, we have defined it to select a out of a and b having the same key. If the order of elements does not matter, you can use Collectors#toMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper) as shown below:
public static Map<String, String> constructMap(List<CustomObject> list) {
return list.stream()
.filter(Objects::nonNull)
.collect(Collectors.toMap(CustomObject::getBookId, CustomObject::getBookName));
}
A sample output:
{bookId2=book2, bookId1=book1}
To turn a stream into a map you're better off using collect(). For instance:
public Map<String,String> toMap(List<Entry<String,String>> entries) {
return entries.stream().collect(Collectors.toMap(Entry::getKey, Entry::getValue));
}
Or if your keys are non-unique and you want the values to be combined as a list:
public Map<String,List<CustomObject>> toMap(List<CustomObject> entries) {
return entries.stream().collect(Collectors.groupingBy(CustomObject::getKey));
}
Look into [Collectors.toMap()] 1. This can return the items as a new Map.
lists.stream().filter(x->x!=null).collect(Collectors.toMap(CustomObject::getMapKey(), CustomObject::getMapValue()));
getMapKey and getMapValue are here methods returning the key and value of the CustomObject for the map. Instead of using simple getters it might also be necessary to execute some more advanced logic.
lists.stream().filter(x->x!=null).collect(Collectors.toMap(l -> {...; return key;}, l -> { ...; return value;}));
Let's assume your CustomObject class has getters to retrieve a school id with a name. You could do it like this. I declared it static as it does not appear to depend on instance fields.
public static Map<String,String> constructMap(List<CustomObject> lists){
return lists.stream()
.filter(Objects::nonNull)
.collect(Collectors.toMap(CustomObject::getName, CustomObject::getID));
}
This presumes that names and Id's are one-to-one, as this does not handle duplicate keys.
I have a Map<String, List<String>> map and I want extract from it a List<String> that contains the strings of all the list of strings in the map. I'd like to use java8 streams syntax.
In old java I would do:
List<String> all = new LinkedList<String>();
for (String key: map.keySet()) {
all.addAll(map.get(key));
}
return all;
how to do that using streams?
You can do what you want using Stream.flatMap(Function).
public static List<String> collectValues(Map<String, List<String>> map) {
return map.values().stream()
.flatMap(Collection::stream)
.collect(Collectors.toList());
}
A more generic version could look like:
public static <E> List<E> collectValues(Map<?, ? extends Collection<? extends E>> map) {
return map.values().stream()
.flatMap(Collection::stream)
.collect(Collectors.toList());
}
And an even more generic version which allows you to specify the return type:
public static <C extends Collection<E>, E> C collectValues(
Map<?, ? extends Collection<? extends E>> map, Supplier<C> collectionFactory) {
return map.values().stream()
.flatMap(Collection::stream)
.collect(Collectors.toCollection(collectionFactory));
}
And finally, just for the fun of it, the most generic version I can think of:
public static <C, E> C collectValues(Map<?, ? extends Iterable<? extends E>> map,
Collector<E, ?, C> collector) {
return map.values().stream()
.flatMap(iterable -> StreamSupport.stream(iterable.spliterator(), false))
.collect(collector);
}
This one uses the StreamSupport class and Collector interface.
Using a new ArrayList and the addAll() method to get the same result.
public class MapTest {
public static void main(String[] args) {
Map<String, List<String>> infoMap = new HashMap<>();
infoMap.put("1", Arrays.asList("a","b","c"));
infoMap.put("2", Arrays.asList("d","e","f"));
infoMap.put("3", Arrays.asList("g","h","i"));
List<String> result = new ArrayList<>();
infoMap.values().stream().forEach(result::addAll);
result.forEach(System.out::println);
}
}
I have a class that return data from an application, where methods point to different endpoint and return different objects, for example:
public List<Cat> getCats() {
String url = this.BASE_URL + "/cats";
return getDataFromAPI(url, new TypeReference<List<Cat>>() {});
}
public List<Dog> getDogs() {
String url = this.BASE_URL + "/dogs";
return getDataFromAPI(url, new TypeReference<List<Dog>>() {});
}
public <T> T getDataFromAPI(String url, TypeReference<T> typeRef) {
//restTemplate is Spring RestTemplate
Map<String, List<Map<String, Object>>> response =
this.restTemplate.getForObject(url, Map.class);
//objectMapper is Jackson ObjectMapper
return this.objectMapper.convertValue(response.get("response"), typeReference);
}
I would like to improve my code and only send two simple parameters, for example: getCats and getDogs only send the raw type to getDataFromAPI... something like:
public List<Cat> getCats() {
String url = this.BASE_URL + "/cats";
return getDataFromAPI(url, Cat); //i know it doesnt work
}
public <T> T getDataFromAPI(String url, T type) {
Map<String, List<Map<String, Object>>> response =
this.restTemplate.getForObject(url, Map.class);
return this.mapper.convertValue(response.get("response"),
new TypeReference<List<type>>() {}); //pass the type dynamically
}
Is there a simple way to do it?
Yes, its possible. Following code will work:
public List<Cat> getCats() {
String url = this.BASE_URL + "/cats";
return getDataFromAPI(url, Cat.class);
}
public <T extends Object> List<T> getDataFromAPI(String url, Class<T> type) {
Map<String, List<Map<String, Object>>> response =
this.restTemplate.getForObject(url, Map.class);
return this.mapper.convertValue(response.get("response"),
new TypeReference<List<type>>() {}); //pass the type dynamically
}
Since, I am not sure what restTemplate and mapper is so I have written a small program of my own to check if it works. Here it is:
public static void main(String[] args) throws IOException {
System.out.println(callFriend("duck", Duck.class));
}
static HashMap<String, List> x = new HashMap<>();
static{
x.put(
"dog", new ArrayList<Dog>(){
{add(new Dog());add(new Dog());}
});
x.put(
"duck", new ArrayList<Duck>(){
{add(new Duck());add(new Duck());}
});
}
public static <T> List<T> callFriend(String name, Class<T> type) {
return x.get(name);
}
Feel free to modify the code and play around. Hope that's what you are looking for.
Problem: We need to get a (String) key for different classes of objects.
For expendability we want to configure the Method to use to get the key String – instead of implementing many if-else with intanceOf…
Naive solution (with example data) is:
public static String getKey(Object object, Map<Class<?>, Method> keySources) {
Method source = keySources.get(object.getClass());
if (source == null) {
return null;
}
try {
return (String) source.invoke(object);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
throw new RuntimeException("Error at 'invoke': " + e.getMessage(), e);
}
}
public static void main(String[] args) {
Map<Class<?>, Method> keySources = new HashMap<>();
try {
keySources.put(String.class, String.class.getMethod("toString"));
keySources.put(Thread.class, Thread.class.getMethod("getName"));
} catch (NoSuchMethodException | SecurityException e) {
throw new RuntimeException("Error at 'getMethod': " + e.getMessage(), e);
}
System.out.println(getKey("test", keySources));
System.out.println(getKey(new Thread("name"), keySources));
}
Desired solution would be like:
public static String getKey(Object object, Map<Class<?>, Function<Object, String>> keySources) {
Function<Object, String> source = keySources.get(object.getClass());
if (source == null) {
return null;
}
return source.apply(object);
}
public static void main(String[] args) {
Map<Class<?>, Function<Object, String>> keySources = new HashMap<>();
keySources.put(String.class, String::toString);
keySources.put(Thread.class, Thread::getName);
System.out.println(getKey("test", keySources));
System.out.println(getKey(new Thread("name"), keySources));
}
But String::toString is giving compilation error: The type String does not define toString(Object) that is applicable here
Constraints: We cannot modify the classes since they were generated.
I managed to get your code to pass compilation and run. I'm not sure why it works with lambda expressions but not with method references.
Perhaps there are better ways to do this.
public static <T> String getKey(T object, Map<Class<?>, Function<? extends Object, String>> keySources)
{
Function<T, String> source = (Function<T, String>) keySources.get(object.getClass());
if (source == null) {
return null;
}
return source.apply(object);
}
public static void main (java.lang.String[] args) throws Exception
{
Map<Class<?>, Function<? extends Object, String>> keySources = new HashMap<>();
keySources.put(String.class, s -> s.toString());
keySources.put(Thread.class, (Thread t) -> t.getName());
System.out.println(getKey("test", keySources));
System.out.println(getKey(new Thread("name"), keySources));
}
A Function<Object, String> is a function that accepts Object, in other words arbitrary objects as argument, so a function like String::toString, that requires its arguments to be String instances can’t fulfill the contract. That’s easy to fix, as you can use Object::toString instead, however, for Thread::getName, which requires the arguments to be Thread instances, there is no such replacement.
Since you are ensuring that the arguments are of the right type due to the map keys, you can solve this by converting each specific function to a Function<Object,String> that does a type cast:
public static <T,R> void put(Class<T> cl,
Function<T,R> f, Map<Class<?>,Function<Object,R>> map) {
map.put(cl, obj -> f.apply(cl.cast(obj)));
}
public static String getKey(Object object,
Map<Class<?>, Function<Object, String>> keySources) {
return keySources.getOrDefault(object.getClass(), x -> null).apply(object);
}
public static void main(String[] args) {
Map<Class<?>, Function<Object, String>> keySources = new HashMap<>();
put(String.class, String::toString, keySources);
// or put(String.class, Function.identity(), keySources);
put(Thread.class, Thread::getName, keySources);
System.out.println(getKey("test", keySources));
System.out.println(getKey(new Thread("name"), keySources));
}
Function take one parameter, which does not match the signature.
Try using Callable which take no parameter and return a value