SonarLint: Replace this lambda with a method reference - java

I have a collection that contains a list of errors. I wanted to group these by a key (UUID UserId). For this I have copied the code from this answer:
https://stackoverflow.com/a/30202075/4045364
Collection<FilterError> filterErrors = new ArrayList<FilterError>();
// ... some filterErrors get added to the collection ...
return filterErrors.stream().collect(Collectors.groupingBy(w -> w.getUserId()));
Sonar Lint gives me the following error:
Replace this lambda with a method reference. ->
What I have tried:
Based on these question: SONAR: Replace this lambda with a method reference and Runable Interface : Replace this lambda with a method reference. (sonar.java.source not set. Assuming 8 or greater.)
filterErrors.stream().collect(Collectors.groupingBy(this::getUserId()));
Based on this question: Replace this lambda with method reference 'Objects::nonNull'
filterErrors.stream().collect(Collectors.groupingBy(UUID::getUserId()));
Both give the error:
The target type of this expression must be a functional interface
Is there a way I can resolve this SonarLint issue?

You need to use the class name of the object being targeted by the stream.
Example:
List<String> list = ...;
list.stream().collect(Collectors.groupingBy(String::toUpperCase));
so in your case:
FilterError::getUserId

In my case previously it was like this -
whitelist0.stream().filter(whitelistEntry -> !whitelistEntry.isEmpty()).map(s -> WhitelistEntry.of(s)).collect(Collectors.toList()));
As I need to pass a value to the function, so I did the following to replace the lambda with method reference -
whitelist0.stream().filter(whitelistEntry -> !whitelistEntry.isEmpty()).map(WhitelistEntry :: of).collect(Collectors.toList()));

Related

Using Method Cast instead of Lambda on List with Objects

I am working on fixing some Sonar - Codesmells in my project at the moment.
Sonar is pointing me at this method:
protected List<Long> rolesIdsFromPortalSpecificAuthorizations(final List<PortalSpecificAuthorization> portalSpecificAuthorizations) {
return portalSpecificAuthorizations.stream().map(portalSpecificAuthorization -> portalSpecificAuthorization.getId()).collect(Collectors.toList());
}
It says:
Replace this lambda with a method reference.
The PortalSpecificAuthorization is an object from my own coding. I was not able to find an easy-to-use solution for this case with Method casts.
What is the correct usage in this case?
Sonar wants a Method Reference instead of the lambda.
Replace this part:
portalSpecificAuthorization -> portalSpecificAuthorization.getId()
with this
PortalSpecificAuthorization::getId
More about Method References: https://www.javatpoint.com/java-8-method-reference
Full Code:
protected List<Long> rolesIdsFromPortalSpecificAuthorizations(final List<PortalSpecificAuthorization> portalSpecificAuthorizations) {
return portalSpecificAuthorizations.stream()
.map(PortalSpecificAuthorization::getId)
.collect(Collectors.toList());
}

Unused method parameters should be removed (squid:S1172) false positive using lambda

I have a working method that uses parameter as a lambda expression
private BiConsumer<List<String>, Properties> handleList(Properties p) {
return (list, prop) -> executeSubList(list, prop);
}
For p I'm getting a false positive warning from SonarLint
Unused method parameters should be removed (squid:S1172)
If I change prop to p I'm getting a compile error
Lambda expression's parameter p cannot redeclare another local variable defined in an enclosing scope
Is there a real issue or is it a false positive check when using a method parameter as a lambda parameter?
The issue is real. This method returns a BiConsumer that runs executeSubList on a pair or List<String> and Properties arguments, but does so regardless of p. You could just remove it:
private BiConsumer<List<String>, Properties> handleList() {
// p was never used, and can just be removed -------^
return (list, prop) -> executeSubList(list, prop);
}
The accepted answer is totally correct.
However, another take on solving this problem is to create a Consumer instead of a BiConsumer. The parameter p would then be used as the parameter to executeSubList():
private Consumer<List<String>> handleList(Properties p) {
return (list) -> executeSubList(list, p);
}
Whether this or the solution provided in the accepted answer is the best way to go is dependant on how the surrounding code looks and how the method is to be used.

Can Java 8 overloads differentiate between function parameter argument types?

