Pregunta

¿Cómo se hace y se intenta encontrar todas las subclases de una clase determinada (o todos los implementadores de una interfaz determinada) en Java? A partir de ahora, tengo un método para hacer esto, pero lo encuentro bastante ineficiente (por decir lo menos). El método es:

  1. Obtenga una lista de todos los nombres de clase que existen en la ruta de clase
  2. Cargue cada clase y pruebe para ver si es una subclase o implementador de la clase o interfaz deseada

En Eclipse, hay una buena característica llamada Jerarquía de tipos que logra mostrar esto de manera bastante eficiente. ¿Cómo se hace y se hace programáticamente?

¿Fue útil?

Solución

No hay otra forma de hacerlo que no sea lo que describiste. Piénselo: ¿cómo puede alguien saber qué clases extienden ClassX sin escanear cada clase en el classpath?

Eclipse solo puede informarle sobre las superclases y subclases en lo que parece ser un & "; eficiente &"; cantidad de tiempo porque ya tiene todos los datos de tipo cargados en el punto donde presiona & "; Mostrar en jerarquía de tipos &"; botón (ya que está compilando constantemente sus clases, sabe todo sobre el classpath, etc.).

Otros consejos

Buscar clases no es fácil con Java puro.

Spring Framework ofrece una clase llamada ClassPathScanningCandidateComponentProvider que puede hacer lo que necesita. El siguiente ejemplo encontraría todas las subclases de MyClass en el paquete org.example.package

ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
provider.addIncludeFilter(new AssignableTypeFilter(MyClass.class));

// scan in org.example.package
Set<BeanDefinition> components = provider.findCandidateComponents("org/example/package");
for (BeanDefinition component : components)
{
    Class cls = Class.forName(component.getBeanClassName());
    // use class cls found
}

Este método tiene la ventaja adicional de usar un analizador de código de bytes para encontrar los candidatos, lo que significa que no cargará todas las clases que escanea.

Esto no es posible hacerlo utilizando solo la API Java Reflections incorporada.

Existe un proyecto que realiza el escaneo e indexación necesarios de su classpath para que pueda acceder a esta información ...

Reflexiones

Un análisis de metadatos en tiempo de ejecución de Java, en el espíritu de Scannotations

Reflections escanea su classpath, indexa los metadatos, le permite consultarlo en tiempo de ejecución y puede guardar y recopilar esa información para muchos módulos dentro de su proyecto.

Usando Reflections puede consultar sus metadatos para:

  • obtener todos los subtipos de algún tipo
  • obtener todos los tipos anotados con alguna anotación
  • obtener todos los tipos anotados con alguna anotación, incluida la coincidencia de parámetros de anotación
  • obtener todos los métodos anotados con algunos

(descargo de responsabilidad: no lo he usado, pero la descripción del proyecto parece ajustarse exactamente a sus necesidades).

No olvide que el Javadoc generado para una clase incluirá una lista de subclases (y para interfaces, clases de implementación conocidas).

Hice esto hace varios años. La forma más confiable de hacer esto (es decir, con API oficiales de Java y sin dependencias externas) es escribir un doclet personalizado para producir una lista que se pueda leer en tiempo de ejecución.

Puede ejecutarlo desde la línea de comando de esta manera:

javadoc -d build -doclet com.example.ObjectListDoclet -sourcepath java/src -subpackages com.example

o ejecutarlo desde una hormiga como esta:

<javadoc sourcepath="${src}" packagenames="*" >
  <doclet name="com.example.ObjectListDoclet" path="${build}"/>
</javadoc>

Aquí está el código básico:

public final class ObjectListDoclet {
    public static final String TOP_CLASS_NAME =  "com.example.MyClass";        

    /** Doclet entry point. */
    public static boolean start(RootDoc root) throws Exception {
        try {
            ClassDoc topClassDoc = root.classNamed(TOP_CLASS_NAME);
            for (ClassDoc classDoc : root.classes()) {
                if (classDoc.subclassOf(topClassDoc)) {
                    System.out.println(classDoc);
                }
            }
            return true;
        }
        catch (Exception ex) {
            ex.printStackTrace();
            return false;
        }
    }
}

Para simplificar, eliminé el análisis de argumentos de la línea de comandos y escribo en System.out en lugar de un archivo.

