superclase subclase reservada polimorfismo palabra metodo llamar herencia ejemplo definicion como clase abstracta java class interface subclass

subclase - super en java



¿Cómo se encuentran todas las subclases de una clase determinada en Java? (15)

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

¿Cómo se hace para tratar de encontrar todas las subclases de una clase dada (o todos los implementadores de una interfaz dada) en Java? A partir de ahora, tengo un método para hacer esto, pero me parece 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 la 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 característica interesante llamada Jerarquía de tipos que logra mostrar esto de manera bastante eficiente. ¿Cómo hace uno para hacerlo programáticamente?


Dependiendo de sus requisitos particulares, en algunos casos el mecanismo del cargador de servicios de Java puede lograr lo que busca.

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

La principal ventaja de este enfoque es que no hay necesidad de escanear manualmente la ruta de clase completa para las subclases: toda la lógica de descubrimiento está contenida dentro de la clase ServiceLoader , y solo carga las clases explícitamente declaradas en el directorio META-INF/services (no cada clase en el classpath).

Sin embargo, hay algunas desventajas:

  • No encontrará todas las subclases, solo aquellas que están explícitamente declaradas. Como tal, si necesita encontrar realmente todas las subclases, este enfoque puede ser insuficiente.
  • Requiere que el desarrollador declare explícitamente la clase en el directorio META-INF/services . Esta es una carga adicional para el desarrollador y puede ser propensa a errores.
  • El ServiceLoader.iterator() genera instancias de subclases, no sus objetos de Class . Esto causa dos problemas:
    • No se puede opinar sobre cómo se construyen las subclases; el constructor no-arg se usa para crear las instancias.
    • Como tal, las subclases deben tener un constructor por defecto, o deben declarar explícitamente un constructor no-arg.

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

Un ejemplo

Supongamos que le interesa encontrar clases que implementen una interfaz com.example.Example :

package com.example; public interface Example { public String getStr(); }

La clase com.example.ExampleImpl implementa esa interfaz:

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

Declararía que la clase ExampleImpl es una implementación de Example al crear un archivo META-INF/services/com.example.Example contiene el texto com.example.ExampleImpl .

Luego, podría obtener una instancia de cada implementación de Example (incluida una instancia de ExampleImpl ) 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.


Escanear para las clases no es fácil con Java puro.

El marco de primavera 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 utilizar un analizador de códigos de bytes para encontrar los candidatos, lo que significa que no cargará todas las clases que escanea.


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

Existe un proyecto que realiza el escaneo e indización necesarios de su ruta de clase para que pueda obtener acceso a esta información ...

Reflections

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

Reflections explora 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.

Con 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, incluidos los parámetros de anotación que coincidan
  • 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).


Estoy usando un reflejo lib, que escanea tu classpath para todas las subclases: org.reflections

Así es como se haría:

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


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) escanean solo una vez (durante la carga del proyecto la mayoría de las veces) y crean un índice. La próxima vez que solicite los datos, no volverá a escanear, pero mire el índice.


Lo hice hace varios años. La forma más confiable de hacer esto (es decir, con API Java oficiales 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 de 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, he eliminado el análisis de argumentos de línea de comandos y estoy escribiendo a System.out en lugar de a un archivo.


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()); } } }


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

Eclipse solo puede informarle acerca de las superclases y subclases en lo que parece ser una cantidad de tiempo "eficiente" porque ya tiene todos los datos de tipo cargados en el punto donde presiona el botón "Mostrar en la jerarquía de tipos" (dado que es compilando constantemente tus clases, conoce todo sobre el classpath, etc.)


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


Puede probar mi biblioteca FastClasspathScanner : puede encontrar todas las subclases de una clase determinada en classpath (así como todas las subinterfaces de una interfaz determinada, todas las clases que implementan una interfaz determinada, todas las clases anotadas con una anotación dada, y más) . Es una dependencia pequeña, y es extremadamente rápida en comparación con otras opciones de escaneo de classpath.

Por cierto, escanear el classpath no es tan simple como verificar la propiedad java.class.path , porque hay muchas formas en que se puede especificar el classpath (por ejemplo, puedes agregar entradas de Class-Path al manifiesto de un archivo jar). FastClasspathScanner maneja estas complejidades para usted.


Sé que llegué 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 Eclipse (y así aprovechar su caché, etc.) para encontrar clases que implementen una interfaz. Aquí está mi primer corte (muy duro):

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 recursividad nunca le hizo daño a nadie.



También se debe tener en cuenta que, por supuesto, solo encontrará todas las subclases que existen en su classpath actual. Presumiblemente, esto está bien para lo que estás mirando en este momento, y es probable que lo hayas considerado, pero si en algún momento has lanzado una clase no final a la naturaleza (para variar los niveles de "salvaje") entonces es completamente factible que alguien más ha escrito su propia subclase de la que no sabrá nada.

Por lo tanto, si por casualidad desea ver todas las subclases porque quiere hacer 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 deberían ser compatibles con versiones anteriores, para cualquier subclase que siguiera al menos su definición de la superclase.


Teniendo en cuenta las limitaciones mencionadas en las otras respuestas, también puede usar PojoClassFactory de 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 (p. Ej., "com.mycompany" o simplemente "com" ), y Superclass es su supertipo (esto también funciona en las interfaces).