Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

[SonarLint]: make this anonymous inner class a lambda

The below code works, but I have notification from SonarLint because I use an anonymous class in stream instead of lambda expression, and I don't see how to improve the below code avoiding the notification:

Properties prop = new Properties();
Properties temp = new Properties();
//... add some values and keys in prop and temp

prop.putAll(temp.entrySet().stream()
    .filter( entry -> !prop.containsKey(entry.getKey()))
    .map( new Function<Entry<Object, Object>, Entry<String, String>>(){ 
        @Override
        public Entry<String, String> apply(Entry<Object, Object> entry) {
            return new Entry<String, String>() {
                @Override
                public String setValue(String value) {
                    return value.trim().toLowerCase();
                }

                @Override
                public String getValue() {
                    return ((String) entry.getValue()).trim().toLowerCase();
                }

                @Override
                public String getKey() {
                    return ((String) entry.getKey()).trim().toLowerCase();
                }
            };
        }
    })
    .collect(Collectors.toMap(Entry<String,String>::getKey, Entry<String,String>::getValue)));

Explication of code: I use the properties class from java.util and unfortunately, the entrySet of properties returns Entry<Object, Object>, not Entry<String, String>. I want to "join" the two properties objects putting key and value in lower case. So, the map allows to convert Entry<Object, Object> in Entry<String,String>. That's why, there is an anonymous class.

like image 986
Nana Ba Avatar asked Jul 25 '17 10:07

Nana Ba


People also ask

How do you define anonymous inner class?

It is an inner class without a name and for which only a single object is created. An anonymous inner class can be useful when making an instance of an object with certain “extras” such as overriding methods of a class or interface, without having to actually subclass a class.

Is lambda expression An anonymous class?

A lambda expression is a short form for writing an anonymous class. By using a lambda expression, we can declare methods without any name.


1 Answers

Sonar is suggesting to replace

prop.putAll(temp.entrySet().stream()
    .filter( entry -> !prop.containsKey(entry.getKey()))
    .map( new Function<Entry<Object, Object>, Entry<String, String>>(){ 
        @Override
        public Entry<String, String> apply(Entry<Object, Object> entry) {
            return new Entry<String, String>() {
                @Override
                public String setValue(String value) {
                    return value.trim().toLowerCase();
                }

                @Override
                public String getValue() {
                    return ((String) entry.getValue()).trim().toLowerCase();
                }

                @Override
                public String getKey() {
                    return ((String) entry.getKey()).trim().toLowerCase();
                }
            };
        }
    })
    .collect(Collectors.toMap(Entry::getKey, Entry::getValue)));

(I removed the unnecessary type arguments in the collector)

with

prop.putAll(temp.entrySet().stream()
    .filter( entry -> !prop.containsKey(entry.getKey()))
    .map(entry -> new Entry<String, String>() { 
        @Override
        public String setValue(String value) {
            return value.trim().toLowerCase();
        }

        @Override
        public String getValue() {
            return ((String) entry.getValue()).trim().toLowerCase();
        }

        @Override
        public String getKey() {
            return ((String) entry.getKey()).trim().toLowerCase();
        }
    })
    .collect(Collectors.toMap(Entry::getKey, Entry::getValue)));

which uses the lambda expression as replacement for the anonymous inner class implementing Function, not for the Entry implementation.

Still, it doesn’t make sense to implement the Entry interface manually here, especially not with the actually unwanted setValue method in this contract violating way. You only want an immutable Entry instance, therefore, you can create an instance of an existing class instead:

prop.putAll(temp.entrySet().stream()
    .filter( entry -> !prop.containsKey(entry.getKey()))
    .map(entry -> new AbstractMap.SimpleImmutableEntry<>(
        ((String) entry.getKey()).trim().toLowerCase(),
        ((String) entry.getValue()).trim().toLowerCase()))
    .collect(Collectors.toMap(Entry::getKey, Entry::getValue)));

as a last improvement, you can get rid of the Entry instance entirely, when performing the transformation in the functions passed to the toMap collector:

prop.putAll(temp.entrySet().stream()
    .filter( entry -> !prop.containsKey(entry.getKey()))
    .collect(Collectors.toMap(
        entry -> ((String) entry.getKey())  .trim().toLowerCase(),
        entry -> ((String) entry.getValue()).trim().toLowerCase())));
like image 50
Holger Avatar answered Sep 27 '22 23:09

Holger