A project I work on has recently switched from Java 7 to Java 8. I'd like to be able to find interfaces that have a single abstract method as candidates for introducing functional interfaces into our code base. (Annotating existing interfaces as @FunctionalInterface
, extending them from the interfaces in java.util.function
, or possibly just replacing them).
Asked
Active
Viewed 291 times
5

smithkm
- 231
- 1
- 9
-
1This is available as an inspection in IntelliJ. Create an inspection profile that has only this inspection, and run it on your project. – Brian Goetz Jan 09 '16 at 01:59
-
As this change would require a case-by-case analysis, and, apart for adding the annotation, the other 2 solutions both require significant refactoring, I wouldn't go for it. If you are not designing a library, adding the annotation does not provide much value either, as you would quickly see compilation issues if you transform an interface into a non-functional one while it has lambda implementations. – Didier L Jan 09 '16 at 13:50
1 Answers
6
The reflections project is able to locate and return all classes on the classpath. Here's a working example:
ReflectionUtils.forNames(new Reflections(new ConfigurationBuilder().setScanners(new SubTypesScanner(false))
.addUrls(ClasspathHelper.forClassLoader()))
.getAllTypes()).stream()
.filter(Class::isInterface)
.collect(toMap(c -> c,
c -> Arrays.stream(c.getMethods())
.filter(m -> !m.isDefault())
.filter(m -> !Modifier.isStatic(m.getModifiers()))
.filter(m -> !isObjectMethod(m))
.collect(toSet())))
.entrySet().stream()
.filter(e -> e.getValue().size() == 1)
.sorted(comparing(e -> e.getKey().toString()))
.map(e -> e.getKey().toString() + " has single method " + e.getValue())//getOnlyElement(e.getValue()))
.forEachOrdered(System.out::println);
The isObjectMethod
helper is defined like this:
private static final Set<Method> OBJECT_METHODS = ImmutableSet.copyOf(Object.class.getMethods());
private static boolean isObjectMethod(Method m){
return OBJECT_METHODS.stream()
.anyMatch(om -> m.getName().equals(om.getName()) &&
m.getReturnType().equals(om.getReturnType()) &&
Arrays.equals(m.getParameterTypes(),
om.getParameterTypes()));
}
This doesn't help you go back to the source code and add the annotations, but it'll give you a list to work from.
As requested in the comments, the imports needed to make this work are:
import static java.util.Comparator.*;
import static java.util.stream.Collectors.*;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Set;
import org.reflections.ReflectionUtils;
import org.reflections.Reflections;
import org.reflections.scanners.SubTypesScanner;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import com.google.common.collect.ImmutableSet;

Matt McHenry
- 20,009
- 8
- 65
- 64
-
What if the interface overrides a method of `Object`, like `Comparator` does? Then, the method should be filtered out. And `static` methods should be removed as well. Or better, just let the filter pass `abstract` methods only. And is there a reason to use `stream(spliterator(c.getMethods(),0), false)` instead of `Arrays.stream(c.getMethods())`? – Holger Jan 11 '16 at 11:00
-
Why not `.filter(m -> Modifier.isAbstract(m.getModifiers()))` instead of `.filter(m -> !m.isDefault()).filter(m -> !Modifier.isStatic(m.getModifiers()))`? – Holger Jan 12 '16 at 09:33
-
By the way, you could replace `.collect(toSet())` by `.reduce( (a,b)->null ).orElse(null)`. Then, you don’t waste resources collecting more than one method into a `Set`, but just get either, the single abstract method or `null` if there isn’t exactly one. – Holger Jan 12 '16 at 09:36
-
You're allowed to leave off the `abstract` modifier when declaring interface methods -- would `Modifier.isAbstract()` still return true in such a case? – Matt McHenry Jan 13 '16 at 21:46
-
I think the `reduce((a,b)->null)` trick is clever, but makes the code less clear. :-/ – Matt McHenry Jan 13 '16 at 21:47
-
Yes, the modifiers returned by `Method.getModifiers()` will reflect what the method *is*, not what has been declared explicitly. So interface methods without any modifiers will be reported to *be* `public abstract`. In the byte code these bits are always set, regardless of whether the modifier keywords appeared in the source code. – Holger Jan 14 '16 at 09:05
-
Not automatically updating things is fine. I wouldn't want it to do that anyway as each case requires consideration. – smithkm Aug 01 '18 at 21:49
-
Is it possible that you add the imports? I'm trying to run the code in eclipse but there are some ambiguous class/interface names and I cannot decide which packages are the correct ones to import them from. – Tulains Córdova Sep 26 '20 at 03:24
-
@TulainsCórdova amazingly I still had the code laying around on my machine, so I've just cut-n-pasted in the imports! :) – Matt McHenry Sep 27 '20 at 15:03