I have been searching around different groupBy and stream threads but cannot find the answer to my problem. Basically I have this object:
public class Object {
string name;
string type;
}
And I return a list of these from the database. What I would like to then do is iterate through the list of objects and remove duplicate names and save a list of the second properties under one object, in a new object that looks like this:
public class NewObject {
String name;
List<String> types;
}
You can do it like this.
use groupingBy to create a map of name, List of type
use the entryset of the map to create the new object.
I added the appropriate constructors and getters in the classes.
List<OldObject> list = ...
List<NewObject> newList = list.stream()
.collect(Collectors.groupingBy(OldObject::getName,
Collectors.mapping(OldObject::getType,
Collectors.toList())))
.entrySet().stream()
.map(e -> new NewObject(e.getKey(), e.getValue()))
.toList();
}
class OldObject {
String name;
String type;
public String getName() {
return name;
}
public String getType() {
return type;
}
}
class NewObject {
String name;
List<String> types = new ArrayList<>();
public NewObject(String name, List<String> types) {
this.types = types;
this.name = name;
}
}
For your added enjoyment, I thought I would also offer the following:
create the map
conditionally create a new object if the name key is not present
in either case, add the type to the list instance of the NewObject instance in the map which is returned by the computeIfAbsent method.
When finished, just assign the values to your Collection.
Map<String, NewObject> map = new HashMap<>();
for (OldObject ob : list) {
map.computeIfAbsent(ob.getName(),
v -> new NewObject(ob.getName(),
new ArrayList<>()))
.add(ob.getType());
}
Collection<NewObject> newLista = map.values();
Caveats:
values returns a Collection, not a list so you would need to use that or pass the Collection to a list constructor of some sort (e.g. ArrayList).
the requires the addition of a add method in the NewObject class.
you could also have a getter that returns the type list directly and do.
map.computeIfAbsent(ob.getName(),
v -> new NewObject(ob.getName(),
new ArrayList<>()))
.getTypeList().add(ob.getType());
Check out these additions to the Map interface
I understand you don't want to remove duplicates but actually merge them.
I replaced your Object class name with OldObject to avoid confusion with the actual Java Object class.
Collecting to Map<String, List<String>> and then converting to List<NewObject>
You could write your own Collector (see later in the answer), but the easiest way of writing what you need would be to use the current groupingBy and toList Collectors and then using the resulting Map and create your newObject instance based on it to then again collect to a new List:
oldObjectList.stream().collect(Collectors.groupingBy(OldObject::getName, Collectors.mapping(OldObject::getType, Collectors.toList())))
.entrySet().stream()
// Assuming a NewObject constructor that receives a String name and a List<String> types
.map(e -> new NewObject(e.getKey(), e.getValue()))
.collect(Collectors.toList());
Custom Collectors
If you are interested in learning how to make your own Collector, I made an example using a custom Collector as well:
oldObjectList.stream().collect(Collector.of(
// Supplier
() -> new ConcurrentHashMap<String, NewObject>(),
// Accumulator
(map, oldObject) -> {
// Assuming a NewObject constructor that gets a name and creates a new empty List as types.
map.computeIfAbsent(oldObject.getName(), name -> new NewObject(name)).getTypes().add(oldObject.getType());
},
// Combiner
(map1, map2) -> {
map2.forEach((k, v) -> map1.merge(k, v, (v1, v2) -> {
v1.getTypes().addAll(v2.getTypes());
return v1;
}));
return map1;
},
// Finisher
map -> new ArrayList<>(map.values())
));
You can even combine groupingBy and a custom Collector as a merge function:
new ArrayList<>(oldObjectList.stream().collect(Collectors.groupingBy(OldObject::getName, Collector.of(
// Supplier
// Assuming a NewObject constructor with no arguments that creates a new empty List as types.
NewObject::new,
// Accumulator
(newObject, oldObject) -> {
newObject.setName(oldObject.getName());
newObject.getTypes().add(oldObject.getType());
},
// Combiner
(newObject1, newObject2) -> {
newObject1.getTypes().addAll(newObject2.getTypes());
return newObject1;
}
))).values());
It may be possible to use Collectors.toMap to create NewObject instances immediately and append types as necessary within the merge function.
Assuming that there NewObject has custom constructor and overridden toString method:
class NewObject {
String name;
List<String> types = new ArrayList<>();
public NewObject(String name, String type) {
this.name = name;
this.types.add(type);
}
#Override
public String toString() {
return "{ name: " + name + "; types: " + types + "}";
}
}
and that there are helper methods to create an instance of NewObject from OldObject and merge types of two NewObject instances:
// MyClass
public static NewObject createNewObject(OldObject oo) {
return new NewObject(oo.getName(), oo.getType());
}
public static NewObject mergeTypes(NewObject no1, NewObject no2) {
no1.getTypes().addAll(no2.getTypes());
return no1;
}
The transformation may be implemented as follows:
List<OldObject> input = Arrays.asList(
new OldObject("n1", "type1"), new OldObject("n1", "type2"),
new OldObject("n2", "type1"), new OldObject("n3", "type2"),
new OldObject("n1", "type3"), new OldObject("n2", "type2")
);
List<NewObject> result = new ArrayList<>(
input
.stream()
.collect(Collectors.toMap(
OldObject::getName,
MyClass::createNewObject,
MyClass::mergeTypes,
LinkedHashMap::new // keep insertion order
))
.values());
result.forEach(System.out::println);
Output:
{ name: n1; types: [type1, type2, type3]}
{ name: n2; types: [type1, type2]}
{ name: n3; types: [type2]}
Related
I have a simple nested structure as such:
public static class A {
private List<B> classBList;
// constructor, getters, etc.
}
public static class B {
private int id;
private String name;
// constructor, getters, etc.
}
I want to create a map of <Integer,List<A>> where the integer field in class B id will be the key, and the A objects in the input that contain the matching id will be rolled up into a list as the value. The input would be a list of class A.
So for example:
Input:
[classBList=[B{id:1, name:"Hello"}, B{id:2, name:"Hi"}],
classBList=[B{id:3, name:"No"}, B{id:3, name:"Go"}],
classBList=[B{id:1, name:"Yes"}]]
Output:
{Key=1, Value=[ A{classBList=[B{id:1, name:"Hello"}, B{id:1, name:"Yes"}]} ]
{Key=2, Value=[ A{classBList=[B{id:2, name:"Hi"}]} ]
{Key=3, Value=[ A{classBList=[B{id:3, name:"No"}, B{id:3, name:"Go"}]} ]
I'm having trouble, however, writing the lambdas that allow this to happen. What I tried:
Map<Integer, List<A>> heyThere = classAListInput.stream()
.collect(Collectors.toMap(
A::getClass,
element -> element.getClassBList().stream()
.map(B::getId)
.collect(Collectors.toList())
));
But this doesn't compile, so really not sure of how the syntax should look.
If you're wondering why not just alter the map so it's <Integer, List< B >>, there are other fields in class A that I didn't note but would be needed in the output, so that's why a list of A objects would be the value in the map.
It seems that you need to rebuild instances of A class with the new list of B.
However, expected output shows that there is only one A entry in the list, and all B's are added to the same A instance:
{Key=2, Value=[ A{classBList=[B{id:2, name:"No"}, B{id:2, name: "Go"}, B{id:2, name:"Yellow"}, B{id:2, name:"Joe"}, B{id:2, name:"Blow"}]} ]
So, the following implementation may be offered assuming there's an all-args constructor in A class accepting List<B>:
Map<Integer, List<A>> result = classAListInput
.stream() // Stream<A>
.flatMap(a -> a.getClassBList().stream()) // Stream<B>
.collect(Collectors.groupingBy(
B::getId,
Collectors.collectingAndThen(
Collectors.toList(), // List<B> flattening all B instances by id
lst -> List.of(new A(lst)) // or Arrays.asList or Collections.singletonList
)
));
Update
As the copies of A class may need to be created with some additional fields in A class and the filtered list of B instances, the following change may be offered using Map.entry (available since Java 9 which has been around for a while).
Also, to address an issue of duplicated entries for the same bKey inside the same A instance, Stream::distinct operation should be applied after mapping to the key field:
Map<Integer, List<A>> aByBKey = classAListInput.stream()
.flatMap(a -> a.getClassBList()
.stream()
.map(B::getBKey)
.distinct()
.map(bk -> Map.entry(bk, getCopy(a, bk)) )
)
.collect(Collectors.groupingBy(
Map.Entry::getKey,
Collectors.mapping(Map.Entry::getValue, Collectors.toList())
));
aByBKey.forEach((bKey, a) -> System.out.println(bKey + " -> " + a));
where getCopy is a static method constructing a copy instance of A keeping the code of class A intact:
public static A getCopy(A a, int bKey) {
return new A(
a.aKey,
a.classBList
.stream()
.filter(b -> b.getBKey() == bKey)
.collect(Collectors.toList())
);
}
JDoodle example
If I understood the problem correctly, judging by the sample data, you have a List<List<B>> as an input.
And based on the sample output you've provided, you need to obtain a map of type Map<Integer,A> as a result (not a Map<Integer,List<A>>).
This can be done in the following steps:
flatten the data using flatMap(), i.e. transform a Stream<List<B>> into a Stream<B>;
group the elements by id by the means of collector groupingBy();
collect the elements mapped to the same key into a list and transform them into an object A, which can be done by applying a combination of collectors collectingAndThen() and toList() as the downstream of groupingBy().
That's how it might be implemented:
public static void main(String[] args) {
List<List<B>> classAListInput = List.of(
List.of(new B(1, "Hello"), new B(1, "Hi"), new B(1, "Bye"), new B(1, "Yes")),
List.of(new B(2, "No"), new B(2, "Go"), new B(2, "Yellow")),
List.of(new B(2, "Joe"), new B(2, "Blow"))
);
Map<Integer, A> aById = classAListInput.stream()
.flatMap(Collection::stream) // flattening the data
.collect(Collectors.groupingBy(
B::getId, // grouping by id
Collectors.collectingAndThen(
Collectors.toList(), // accumulating elements into a list
A::new) // instantiating object A based on the List<B>
));
aById.forEach((id, a) -> System.out.println(id + " -> " + a));
}
Output:
1 -> A{classBList=[B{id=1, name='Hello'}, B{id=1, name='Hi'}, B{id=1, name='Bye'}, B{id=1, name='Yes'}]}
2 -> A{classBList=[B{id=2, name='No'}, B{id=2, name='Go'}, B{id=2, name='Yellow'}, B{id=2, name='Joe'}, B{id=2, name='Blow'}]}
A link to Online Demo
Using Java 8 groupingBy:
You can use groupingBy feature as shown below:
Here,
I have used flatMap() to merge all the list of data into a single list.
I have used groupingBy() using id as a grouping element and then before collecting the data i have converted the data into List<A> using Collectors.mapping()
B.java
public class B {
private int id;
private String name;
public B(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public String toString() {
return "B{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
A.java
public class A {
private List<B> classBList;
public A(List<B> classBList) {
this.classBList = classBList;
}
public List<B> getClassBList() {
return classBList;
}
public void setClassBList(List<B> classBList) {
this.classBList = classBList;
}
#Override
public String toString() {
return "A{" +
"classBList=" + classBList +
'}';
}
}
Test.java
public class Test {
public static void main(String[] args) {
List<List<B>> listInput = List.of(
List.of(new B(1, "Hello"), new B(1, "Hi"), new B(1, "Bye"), new B(1, "Yes")),
List.of(new B(2, "No"), new B(2, "Go"), new B(2, "Yellow")),
List.of(new B(2, "Joe"), new B(2, "Blow"))
);
Map<Integer,List<A>> output =
listInput.stream().flatMap(Collection::stream)
.collect(Collectors.groupingBy(B::getId,
Collectors.mapping(x ->
new A(List.of(new B(x.getId(), x.getName())))
,Collectors.toList())));
System.out.println(output);
}
}
Output:
{1=[A{classBList=[B{id=1, name='Hello'}]}, A{classBList=[B{id=1, name='Hi'}]}, A{classBList=[B{id=1, name='Bye'}]}, A{classBList=[B{id=1, name='Yes'}]}],
2=[A{classBList=[B{id=2, name='No'}]}, A{classBList=[B{id=2, name='Go'}]}, A{classBList=[B{id=2, name='Yellow'}]}, A{classBList=[B{id=2, name='Joe'}]}, A{classBList=[B{id=2, name='Blow'}]}]}
You'll need to flat map to some sort of tuple class, like AbstractMap.SimpleEntry, so you can stream A and B in parallel and then invert the grouping:
classAListInput.stream()
.flatMap(a -> a.getClassBList()
.stream()
.map(b -> new SimpleEntry<>(b.getId(), a)))
.collect(groupingBy(Entry::getKey, mapping(Entry::getValue, toList())))
I wanna get rid of method and fold lists in stream operation which return map, how I can do it?
enum Type {
type1, type2
}
public List<String> getList(Type type){
return Arrays.stream(values())
.filter(some->condition)
.filter(var -> var.equals(type))
.sorted()
.collect(Collectors.toList());
}
// I want to move this logic into stream which is above it
public Map<Type,List<String>> getResultMap(){
Map<Type,List<String>> map = new HashMap<>();
map.put(type1, getList(type1);
map.put(type2, getList(type2);
return map;
}
I expect method which returns map collected two list with two keys:
public Map<Type,List<String>> getResultMap(){
return // do here logic from getList + getResultMap
.collect(Collectors.toMap);
}
It is unclear exactly what you want so I propose the following.
enum Type {
TYPE1, TYPE2 // enum constants are usually upper case by convention
}
static class SomeClass {
Type type;
String someValue;
public SomeClass(Type type, String value) {
this.type = type;
this.someValue = value;
}
public String getValue() {
return someValue;
}
public Type getType() {
return type;
}
public String toString() {
return String.format("{%s, %s}", type, someValue);
}
}
Here is some arbitrary List of SomeClass where each instance could contain either TYPE1 or TYPE2
List<SomeClass> list = List.of(
new SomeClass(Type.TYPE1, "A"),
new SomeClass(Type.TYPE2, "B"),
new SomeClass(Type.TYPE2, "C"),
new SomeClass(Type.TYPE2, "D"),
new SomeClass(Type.TYPE1, "E"),
new SomeClass(Type.TYPE1, "F"),
new SomeClass(Type.TYPE1, "G"));
now just stream the list create a map keyed by type using the groupingBy() collector.
Map<Type, List<SomeClass>> map = list.stream()
.collect(Collectors.groupingBy(SomeClass::getType));
map.entrySet().forEach(System.out::println);
prints
TYPE2=[{TYPE2, B}, {TYPE2, C}, {TYPE2, D}]
TYPE1=[{TYPE1, A}, {TYPE1, E}, {TYPE1, F}, {TYPE1, G}]
If you only want a specific value of the class in the map then you can do it like this. The second Collector, maps the class to the specific type you want (in this case a String returned by getValue()).
Map<Type, List<String>> map = list.stream()
.collect(Collectors.groupingBy(SomeClass::getType,
Collectors.mapping(SomeClass::getValue,
Collectors.toList())));
map.entrySet().forEach(System.out::println);
prints
TYPE1=[A, E, F, G]
TYPE2=[B, C, D]
Let's try this code.
// List<String> list
List<String> list = Arrays.asList("A", "B", "C");
// Stream of Type, using object here as example
Map<Object, String> map = Stream.of("Type A", "Type B", "Type C").collect(
Collectors.toMap(type -> type, type -> {
// getList filter or some other logics
return list.stream().filter(y -> type.contains(y)).findFirst().orElse(null);
}));
I have such collection: Map<Integer, List<MyObject>> collection
I would like to map the whole list of MyObject to MyObjectDTO and return the whole map with the mapped list.
So return will be:
Map<Integer, List<MyObjectDto>> collectionWithDtos
What is the easiest and quickest way? I've checked a couple of ways with streams but none of that produced results as I expected.
Thanks
This is a way to go with the following simple call:
Map<Integer, List<MyObjectDto>> mappedCollection = collection.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> e.getValue()
.stream()
.map(myObject -> new MyObjectDto()) // mapping here
.collect(Collectors.toList())));
Basically, you want to collect it into the same-structured map with the same key. Stream the set of entries Set<Map.Entry<Integer, List<MyObject>>> and map it into a new map using Collectors.toMap(Function, Function) where:
Key is the same key: entry -> entry.getKey()
Value is the same value (List), except all MyObject objects are mapped into MyObjectDto, which can be performed with another stream.
As long as we don't know the structures of the objects to be mapped, you have to add it by yourself to the line with a comment.
With a for-loop and streams:
private Map<Integer, List<MyObjectDto>> mapNestedListToDto(Map<Integer, List<MyObject>> collection) {
Map<Integer, List<Dto>> collectionWithDtos = new HashMap<>();
for (Integer i : collection.keySet()) {
List<Dto> dtos = collection.get(i).stream().map(myObject -> mappingFunction(myObject)).collect(Collectors.toList());
mapped.put(i, dtos);
}
return collectionWithDtos;
}
Where mappingFunction() is the method that will actually convert an instance of MyObject to an instance of MyObjectDTO.
You can create a new HashMap where you will put the old keys with a new values ( values will be the Dto created based on the original objects )
class Person {
String name ;
public Person(String s) {
this.name=s;
}
#Override
public String toString() {
// TODO Auto-generated method stub
return name;
}
}
class PersonDto {
String name;
public PersonDto(Person p) {
this.name=p.name+"Dto";
}
#Override
public String toString() {
// TODO Auto-generated method stub
return name;
}
}
public class Main {
public static void main(String[] args) {
// your initial HashMap
Map<Integer, List<Person>> map = new HashMap<>();
Person x =new Person("X");
Person y =new Person("Y");
List<Person> lst1 = new ArrayList<>();
lst1.add(x);
List<Person> lst2 = new ArrayList<>();
lst2.add(y);
map.put(1, lst1);
map.put(2, lst2);
// create a new HashMap<Integer, List<MyObjectDto>>
Map<Integer, List<PersonDto>> mapDto = new HashMap<>();
// Simply use forEach
// in the next line instead of "new PersonDto(e)" you will put your mapping method
map.forEach((k,v)->{
mapDto.put(k, v.stream().map(e-> new PersonDto(e)).collect(Collectors.toList()));
});
System.out.println(map);
System.out.println(mapDto);
}
}
Output :
{1=[X], 2=[Y]}
{1=[XDto], 2=[YDto]}
Is it possible use Collectors.groupingBy() with Collectors.counting() to count to the field of a custom object instead of creating a map and transforming it afterwards.
I have a list of users, like this:
public class User {
private String firstName;
private String lastName;
// some more attributes
// getters and setters
}
I want to count all users with the same first and last name. Therefore I have custom object looking like this:
public static class NameGroup {
private String firstName;
private String lastName;
private long count;
// getters and setters
}
I can collect the name groups using this:
List<NameGroup> names = users.stream()
.collect(Collectors.groupingBy(p -> Arrays.asList(p.getFirstName(), p.getLastName()), Collectors.counting()))
.entrySet().stream()
.map(e -> new NameGroup(e.getKey().get(0), e.getKey().get(1), e.getValue()))
.collect(Collectors.toList());
With this solution I have to group the users first to a map and transform it afterwards to my custom object. Is it possible to count all names directly to nameGroup.count to avoid iterating twice over the list (or map) and improve the performance?
You could collect directly to NameGroup.count, but it would be less efficient than what you have, not more.
Internally, the map is being used to maintain a data structure that can efficiently track the name combinations and map them to counts which are updated as more matches are found. Reinventing that data structure is painful and unlikely to result in meaningful improvements.
You could try to collect NameGroups directly in the map instead of going via a count, but most approaches for that would, again, be more expensive than what you have now, and certainly much more awkward.
Honestly: what you have now is perfectly good, and not inefficient in any ways that are important. You should almost certainly stick to what you have.
Not very clean but you can possibly do it as :
List<NameGroup> convertUsersToNameGroups(List<User> users) {
return new ArrayList<>(users.stream()
.collect(Collectors.toMap(p -> Arrays.asList(p.getFirstName(), p.getLastName()),
p -> new NameGroup(p.getFirstName(), p.getLastName(), 1L),
(nameGroup1, nameGroup2) -> new NameGroup(nameGroup1.getFirstName(), nameGroup1.getLastName(),
nameGroup1.getCount() + nameGroup2.getCount()))).values());
}
You can minimize allocations of intermediate objects, e.g. all the Arrays.asList(...) objects, by build a map yourself, instead of using streaming.
This relies on the fact that your NameGroup is mutable.
To even make the code simpler, lets add two helpers to NameGroup:
public static class NameGroup {
// fields here
public NameGroup(User user) {
this.firstName = user.getFirstName();
this.lastName = user.getLastName();
}
public void incrementCount() {
this.count++;
}
// other constructors, getters and setters here
}
With that in place, you can implement the logic like this:
Map<User, NameGroup> map = new TreeMap<>(Comparator.comparing(User::getFirstName)
.thenComparing(User::getLastName));
users.stream().forEach(user -> map.computeIfAbsent(user, NameGroup::new).incrementCount());
List<NameGroup> names = new ArrayList<>(map.values());
Or if you don't actually need a list, the last line can be simplified to:
Collection<NameGroup> names = map.values();
public static class NameGroup {
// ...
#Override
public boolean equals(Object other) {
final NameGroup o = (NameGroup) other;
return firstName.equals(o.firstName) && lastName.equals(o.lastName);
}
#Override
public int hashCode() {
return Objects.hash(firstName, lastName);
}
#Override
public String toString() {
return firstName + " " + lastName + " " + count;
}
}
public static void main(String[] args) throws IOException {
List<User> users = new ArrayList<>();
users.add(new User("fooz", "bar"));
users.add(new User("fooz", "bar"));
users.add(new User("foo", "bar"));
users.add(new User("foo", "bar"));
users.add(new User("foo", "barz"));
users.stream()
.map(u -> new NameGroup(u.getFirstName(), u.getLastName(), 1L))
.reduce(new HashMap<NameGroup, NameGroup>(), (HashMap<NameGroup, NameGroup> acc, NameGroup e) -> {
acc.compute(e, (k, v) -> v == null ? e : new NameGroup(e.firstName, e.lastName, e.count + acc.get(e).count));
return acc;
}, (a, b) -> {
b.keySet().forEach(e -> a.compute(e, (k, v) -> v == null ? e : new NameGroup(e.firstName, e.lastName, e.count + a.get(e).count)));
return a;
}).values().forEach(x -> System.out.println(x));
}
This will output
fooz bar 2
foo barz 1
foo bar 2
I don't know what your requirements are but I modified your NameGroup class to accept a User class instead of first and last names. This then negated the need for for selecting the values from the intermediate stream of List and just from a stream of User. But it still requires the map.
List<NameGroup> names =
users.stream().collect(Collectors.groupingBy(p -> p,Collectors.counting()))
.entrySet().stream()
.map(e -> new NameGroup(e.getKey(), e.getValue())).collect(
Collectors.toList());
Let's say I have this code:
Map<String, String> map;
// later on
map.entrySet().stream().map(MyObject::new).collect(Collectors.toList());
And I have a MyObject Constructor which takes two arguments of type String.
I want to be able to do this but I cannot.
I know I can do e -> new MyObject(e.getKey(), e.getValue()) but prefer MyObject::new.
Similar code works for Set<String> and List<String> with one argument constructor of MyObject class.
use a lambda:
map.entrySet()
.stream()
.map(e -> new MyObject(e.getKey(), e.getValue()))
.collect(Collectors.toList());
otherwise the only way to use a method reference is by creating a function as such:
private static MyObject apply(Map.Entry<String, String> e) {
return new MyObject(e.getKey(), e.getValue());
}
then do something like:
map.entrySet()
.stream()
.map(Main::apply)
.collect(Collectors.toList());
Where Main is the class containing the apply method.
map.entrySet().stream().map(MyObject::new).collect(Collectors.toList()));
And I have a MyObject Constructor which takes two arguments of type String. I want to be able to do this but I cannot.
In map.entrySet().stream().map(...), Java is expecting a Function,
mapping one value to another value.
One value.
From the stream, the function receives a value of type Map.Entry<String, String>,
but your constructor takes two String arguments.
Java doesn't automagically expand a Map.Entry<String, String> to a pair of Strings to pass to your constructor.
You have to do that unwrapping manually.
The problem with the constructor is that it defines two parameters, while Function#apply demanded by Stream#map accepts only one.
You could write a static factory method for MyObject
class MyObject {
public static MyObject of(Map.Entry<String, String> e) {
return new MyObject(e.getKey(), e.getValue());
}
}
and refer to it like
map(MyObject::of)
Before you do so, ask yourself if one pretty-looking line in a plain processing chain somewhere is worthy of a new constructor or utility method.
Add a Map.Entry constructor
class MyObject {
private final String k;
private final String v;
MyObject(String s1, String s2) {
k = s1;
v = s2;
}
MyObject(Map.Entry<String, String> e) {
this(e.getKey(), e.getValue());
}
public String toString() {
return "key: " + k + ' ' + "value: " + v;
}
}
You will be able to call
List<MyObject> myObjectList = map.entrySet().stream().map(MyObject::new).collect(Collectors.toList());