I'm try to convert a list to a map using the Collectors.toMap call. The list consists of ActivityReconcile objects. I want to pass an instance for every entry in the list into the toMap call.
The code is below and where I need the instances is denoted by ??.
final List<ActivityReconcile> activePostedList = loader.loadActivePosted(accessToken);
Map<AccountTransactionKey, ActivityReconcile> postedActiveMap =
activePostedList.stream().collect(
Collectors.toMap(
AccountTransactionKey.createNewAccountTransactionKeyFromActivityReconcileRecord(??),??));
If I understood you correctly, you will need something like
Map<AccountTransactionKey, ActivityReconcile> result = choices
.stream()
.collect(Collectors.toMap(
AccountTransactionKey::generate,
Function.identity()));
And the method (in AccountTransactionKey class) will look like
public static AccountTransactionKey generate(ActivityReconcile reconcile) {...}
I've replaced createNewAccountTransactionKeyFromActivityReconcileRec by generate for making the answer more readable and understandable.
To "fix" your code with the least changes, add a lambda parameter:
activePostedList.stream().collect(Collectors.toMap(
ar -> AccountTransactionKey.createNewAccountTransactionKeyFromActivityReconcileRecord(ar)),
o -> o));
or use a method reference:
activePostedList.stream().collect(Collectors.toMap(
AccountTransactionKey::createNewAccountTransactionKeyFromActivityReconcileRecord, o -> o));
btw, I can't recall ever seeing a method name as long as createNewAccountTransactionKeyFromActivityReconcileRecord - for readability, consider reducing it to just create(), since the return type and parameter type are enough to distinguish it from other factory methods you may have.
Related
I have an optional Optional<TypeA> from which I can do a map function and get Optional<List<TypeB>>.
But I would like to now map it to Optional<List<TypeC>> and then orElse() it. So putting in pseudo code
Optional.ofNullable(fooReturnsTypeA()).map(TypeA::barReturnsListTypeB()).{?}.orElse(new ArrayList<TypeC>());
I know how to use map function, but it's been a while dealing with Optional with map. So, I might be just staring at the answer. Could someone help me with {?} part?
Regards,
What about this?
Optional.ofNullable(fooReturnsTypeA())
.map(TypeA::barReturnsListTypeB())
.map(typeBList->typeBList
.stream()
.map(TypeB::barReturnsTypeC)
.collect(Collectors.toList())
)
.orElse(new ArrayList<TypeC>())
This basically creates a new Stream in .map and uses it to convert all elements of TypeB to TypeC (assuming TypeB has a method barReturnsTypeC without parameters that returns an instance of TypeC).
You can use another map and write method to convert List<TypeB> to List<TypeC>.
You're code will look like this:
Optional.ofNullable(fooReturnsTypeA())
.map(TypeA::barReturnsListTypeB())
.map(this::convert)
.orElse(new ArrayList<TypeC>());
And your method to convert:
private List<TypeC> convert(List<TypeB> list) {
// conversion logic
// return List<TypeC>
}
First, I would replace the orElse with orElseGet. Depending on your code, using orElseGet instead of orElse can improve your performance. The parameter of orElse is evaluated even when the Optional is non-empty. On the other hand, the parameter of orElseGet is evaluated only when the Optional is empty. In your specific case, you may be instantiating unnecessary ArrayLists. From the Javadoc:
Supplier method passed as an argument is only executed when an
Optional value is not present.
You can also dismiss the {?} by using a stream in the map.
Optional.ofNullable(fooReturnsTypeA())
.map(typeA -> typeA.barReturnsListTypeB().stream()
.map(TypeB::barReturnsTypeC).collect(Collectors.toList()))
.orElseGet(() -> new ArrayList<TypeC>());
Note: see this for more information about the difference between orElse and orElseGet.
I have already look at this questions, but my problem is a little different.
I have a "baseString", n HashMap and an output string.
I want to fill the baseString with the hashmap, to construct a json call parameters.
I have already done it with Java 7, in this way:
HashMap<String,Integer> element= getAllElement();
String baseElem="{\"index\":{}}\r\n" +
"{\"name\":\"$name$\",\"age\":$age$}";
String result=baseElem;
for (Map.Entry<String, Integer> entry : element.entrySet()) {
result=result.replace("$name$", entry.getKey());
result=result.replace("$age$", entry.getValue().toString());
result=result+baseElem;
}
result= result.replace(baseElem, "");
Now I want to the same with Java 8,
I have tried in this way:
element.forEach((k,v)->{
result=result.replaceAll("$name$", k);
result=result.replaceAll("$age$", v.toString());
result=result+baseElem;
});
But for each result I have an error
"Local variable result defined in an enclosing scope must be final or
effectively final"
So the question is: I can do that in some kind of way with Java 8 and streams? Or there is no way, and so I can use the simple Java 7 for?
Your approach is going entirely into the wrong direction. This is not only contradicting the functional programming style, the Stream API adopts. Even the loop is horribly inefficient, performing repeated string concatenation and replace operations on the growing result string.
You did a Shlemiel the painter’s algorithm
You actually only want to perform a replace operation on the baseElem template for each map entry and join all results. This can be expressed directly:
Map<String,Integer> element = getAllElement();
String baseElem = "{\"index\":{}}\r\n{\"name\":\"$name$\",\"age\":$age$}";
String result = element.entrySet().stream()
.map(e -> baseElem.replace("$name$", e.getKey())
.replace("$age$", e.getValue().toString()))
.collect(Collectors.joining());
As for "Local variable result defined in an enclosing scope must be final or effectively final" see this answer for further explanation.
As for:
So the question is: I can do that in some kind of way with Java 8 and
streams? Or there is no way, and so I can use the simple Java 7 for?
The logic you're performing with the iterative approach is known as "fold" or "reduce" in the functional world i.e. streams.
So, what you want to do is:
String result = element.entrySet()
.stream()
.reduce(baseElem,
(e, a) -> e.replace("$name$", a.getKey()).replace("$age$",
a.getValue().toString()),
(x, y) -> {
throw new RuntimeException();
});
the third input argument to reduce is known as the combiner function which should be an associative, non-interfering, stateless function for combining two values, which must be compatible with the accumulator function.
if you don't plan on using a parallel stream then the current logic should suffice otherwise you'll need to replace (x, y) -> {...} with the actual logic.
It think, you could use this one:
private static Map<String, Integer> getAllElement() {
Map<String, Integer> map = new HashMap<>();
map.put("\\$name\\$", 666);
map.put("\\$age\\$", 777);
return map;
}
Map<String, Integer> map = getAllElement();
String[] json = { "{\"index\":{}}\r\n{\"name\":\"$name$\",\"age\":$age$}" };
map.forEach((key, val) -> json[0] = json[0].replaceAll(key, String.valueOf(val)));
System.out.println(json[0]);
Output:
{"index":{}}
{"name":"666","age":777}
From the docs,
Any local variable, formal parameter, or exception parameter used but
not declared in a lambda expression must either be declared final or
be effectively final (§4.12.4), or a compile-time error occurs where
the use is attempted.
Similar rules on variable use apply in the body of an inner class (§8.1.3). The restriction to effectively final variables prohibits access to dynamically-changing local variables, whose capture would likely introduce concurrency problems. Compared to the final restriction, it reduces the clerical burden on programmers.
What is to be a constant here is the reference, but not the values.
You are getting this exception because, you are changing the reference of result. You are re-assigning result to some point to some other String inside your lambda. Thus, conflicting with the JLS, dynamically-changing local variables
Also, Adding to this, You can use Jackson ObjectMapper for producing JSON from Java object(s) instead of hardcoding and replacing stuff.
Define the result variable outside your method something like below
Class A{
String result=null;
Method a(){
//method implementation
}
}
I want to init a Map<String, BigDecimal> and want to always put the same BigDecimal value from outside of the stream.
BigDecimal samePrice;
Set<String> set;
set.stream().collect(Collectors.toMap(Function.identity(), samePrice));
However Java complains as follows:
The method toMap(Function, Function) in the type Collectors is not applicable for the arguments
(Function, BigDecimal)
Why can't I use the BigDecimal from outside? If I write:
set.stream().collect(Collectors.toMap(Function.identity(), new BigDecimal()));
it would work, but that's of course not what I want.
The second argument (like the first one) of toMap(keyMapper, valueMapper) is a function that takes the stream element and returns the value of the map.
In this case, you want to ignore it so you can have:
set.stream().collect(Collectors.toMap(Function.identity(), e -> samePrice));
Note that your second attempt wouldn't work for the same reason.
Collectors#toMap expects two Functions
set.stream().collect(Collectors.toMap(Function.identity(), x -> samePrice));
You can find nearly the same example within the JavaDoc
Map<Student, Double> studentToGPA
students.stream().collect(toMap(Functions.identity(),
student -> computeGPA(student)));
As already said in the other answers, you need to specify a function which maps each element to the fixed value like element -> samePrice.
As an addition, if you want to specifically fill a ConcurrentHashMap, there is a neat feature that doesn’t need a stream operation at all:
ConcurrentHashMap<String,BigDecimal> map = new ConcurrentHashMap<>();
map.keySet(samePrice).addAll(set);
Unfortunately, there is no such operation for arbitrary Maps.
I'm trying to learn the Java Set interface and have encountered the following code online, I understand the purpose of this code is to convert a Collection<Object> to a TreeSet, but I do not understand how the statement works because the syntax is complex and foreign to me. Could someone walk me through the process step by step?
Set<String> set = people.stream()
.map(Person::getName)
.collect(Collectors.toCollection(TreeSet::new));
And also, under what kind of circumstances should we prefer the above syntax over the one below?
Set<Integer> s1 = new TreeSet(c1); //where c1 is an instance of Collection interface type
people.stream()
Takes a set of people and obtains a stream.
.map(Person::getName)
Takes a stream of people and invokes the getName method on each one, returning a list with all the results. This would be "equivalent" to
for(Person person : people){
setOfNames.add(person.getName())
}
.collect(Collectors.toCollection(TreeSet::new));
Takes a stream of strings and converts it in a set.
The streams are very useful when you need to apply several transformations. They can also perform very well if you make use of parallel streams, since each transformation (in your case each getName) can be done in parallel instead of sequentially.
peopele.stream() create a stream of elements
.map(Person::getName) takes each object from people collection and calls getName , coverts to string then
.collect(Collectors.toCollection(TreeSet::new)) - Collects these String elements and create a TreeSet out of it.
Hope its clear
I have two Lists, for example:
List<Foo> list1 = Lists.newArrayList(new Foo(...),...);
List<Bar> list2 = Lists.newArrayList(new Bar(...),...);
In Bar there is a property, fooId. Suppose list1.size() == list2.size().
I want to set fooIds of the Bar instances in order.
I tried below code:
int index = 0;
list2.forEach(b -> b.fooId = list1.get(index++).getId());
but compilation failed
Local variable index defined in an enclosing scope must be final or effectively final
Does Java 8 have some convenient manner to process this?
You can't modify the index variable from inside the lambda expression.
You can iterate over the indices using an IntStream (though that's not much of an improvement compared to a simple for loop) :
IntStream.range(0,list1.size())
.forEach(i -> list2.get(i).setFooId(list1.get(i).getId()));
You could do:
IntStream.range(0, list1.size())
.forEach(i -> list2.get(i).fooId = list1.get(i).getId());
but do note that this is not good functional code. You would be better to write an explicit for loop and not use Stream.
The code in your question doesn't work because you can't reference a variable inside a lambda expression that isn't final (or effectively final).
If you don't mind about list1 after this operation and its element can be removed, you could also use this:
list2.forEach(bar -> bar.fooId = list1.remove(0).getId());
Unfortunately there is no zip operation in Java 8 streams API. But you can use protonpack library.
List<Bar> upd = StreamUtils.zip(list1.stream(), list2.stream(), (foo, bar) -> {
bar.setId(foo.getId());
return bar;
}).collect(Collectors.toList());
I also want to add that using mutable data objects is an imperative style. In functional style it is preferable to use immutable data and return new objects instead if you need to change anything.
Using my StreamEx library you can reduce the boilerplate:
EntryStream.zip(list1, list2).forKeyValue((foo, bar) -> bar.fooId = foo.getId());
EntryStream.zip creates a stream of Map.Entry<Foo, Bar> for each corresponding pair of list1 and list2. The forKeyValue method accepts BiConsumer (it's a syntactic sugar for forEach). Internally it's close to #Eran solution.