Sé que llego unos años tarde a esta fiesta, pero me encontré con esta pregunta tratando de resolver el mismo problema. Puede utilizar la búsqueda interna de Eclipse mediante programación, si está escribiendo un complemento de Eclipse (y, por lo tanto, aprovecha su almacenamiento en caché, etc.), para encontrar clases que implementen una interfaz. Aquí está mi primer corte (muy áspero):

  protected void listImplementingClasses( String iface ) throws CoreException
  {
    final IJavaProject project = <get your project here>;
    try
    {
      final IType ifaceType = project.findType( iface );
      final SearchPattern ifacePattern = SearchPattern.createPattern( ifaceType, IJavaSearchConstants.IMPLEMENTORS );
      final IJavaSearchScope scope = SearchEngine.createWorkspaceScope();
      final SearchEngine searchEngine = new SearchEngine();
      final LinkedList<SearchMatch> results = new LinkedList<SearchMatch>();
      searchEngine.search( ifacePattern, 
      new SearchParticipant[]{ SearchEngine.getDefaultSearchParticipant() }, scope, new SearchRequestor() {

        @Override
        public void acceptSearchMatch( SearchMatch match ) throws CoreException
        {
          results.add( match );
        }

      }, new IProgressMonitor() {

        @Override
        public void beginTask( String name, int totalWork )
        {
        }

        @Override
        public void done()
        {
          System.out.println( results );
        }

        @Override
        public void internalWorked( double work )
        {
        }

        @Override
        public boolean isCanceled()
        {
          return false;
        }

        @Override
        public void setCanceled( boolean value )
        {
        }

        @Override
        public void setTaskName( String name )
        {
        }

        @Override
        public void subTask( String name )
        {
        }

        @Override
        public void worked( int work )
        {
        }

      });

    } catch( JavaModelException e )
    {
      e.printStackTrace();
    }
  }

El primer problema que veo hasta ahora es que solo estoy captando clases que implementan directamente la interfaz, no todas sus subclases, pero una pequeña recursión nunca lastima a nadie.

Teniendo en cuenta las limitaciones mencionadas en las otras respuestas, también puede usar openpojo's PojoClassFactory ( disponible en Maven ) de la siguiente manera :

for(PojoClass pojoClass : PojoClassFactory.enumerateClassesByExtendingType(packageRoot, Superclass.class, null)) {
    System.out.println(pojoClass.getClazz());
}

Donde packageRoot es la cadena raíz de los paquetes en los que desea buscar (por ejemplo, "com.mycompany" o incluso "com"), y Superclass es su supertipo (esto también funciona en las interfaces).

Pruebe ClassGraph . (Descargo de responsabilidad, yo soy el autor). ClassGraph admite la exploración de subclases de una clase determinada, ya sea en tiempo de ejecución o en tiempo de compilación, pero también mucho más. ClassGraph puede construir una representación abstracta de todo el gráfico de clase (todas las clases, anotaciones, métodos, parámetros de método y campos) en la memoria, para todas las clases en el classpath, o para clases en paquetes en la lista blanca, y puede consultar ese gráfico de clase sin embargo usted quiere. ClassGraph admite más mecanismos de especificación de classpath y cargadores de clases que cualquier otro escáner, y también funciona a la perfección con el nuevo sistema de módulos JPMS, por lo que si basa su código en ClassGraph, su código será máximamente portátil. Consulte la API aquí.

También debe tenerse en cuenta que esto, por supuesto, solo encontrará todas las subclases que existen en su classpath actual. Presumiblemente, esto está bien para lo que está viendo actualmente, y es probable que lo haya considerado, pero si en algún momento ha liberado una clase que no sea final en estado salvaje (para niveles variables de & Quot; wild < !> quot;) entonces es completamente factible que alguien más haya escrito su propia subclase que no conocerá.

Por lo tanto, si desea ver todas las subclases porque desea realizar un cambio y verá cómo afecta el comportamiento de las subclases, tenga en cuenta las subclases que no puede ver. Idealmente todos sus métodos no privados, y la clase en sí misma debe estar bien documentada; realice cambios de acuerdo con esta documentación sin cambiar la semántica de los métodos / campos no privados y sus cambios deben ser compatibles con versiones anteriores, para cualquier subclase que siga su definición de la superclase al menos.

La razón por la que ve una diferencia entre su implementación y Eclipse es porque escanea cada vez, mientras que Eclipse (y otras herramientas) escanea solo una vez (durante la carga del proyecto la mayoría de las veces) y crea un índice. La próxima vez que solicite los datos, no volverá a escanear, pero mire el índice.