I am injecting a kotlin class into my java code. The kotlin class has two methods with nearly identical signatures:
fun <R> isFluxAuthorizedFor(
strings: List<StringRequest>,
then: (responses: List<StringResult>) -> Flux<R>
): Flux<R>
and
fun <R> isFluxAuthorizedFor(
string: StringRequest,
then: (per: StringResult) -> Flux<R>
): Flux<R> {
The kotlin class supports this overloading just fine.
However, I'm having a devil of a time getting my IDE to use the correct method. I have a method which matches the signature of the former:
private Flux<AuthorizedStrings> collectResults(List<StringResult> responses)
{
//not yet implemented
return null;
}
And yet, when I try and call the injected class' method, I get compilation errors:
List<StringRequest> allStrings = new ArrayList<StringRequest>();
Flux<UserReadAuthorizations> test = authCheck.isFluxAuthorizedFor(allStrings, (it) -> this.collectResults(it) );
The IDE makes two suggestions:
"change type of 'it' to 'List<StringResult>'"
"change method 'collectResults(List<StringResult>)' to 'collectResults(StringResult)'"
both of which imply that Java (or at least the compiler) can't figure out that I'm trying to call the other method. Is this a problem trying to integrate Java 8 and Kotlin? A quirk of the IDE? (I'm using Spring Tool Suite) Some silly user error that I've not yet been able to rubber-duck through?
I played with your code and found that IntelliJ tripped over the type of the lambda as a whole. I had to cast it -> this.collectResults(it) to the type Kotlin was expecting:
List<StringRequest> allStrings = new ArrayList<>();
Flux<UserReadAuthorizations> test = authCheck.isFluxAuthorizedFor(
allStrings,
(Function1<List<StringResult>, Flux<AuthorizedStrings>>) (it -> this.collectResults(it))
);
The signature of your methods themselves was not an issue. Hope this helps you in STS as well.

Pass Two Parameters In a function using lambda

I am very new to lambdas in Java.
I have started using it as i found them quite interesting
but i still don't know how to use them completely
I have a list of uuids and for each uuid i want to call a function which takes two parameters : first is a string and second is uuid
I am passing a constant string for each uuid
I have written a following code but its not working
uuids.stream()
.map(uuid -> {"string",uuid})
.forEach(AService::Amethod);
It is method which is another class AService
public void Amethod(String a, UUID b) {
System.out.println(a+b.toString());
}
A lambda expression has a single return value, so {"string",uuid} doesn't work.
You could return an array using .map(uuid -> new Object[]{"string",uuid}) but that won't be accepted by your AService::Amethod method reference.
In your example you can skip the map step:
uuids.stream()
.forEach(uuid -> aservice.Amethod("string",uuid));
where aservice is the instance of AService class on which you wish to execute the Amethod method.
uuids.stream().forEach(uuid -> AService.Amethod("string", uuid));
You can write something closer to your current code given a Pair class, but 1) you end up with more complicated code; 2) Java standard library doesn't have one built-in. Because of 2), there are quite a few utility libraries which define one. E.g. with https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/tuple/Pair.html, it would be
uuids.stream()
.map(uuid -> Pair.of("string",uuid))
.forEach(pair -> AService.Amethod(pair.getLeft(), pair.getRight()));

Java Eclipse Lambda function error

The following code produces an error for me in Eclipse Luna and prevents from running.
jdbcTemplate.query( query, r -> {
Folder folder = new Folder();
folder.setId(r.getLong("id"));
folder.setFolderName(r.getString("folder_name"));
folder.setFullPath(r.getString("full_path"));
folders.add(folder);
}, folderId);
return folders;
}
The error occurs at jdbcTemplate.query
The method query(String, ResultSetExtractor<Object>, Object[]) is ambiguous for the type JdbcTemplate
Error does not occur when I package file as jar and run.
Thanks for any guidance or suggestions.
There are three query methods in JdbcTemplate, as follows:
query(String, ResultSetExtractor<T>);
query(String, RowCallbackHandler);
query(String, RowMapper<T>);
In all three, the second argument can be implemented as a lambda, and the two that have a generic <T> parameter will return something. Eclipse is complaining that it can't tell the difference between the first and the third method you are referring to, so you need to specify the type of the functional interface that the lambda is replacing. Additionally, as Jack Ammo says, you need to return an object from the lambda to satisfy the interface. Don't add the returned object to your list of folders inside the lambda, either. So, you'd be looking at something like this:
Folder folder = jdbcTemplate.query( query, (ResultSetExtractor<Folder>) r -> {
Folder folder = new Folder();
folder.setId(r.getLong("id"));
folder.setFolderName(r.getString("folder_name"));
folder.setFullPath(r.getString("full_path"));
return folder;
}, folderId);
folders.add(folder);
return folders;
according to the javadoc for ResultSetExtractor, the method you're implementing with your lambda expression is Object extractData(ResultSet rs). extractData returns an Object so your lambda also has to return an Object in order to properly implement it.

Categories