I'm playing with java reflection and learning more about Stream.collect.
I have an annotation MyTag that has two properties (id
and type
enum[Normal|Failure]).
Also, I have a list of annotated methods with MyTag and I was able to group those methods by the id property of the MyTag annotation using Collectors.groupingBy:
List<Method> ml = getMethodsAnnotatedWith(anClass.getClass(),
MyTag.class);
Map<String, List<Method>> map = ml.stream().collect(groupingBy(m -> {
var ann = m.getDeclaredAnnotation(MyTag.class);
return ann.anId();
}, TreeMap::new, toList()));
Now I need to reduce the resulting List to one single object composed of ONLY TWO items of the same MyTag.id, one with a MyTag.type=Normal and the other with a MyTag.type=Failure. So it would result in something like a Map<String, Pair<Method, Method>>. If there are more than two occurrences, I must just pick the first ones, log and ignore the rest.
How could I achieve that ?
You can use
Map<String, Map<Type, Method>> map = Arrays.stream(anClass.getClass().getMethods())
.filter(m -> m.isAnnotationPresent(MyTag.class))
.collect(groupingBy(m -> m.getDeclaredAnnotation(MyTag.class).anId(),
TreeMap::new,
toMap(m -> m.getDeclaredAnnotation(MyTag.class).aType(),
m -> m, (first, last) -> first,
() -> new EnumMap<>(Type.class))));
The result maps the annotations ID property to a Map
from Type
(the enum constants NORMAL
and FAILURE
) to the first encountered method with a matching annotation. Though “first” has not an actual meaning when iterating over the methods discovered by Reflection, as it doesn’t guaranty any specific order.
The () -> new EnumMap<>(Type.class)
map factory is not necessary, it would also work with the general purpose map used by default when you don’t specify a factory. But the EnumMap
will handle your case of having only two constants to map in a slightly more efficient way and its iteration order will match the declaration order of the enum constants.
I think, the EnumMap
is better than a Pair<Method, Method>
that requires to remember which method is associated with “normal” and which with “failure”. It’s also easier to adapt to more than two constants. Also, the EnumMap
is built-in and doesn’t require a 3rd party library.
The following example can easily be adapted to your code:
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
import java.util.stream.Collectors;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Test {
private static final Logger logger = LoggerFactory.getLogger(Test.class);
public static void main(String[] args) {
List<Pair<String, String>> ml = Arrays.asList(
Pair.of("key1", "value1"),
Pair.of("key1", "value1"),
Pair.of("key1", "value2"),
Pair.of("key2", "value1"),
Pair.of("key2", "value3"));
Map<String, Pair<String, String>> map = ml.stream().collect(
Collectors.groupingBy(m -> {
return m.getKey();
}, TreeMap::new, Collectors.toList()))
.entrySet()
.stream()
.collect(Collectors.toMap(
Map.Entry::getKey, e -> convert(e.getValue())));
System.out.println(map.values());
}
private static Pair<String, String> convert(List<Pair<String, String>> original) {
long count1 = original.stream().filter(e -> Objects.equals(e.getValue(), "value1")).count();
long count2 = original.stream().filter(e -> Objects.equals(e.getValue(), "value2")).count();
if (count1 > 1) {
logger.warn("More than one occurrence of value1");
}
if (count2 > 1) {
logger.warn("More than one occurrence of value2");
}
return Pair.of(count1 > 0 ? "value1" : null,
count2 > 0 ? "value2" : null);
}
}
The folowing result is printed to the console:
01:23:27.959 [main] WARN syglass.Test2 - More than one occurrence of value1
[(value1,value2), (value1,null)]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With