Acabo de escribir una demostración simple para usar el org.reflections.Reflections para obtener subclases de clase abstracta:

https://github.com/xmeng1/ReflectionsDemo

Dependiendo de sus requisitos particulares, en algunos casos, Java el mecanismo del cargador de servicios podría lograr lo que buscas.

En resumen, permite a los desarrolladores declarar explícitamente que una clase subclasifica alguna otra clase (o implementa alguna interfaz) al incluirla en un archivo en el directorio META-INF/services del archivo JAR / WAR. Luego se puede descubrir usando el java.util.ServiceLoader que, cuando se le da un objeto Class, generará instancias de todas las subclases declaradas de esa clase (o, si ServiceLoader representa una interfaz, todas las clases que implementan esa interfaz).

La principal ventaja de este enfoque es que no es necesario escanear manualmente todo el classpath en busca de subclases: toda la lógica de descubrimiento está contenida dentro de la clase ServiceLoader.iterator(), y solo carga las clases declaradas explícitamente en el com.example.Example directorio (no todas las clases en el classpath).

Sin embargo, existen algunas desventajas:

  • No encontrará todas subclases, solo aquellas que se declaren explícitamente. Como tal, si realmente necesita encontrar todas las subclases, este enfoque puede ser insuficiente.
  • Requiere que el desarrollador declare explícitamente la clase en el directorio com.example.ExampleImpl. Esta es una carga adicional para el desarrollador y puede ser propensa a errores.
  • ExampleImpl genera instancias de subclase, no sus objetos Example. Esto causa dos problemas:
    • No tiene nada que decir sobre cómo se construyen las subclases: el constructor sin argumentos se utiliza para crear las instancias.
    • Como tal, las subclases deben tener un constructor predeterminado, o deben declarar explícitamente un constructor sin argumentos.

Aparentemente, Java 9 abordará algunas de estas deficiencias (en particular, las relacionadas con la creación de instancias de subclases).

Un ejemplo

Suponga que está interesado en encontrar clases que implementen una interfaz META-INF/services/com.example.Example:

package com.example;

public interface Example {
    public String getStr();
}

La clase <=> implementa esa interfaz:

package com.example;

public class ExampleImpl implements Example {
    public String getStr() {
        return "ExampleImpl's string.";
    }
}

Declararía que la clase <=> es una implementación de <=> creando un archivo <=> que contiene el texto <=>.

Luego, puede obtener una instancia de cada implementación de <=> (incluida una instancia de <=>) de la siguiente manera:

ServiceLoader<Example> loader = ServiceLoader.load(Example.class)
for (Example example : loader) {
    System.out.println(example.getStr());
}

// Prints "ExampleImpl's string.", plus whatever is returned
// by other declared implementations of com.example.Example.

Agréguelos a un mapa estático dentro (this.getClass (). getName ()) el constructor de clases padre (o cree uno predeterminado) pero esto se actualizará en tiempo de ejecución. Si la inicialización diferida es una opción, puede probar este enfoque.

Estoy usando una biblioteca de reflexión, que escanea su classpath para todas las subclases: https://github.com / ronmamo / reflexiones

Así es como se haría:

Reflections reflections = new Reflections("my.project");
Set<Class<? extends SomeType>> subTypes = reflections.getSubTypesOf(SomeType.class);

Necesitaba hacer esto como un caso de prueba, para ver si se habían agregado nuevas clases al código. Esto es lo que hice

final static File rootFolder = new File(SuperClass.class.getProtectionDomain().getCodeSource().getLocation().getPath());
private static ArrayList<String> files = new ArrayList<String>();
listFilesForFolder(rootFolder); 

@Test(timeout = 1000)
public void testNumberOfSubclasses(){
    ArrayList<String> listSubclasses = new ArrayList<>(files);
    listSubclasses.removeIf(s -> !s.contains("Superclass.class"));
    for(String subclass : listSubclasses){
        System.out.println(subclass);
    }
    assertTrue("You did not create a new subclass!", listSubclasses.size() >1);     
}

public static void listFilesForFolder(final File folder) {
    for (final File fileEntry : folder.listFiles()) {
        if (fileEntry.isDirectory()) {
            listFilesForFolder(fileEntry);
        } else {
            files.add(fileEntry.getName().toString());
        }
    }
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top