5

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).

smithkm
  • 231
  • 1
  • 9
  • 1
    This 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 Answers1

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