¿Puedes encontrar todas las clases en un paquete usando la reflexión?


528

¿Es posible encontrar todas las clases o interfaces en un paquete dado? (Mirando rápidamente, por ejemplo Package, parecería que no).


2
Para su información, la solución Amit se vincula a las obras, aunque tiene un error si la ruta de clase tiene un carácter de espacio (y probablemente también para otros caracteres no alfanuméricos). Si lo está utilizando en cualquier tipo de código de producción, vea mi comentario a su respuesta para una solución alternativa.
Kip

2
También tenga en cuenta esta publicación .
barfuin

1
Ver respuesta relacionada: stackoverflow.com/a/30149061/4102160
Cfx

1
También tenga en cuenta esta publicación .
sp00m

1
Vea mi respuesta a continuación sobre ClassGraph, actualmente es el método más robusto para escanear el classpath y la ruta del módulo.
Luke Hutchison el

Respuestas:


373

Debido a la naturaleza dinámica de los cargadores de clases, esto no es posible. Los cargadores de clases no están obligados a decirle a la VM qué clases puede proporcionar, sino que simplemente reciben solicitudes de clases y tienen que devolver una clase o lanzar una excepción.

Sin embargo, si escribe sus propios cargadores de clases, o examina los classpaths y sus frascos, es posible encontrar esta información. Sin embargo, esto será a través de las operaciones del sistema de archivos, y no a través de la reflexión. Incluso puede haber bibliotecas que pueden ayudarlo a hacer esto.

Si hay clases que se generan o se entregan de forma remota, no podrá descubrir esas clases.

El método normal consiste en registrar en algún lugar las clases a las que necesita acceder en un archivo, o hacer referencia a ellas en una clase diferente. O simplemente use la convención cuando se trata de nombrar.

Anexo: La Biblioteca Reflections le permitirá buscar clases en el classpath actual. Se puede usar para obtener todas las clases en un paquete:

 Reflections reflections = new Reflections("my.project.prefix");

 Set<Class<? extends Object>> allClasses = 
     reflections.getSubTypesOf(Object.class);

12
La incapacidad de buscar nombres de clase me ha molestado durante mucho tiempo. Claro, es difícil y el rendimiento puede variar ampliamente, y para ciertos cargadores de clases la lista es indefinida o ilimitada, pero hay formas en que esto podría haberse solucionado.
Mr. Shiny and New 安 宇

16
Tenga en cuenta que esta solución no funcionará, ya que por defecto getSubTypesOf no devuelve subtipos de Object. Vea la solución de Aleksander Blomskøld para saber cómo configurar SubTypeScanner.
Alex Spurling

17
Reflexiones requiere guayaba. La guayaba es grande. La versión 14.0.1 es 2.1MB.
Mike Jones

3
No funciono para mi. Mac OSX: versión de dependencia de Reflections 0.9.9-RC1 (maven): JDK 1.7. Reconsidere la respuesta aceptada. @ AleksanderBlomskøld respuesta es la que hay que ir. !!!!!
Konstantinos Margaritis

68
Si esto devuelve una lista vacía, inicialice el objeto Reflections de esta manera: Reflections reflections = new Reflections ("your.package.here", new SubTypesScanner (false));
João Rocha da Silva

186

Probablemente deberías echar un vistazo a la biblioteca de Reflexiones de código abierto . Con él puedes lograr fácilmente lo que quieres.

Primero, configure el índice de reflexiones (es un poco desordenado ya que la búsqueda de todas las clases está deshabilitada de forma predeterminada):

List<ClassLoader> classLoadersList = new LinkedList<ClassLoader>();
classLoadersList.add(ClasspathHelper.contextClassLoader());
classLoadersList.add(ClasspathHelper.staticClassLoader());

Reflections reflections = new Reflections(new ConfigurationBuilder()
    .setScanners(new SubTypesScanner(false /* don't exclude Object.class */), new ResourcesScanner())
    .setUrls(ClasspathHelper.forClassLoader(classLoadersList.toArray(new ClassLoader[0])))
    .filterInputsBy(new FilterBuilder().include(FilterBuilder.prefix("org.your.package"))));

Luego puede consultar todos los objetos en un paquete dado:

Set<Class<?>> classes = reflections.getSubTypesOf(Object.class);

66
Ah, aquí vamos: code.google.com/p/reflections/issues/detail?id=122 . El objeto está excluido de forma predeterminada, pero puede reiniciarlo. Gracias por señalarme a esta biblioteca, ¡es genial!
mtrc

1
Me encontré con problemas en mi Mac con este código (relacionado con las bibliotecas nativas), pero el uso en .addUrls(ClasspathHelper.forJavaClassPath())lugar de lo anterior los resolvió por mí. ¡Menos código también!
David Pärsson

3
si alguien se pregunta que la forma más sencilla de obtener el paquete predeterminado es tener el prefijo una Cadena vacía -> "".
JBA

2
La biblioteca "Reflections" tiene una licencia complicada: github.com/ronmamo/reflections/blob/master/COPYING.txt . El truco es que la licencia permite el uso gratuito de solo la licencia misma. Entonces, para usar realmente la biblioteca (no la licencia), todos deben contactar al autor y negociar los términos de uso.
Serge Rogatch

3
Serge, creo que no entiendes WTFPL: wtfpl.net Creo que WTFPL significa que eres libre de hacer lo que quieras, no solo con la licencia sino también con el código
Richo

122

Google Guava 14 incluye una nueva clase ClassPathcon tres métodos para buscar clases de nivel superior:

  • getTopLevelClasses()
  • getTopLevelClasses(String packageName)
  • getTopLevelClassesRecursive(String packageName)

Vea los ClassPathjavadocs para más información.


esto funcionó para mí donde Reflection no podía hacer (sin ancestro directo común, sin anotación común)
Riccardo Cossu

1
Como mencioné en un comentario a continuación , ClassPathestá etiquetado con @Beta, por lo que podría no ser una buena idea para algunos ...
Christian

1
Decir que esto funciona donde la reflexión no funciona es un poco extraño, la solución, sin duda, se implementa utilizando la funcionalidad de reflexión (y cargador de clases).
Maarten Bodewes

55
Creo que se refería a la biblioteca Reflections mencionada en la otra respuesta.
Christoph Leiter

Funciona bajo Java 11, si usa guava versión 28.1-jre.
gorjanz

112

Puede usar este método 1 que usa el ClassLoader.

/**
 * Scans all classes accessible from the context class loader which belong to the given package and subpackages.
 *
 * @param packageName The base package
 * @return The classes
 * @throws ClassNotFoundException
 * @throws IOException
 */
private static Class[] getClasses(String packageName)
        throws ClassNotFoundException, IOException {
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    assert classLoader != null;
    String path = packageName.replace('.', '/');
    Enumeration<URL> resources = classLoader.getResources(path);
    List<File> dirs = new ArrayList<File>();
    while (resources.hasMoreElements()) {
        URL resource = resources.nextElement();
        dirs.add(new File(resource.getFile()));
    }
    ArrayList<Class> classes = new ArrayList<Class>();
    for (File directory : dirs) {
        classes.addAll(findClasses(directory, packageName));
    }
    return classes.toArray(new Class[classes.size()]);
}

/**
 * Recursive method used to find all classes in a given directory and subdirs.
 *
 * @param directory   The base directory
 * @param packageName The package name for classes found inside the base directory
 * @return The classes
 * @throws ClassNotFoundException
 */
private static List<Class> findClasses(File directory, String packageName) throws ClassNotFoundException {
    List<Class> classes = new ArrayList<Class>();
    if (!directory.exists()) {
        return classes;
    }
    File[] files = directory.listFiles();
    for (File file : files) {
        if (file.isDirectory()) {
            assert !file.getName().contains(".");
            classes.addAll(findClasses(file, packageName + "." + file.getName()));
        } else if (file.getName().endsWith(".class")) {
            classes.add(Class.forName(packageName + '.' + file.getName().substring(0, file.getName().length() - 6)));
        }
    }
    return classes;
}

__________
1 Este método fue tomado originalmente de http://snippets.dzone.com/posts/show/4831 , que fue archivado por el Archivo de Internet, como se vincula ahora. El fragmento también está disponible en https://dzone.com/articles/get-all-classes-within-package .


66
Tuve un problema con esto si mi camino incluía espacios. La clase URL escapaba espacios a %20, pero el new File()constructor lo trató como un signo de porcentaje literal dos cero. Lo arreglé cambiando la dirs.add(...)línea a esto: dirs.add(new File(resource.toURI())); Esto también significaba que tenía que agregar URISyntaxExceptiona la cláusula de tiros degetClasses
Kip

19
¡Acaba de copiar de dzone.com/articles/get-all-classes-within-package ! consulte la fuente la próxima vez
RS

19
+1 porque esta solución NO requiere bibliotecas externas ... NUNCA, realmente NUNCA acople su código aleatoriamente con bibliotecas solo para lograr una cosa pequeña como esta. ¿Sabes que estás agregando una superficie de ataque potencial para los atacantes? Nov de 2015, un problema Apache Commons descubrió que conduce a la ejecución remota de comandos sólo por tener Apache Commons en la ruta de clase de una aplicación desplegada en JBoss / Weblogic [ foxglovesecurity.com/2015/11/06/...
sc0p

@Qix notó correctamente que este código no es compatible con jar. Para soportar tarros y directorios . El código se cambió como se indica a continuación:
user1523177

1
Buena solución, pero parece ser mejor si 'Class.forName (String className)' será reemplazado por 'Class.forName (String className, boolean initialize, ClassLoader loader)' donde 'initialize = false;' para no crear instancias de clase.
Andrei_N

99

Primavera

Este ejemplo es para Spring 4, pero también puede encontrar el escáner classpath en versiones anteriores.

// create scanner and disable default filters (that is the 'false' argument)
final ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
// add include filters which matches all the classes (or use your own)
provider.addIncludeFilter(new RegexPatternTypeFilter(Pattern.compile(".*")));

// get matching classes defined in the package
final Set<BeanDefinition> classes = provider.findCandidateComponents("my.package.name");

// this is how you can load the class type from BeanDefinition instance
for (BeanDefinition bean: classes) {
    Class<?> clazz = Class.forName(bean.getBeanClassName());
    // ... do your magic with the class ...
}

Google guayaba

Nota: En la versión 14, la API todavía está marcada como @Beta , así que tenga cuidado con el código de producción.

final ClassLoader loader = Thread.currentThread().getContextClassLoader();

for (final ClassPath.ClassInfo info : ClassPath.from(loader).getTopLevelClasses()) {
  if (info.getName().startsWith("my.package.")) {
    final Class<?> clazz = info.load();
    // do something with your clazz
  }
}

55
Excelente respuesta ¡Aquí hay demasiadas soluciones que son detalladas, no probadas, que no funcionan! Este es fantástico: es conciso y probado (es de guayaba). ¡Muy bien! Es útil, merece más votos a favor.
JeanValjean

Desafortunadamente, la ClassPathclase en Guava también está marcada con @Beta: "Las API marcadas con la anotación @Beta a nivel de clase o método están sujetas a cambios. Se pueden modificar de cualquier manera, o incluso eliminar, en cualquier versión principal. Si su código es una biblioteca en sí misma (es decir, se usa en CLASSPATH de usuarios que están fuera de su propio control), no debe usar las API beta, a menos que las vuelva a
Christian

@Christian Buen punto, no me he dado cuenta! Gracias. Agregaré otra respuesta usando el escáner Spring classpath, que no es beta con seguridad.
voho

Para encontrar clases estáticas anidadas usando la solución de guayaba, getAllClasses()se puede usar el método.
v.ladynev

1
La solución Spring es la única que funciona si se ejecuta desde un jar ejecutable.
Lucas el

38

Hola. Siempre he tenido algunos problemas con las soluciones anteriores (y en otros sitios).
Yo, como desarrollador, estoy programando un complemento para una API. La API evita el uso de bibliotecas externas o herramientas de terceros. La configuración también consiste en una mezcla de código en archivos jar o zip y archivos de clase ubicados directamente en algunos directorios. Por lo tanto, mi código tenía que poder funcionar en cada configuración. Después de mucha investigación, he encontrado un método que funcionará en al menos el 95% de todas las configuraciones posibles.

El siguiente código es básicamente el método overkill que siempre funcionará.

El código:

Este código escanea un paquete dado para todas las clases que están incluidas en él. Solo funcionará para todas las clases en la corriente ClassLoader.

/**
 * Private helper method
 * 
 * @param directory
 *            The directory to start with
 * @param pckgname
 *            The package name to search for. Will be needed for getting the
 *            Class object.
 * @param classes
 *            if a file isn't loaded but still is in the directory
 * @throws ClassNotFoundException
 */
private static void checkDirectory(File directory, String pckgname,
        ArrayList<Class<?>> classes) throws ClassNotFoundException {
    File tmpDirectory;

    if (directory.exists() && directory.isDirectory()) {
        final String[] files = directory.list();

        for (final String file : files) {
            if (file.endsWith(".class")) {
                try {
                    classes.add(Class.forName(pckgname + '.'
                            + file.substring(0, file.length() - 6)));
                } catch (final NoClassDefFoundError e) {
                    // do nothing. this class hasn't been found by the
                    // loader, and we don't care.
                }
            } else if ((tmpDirectory = new File(directory, file))
                    .isDirectory()) {
                checkDirectory(tmpDirectory, pckgname + "." + file, classes);
            }
        }
    }
}

/**
 * Private helper method.
 * 
 * @param connection
 *            the connection to the jar
 * @param pckgname
 *            the package name to search for
 * @param classes
 *            the current ArrayList of all classes. This method will simply
 *            add new classes.
 * @throws ClassNotFoundException
 *             if a file isn't loaded but still is in the jar file
 * @throws IOException
 *             if it can't correctly read from the jar file.
 */
private static void checkJarFile(JarURLConnection connection,
        String pckgname, ArrayList<Class<?>> classes)
        throws ClassNotFoundException, IOException {
    final JarFile jarFile = connection.getJarFile();
    final Enumeration<JarEntry> entries = jarFile.entries();
    String name;

    for (JarEntry jarEntry = null; entries.hasMoreElements()
            && ((jarEntry = entries.nextElement()) != null);) {
        name = jarEntry.getName();

        if (name.contains(".class")) {
            name = name.substring(0, name.length() - 6).replace('/', '.');

            if (name.contains(pckgname)) {
                classes.add(Class.forName(name));
            }
        }
    }
}

/**
 * Attempts to list all the classes in the specified package as determined
 * by the context class loader
 * 
 * @param pckgname
 *            the package name to search
 * @return a list of classes that exist within that package
 * @throws ClassNotFoundException
 *             if something went wrong
 */
public static ArrayList<Class<?>> getClassesForPackage(String pckgname)
        throws ClassNotFoundException {
    final ArrayList<Class<?>> classes = new ArrayList<Class<?>>();

    try {
        final ClassLoader cld = Thread.currentThread()
                .getContextClassLoader();

        if (cld == null)
            throw new ClassNotFoundException("Can't get class loader.");

        final Enumeration<URL> resources = cld.getResources(pckgname
                .replace('.', '/'));
        URLConnection connection;

        for (URL url = null; resources.hasMoreElements()
                && ((url = resources.nextElement()) != null);) {
            try {
                connection = url.openConnection();

                if (connection instanceof JarURLConnection) {
                    checkJarFile((JarURLConnection) connection, pckgname,
                            classes);
                } else if (connection instanceof FileURLConnection) {
                    try {
                        checkDirectory(
                                new File(URLDecoder.decode(url.getPath(),
                                        "UTF-8")), pckgname, classes);
                    } catch (final UnsupportedEncodingException ex) {
                        throw new ClassNotFoundException(
                                pckgname
                                        + " does not appear to be a valid package (Unsupported encoding)",
                                ex);
                    }
                } else
                    throw new ClassNotFoundException(pckgname + " ("
                            + url.getPath()
                            + ") does not appear to be a valid package");
            } catch (final IOException ioex) {
                throw new ClassNotFoundException(
                        "IOException was thrown when trying to get all resources for "
                                + pckgname, ioex);
            }
        }
    } catch (final NullPointerException ex) {
        throw new ClassNotFoundException(
                pckgname
                        + " does not appear to be a valid package (Null pointer exception)",
                ex);
    } catch (final IOException ioex) {
        throw new ClassNotFoundException(
                "IOException was thrown when trying to get all resources for "
                        + pckgname, ioex);
    }

    return classes;
}

Estos tres métodos le brindan la capacidad de encontrar todas las clases en un paquete dado.
Lo usas así:

getClassesForPackage("package.your.classes.are.in");

La explicación:

El método primero obtiene la corriente ClassLoader. Luego recupera todos los recursos que contienen dicho paquete e itera estos URLs. Luego crea URLConnectionay determina qué tipo de URL tenemos. Puede ser un directorio ( FileURLConnection) o un directorio dentro de un archivo jar o zip ( JarURLConnection). Dependiendo de qué tipo de conexión tenemos, se llamarán dos métodos diferentes.

Primero veamos qué sucede si es a FileURLConnection.
Primero verifica si el archivo pasado existe y es un directorio. Si ese es el caso, verifica si se trata de un archivo de clase. Si es así Class, se creará un objeto y se colocará en el ArrayList. Si no es un archivo de clase sino un directorio, simplemente lo iteramos y hacemos lo mismo. Todos los demás casos / archivos serán ignorados.

Si URLConnectiones un JarURLConnection, se llamará al otro método auxiliar privado. Este método itera sobre todas las entradas en el archivo zip / jar. Si una entrada es un archivo de clase y está dentro del paquete Class, se creará un objeto y se almacenará en el ArrayList.

Después de analizar todos los recursos, (el método principal) devuelve el ArrayListcontenido de todas las clases en el paquete dado, que el actual ClassLoaderconoce.

Si el proceso falla en algún momento ClassNotFoundException, se lanzará un mensaje con información detallada sobre la causa exacta.


44
Este ejemplo parece requerir importación sun.net.www.protocol.file.FileURLConnection, lo que genera una advertencia en tiempo de compilación ("advertencia: sun.net.www.protocol.file.FileURLConnection es la API propiedad de Sun y puede eliminarse en una versión futura"). ¿Existe alguna alternativa para usar esa clase, o se puede suprimir la advertencia mediante anotaciones?
Christian

Este método no funciona para las clases bootstrap, como las de java.lang, java.util, ... Se pueden encontrar obteniendo System.getProperty ("sun.boot.class.path"), dividiendo con: o; (dependiendo del sistema operativo), y luego ejecutando versiones ligeramente modificadas de los checkDirectory y checkJarFile anteriores.
coderforlife

1
Puede evitar la advertencia / error utilizando connection.getClass (). GetCanonicalName (). Equals ("sun.net.www.protocol.file.FileURLConnection"). Si realmente lo desea, puede crear una URLConnection que cree que DEBERÍA usar sun.net.www.protocol.file.FileURLConnection y también comparar el nombre de la clase de conexión con el nombre de la clase que creó. Si ambos son iguales, puede tratarlo como una instancia de sun.net.www.protocol.file.FileURLConnection en lugar de fallar en caso de que cambie el nombre de la clase.
William Deans

1
@Christian Puede evitar que FileURLConnection haga algo como esto: if ( ... instanceof JarURLConnecton) { ... } else { // Asume that the Connection is valid and points to a File }es lo que hice en mi código para buscar clases anotadas JPA
Zardoz89

15

Sin usar ninguna biblioteca adicional:

package test;

import java.io.DataInputStream;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

public class Test {
    public static void main(String[] args) throws Exception{
        List<Class> classes = getClasses(Test.class.getClassLoader(),"test");
        for(Class c:classes){
            System.out.println("Class: "+c);
        }
    }

    public static List<Class> getClasses(ClassLoader cl,String pack) throws Exception{

        String dottedPackage = pack.replaceAll("[/]", ".");
        List<Class> classes = new ArrayList<Class>();
        URL upackage = cl.getResource(pack);

        DataInputStream dis = new DataInputStream((InputStream) upackage.getContent());
        String line = null;
        while ((line = dis.readLine()) != null) {
            if(line.endsWith(".class")) {
               classes.add(Class.forName(dottedPackage+"."+line.substring(0,line.lastIndexOf('.'))));
            }
        }
        return classes;
    }
}

Cuando ejecuto esto en un JAR, upackagees null... :(
Christian

Para un paquete "com.mycompany.beans", reemplace "test" con "com / mycompany / beans"
James Jithin

44
Me sale un nulo cuando uso este código. Parece que solo funciona si su jar es un ejecutable
Alao

si obtuvo el nombre del paquete String pack = getPackage().getName();, entonces debe agregarpack = pack.replaceAll("[.]", "/");
user2682877

13

En general, los cargadores de clases no permiten escanear todas las clases en el classpath. Pero, por lo general, el único cargador de clases utilizado es UrlClassLoader, del cual podemos recuperar la lista de directorios y archivos jar (consulte getURL ) y abrirlos uno por uno para enumerar las clases disponibles. Este enfoque, llamado exploración de ruta de clase, se implementa en Scannotation and Reflections .

Reflections reflections = new Reflections("my.package");
Set<Class<? extends Object>> classes = reflections.getSubTypesOf(Object.class);

Otro enfoque es utilizar la API de procesamiento de anotación conectable de Java para escribir un procesador de anotación que recopilará todas las clases anotadas en tiempo de compilación y creará el archivo de índice para uso en tiempo de ejecución. Este mecanismo se implementa en la biblioteca ClassIndex :

// package-info.java
@IndexSubclasses
package my.package;

// your code
Iterable<Class> classes = ClassIndex.getPackageClasses("my.package");

Tenga en cuenta que no se necesita ninguna configuración adicional ya que el escaneo está completamente automatizado gracias al compilador Java que descubre automáticamente cualquier procesador encontrado en el classpath.


¿Esto descubre clases empaquetadas en un frasco? No parece funcionar para mí.
pregunta el

¿Qué herramienta estás tratando de usar?
Sławek

Estoy usando la biblioteca Reflections. Pero lo hice funcionar después de seguir la solución mencionada por @Aleksander Blomskøld para versiones recientes de esta lib.
pide el

Hola, estoy usando eclipse y no puedo hacerlo funcionar, ClassIndex.getPackageClasses ("my.package") devuelve un mapa vacío
Juan


5

Así es como lo hago. Escaneo todas las subcarpetas (subpaquetes) y no intento cargar clases anónimas:

   /**
   * Attempts to list all the classes in the specified package as determined
   * by the context class loader, recursively, avoiding anonymous classes
   * 
   * @param pckgname
   *            the package name to search
   * @return a list of classes that exist within that package
   * @throws ClassNotFoundException
   *             if something went wrong
   */
  private static List<Class> getClassesForPackage(String pckgname) throws ClassNotFoundException {
      // This will hold a list of directories matching the pckgname. There may be more than one if a package is split over multiple jars/paths
      ArrayList<File> directories = new ArrayList<File>();
      String packageToPath = pckgname.replace('.', '/');
      try {
          ClassLoader cld = Thread.currentThread().getContextClassLoader();
          if (cld == null) {
              throw new ClassNotFoundException("Can't get class loader.");
          }

          // Ask for all resources for the packageToPath
          Enumeration<URL> resources = cld.getResources(packageToPath);
          while (resources.hasMoreElements()) {
              directories.add(new File(URLDecoder.decode(resources.nextElement().getPath(), "UTF-8")));
          }
      } catch (NullPointerException x) {
          throw new ClassNotFoundException(pckgname + " does not appear to be a valid package (Null pointer exception)");
      } catch (UnsupportedEncodingException encex) {
          throw new ClassNotFoundException(pckgname + " does not appear to be a valid package (Unsupported encoding)");
      } catch (IOException ioex) {
          throw new ClassNotFoundException("IOException was thrown when trying to get all resources for " + pckgname);
      }

      ArrayList<Class> classes = new ArrayList<Class>();
      // For every directoryFile identified capture all the .class files
      while (!directories.isEmpty()){
          File directoryFile  = directories.remove(0);             
          if (directoryFile.exists()) {
              // Get the list of the files contained in the package
              File[] files = directoryFile.listFiles();

              for (File file : files) {
                  // we are only interested in .class files
                  if ((file.getName().endsWith(".class")) && (!file.getName().contains("$"))) {
                      // removes the .class extension
                      int index = directoryFile.getPath().indexOf(packageToPath);
                      String packagePrefix = directoryFile.getPath().substring(index).replace('/', '.');;                          
                    try {                  
                      String className = packagePrefix + '.' + file.getName().substring(0, file.getName().length() - 6);                            
                      classes.add(Class.forName(className));                                
                    } catch (NoClassDefFoundError e)
                    {
                      // do nothing. this class hasn't been found by the loader, and we don't care.
                    }
                  } else if (file.isDirectory()){ // If we got to a subdirectory
                      directories.add(new File(file.getPath()));                          
                  }
              }
          } else {
              throw new ClassNotFoundException(pckgname + " (" + directoryFile.getPath() + ") does not appear to be a valid package");
          }
      }
      return classes;
  }  

4

Creé un proyecto simple de github que resuelve este problema:

https://github.com/ddopson/java-class-enumerator

Debería funcionar para AMBOS classpaths basados ​​en archivos Y para archivos jar.

Si ejecuta 'make' después de revisar el proyecto, lo imprimirá:

 Cleaning...
rm -rf build/
 Building...
javac -d build/classes src/pro/ddopson/ClassEnumerator.java src/test/ClassIShouldFindOne.java src/test/ClassIShouldFindTwo.java src/test/subpkg/ClassIShouldFindThree.java src/test/TestClassEnumeration.java
 Making JAR Files...
jar cf build/ClassEnumerator_test.jar -C build/classes/ . 
jar cf build/ClassEnumerator.jar -C build/classes/ pro
 Running Filesystem Classpath Test...
java -classpath build/classes test.TestClassEnumeration
ClassDiscovery: Package: 'test' becomes Resource: 'file:/Users/Dopson/work/other/java-class-enumeration/build/classes/test'
ClassDiscovery: Reading Directory '/Users/Dopson/work/other/java-class-enumeration/build/classes/test'
ClassDiscovery: FileName 'ClassIShouldFindOne.class'  =>  class 'test.ClassIShouldFindOne'
ClassDiscovery: FileName 'ClassIShouldFindTwo.class'  =>  class 'test.ClassIShouldFindTwo'
ClassDiscovery: FileName 'subpkg'  =>  class 'null'
ClassDiscovery: Reading Directory '/Users/Dopson/work/other/java-class-enumeration/build/classes/test/subpkg'
ClassDiscovery: FileName 'ClassIShouldFindThree.class'  =>  class 'test.subpkg.ClassIShouldFindThree'
ClassDiscovery: FileName 'TestClassEnumeration.class'  =>  class 'test.TestClassEnumeration'
 Running JAR Classpath Test...
java -classpath build/ClassEnumerator_test.jar  test.TestClassEnumeration
ClassDiscovery: Package: 'test' becomes Resource: 'jar:file:/Users/Dopson/work/other/java-class-enumeration/build/ClassEnumerator_test.jar!/test'
ClassDiscovery: Reading JAR file: '/Users/Dopson/work/other/java-class-enumeration/build/ClassEnumerator_test.jar'
ClassDiscovery: JarEntry 'META-INF/'  =>  class 'null'
ClassDiscovery: JarEntry 'META-INF/MANIFEST.MF'  =>  class 'null'
ClassDiscovery: JarEntry 'pro/'  =>  class 'null'
ClassDiscovery: JarEntry 'pro/ddopson/'  =>  class 'null'
ClassDiscovery: JarEntry 'pro/ddopson/ClassEnumerator.class'  =>  class 'null'
ClassDiscovery: JarEntry 'test/'  =>  class 'null'
ClassDiscovery: JarEntry 'test/ClassIShouldFindOne.class'  =>  class 'test.ClassIShouldFindOne'
ClassDiscovery: JarEntry 'test/ClassIShouldFindTwo.class'  =>  class 'test.ClassIShouldFindTwo'
ClassDiscovery: JarEntry 'test/subpkg/'  =>  class 'null'
ClassDiscovery: JarEntry 'test/subpkg/ClassIShouldFindThree.class'  =>  class 'test.subpkg.ClassIShouldFindThree'
ClassDiscovery: JarEntry 'test/TestClassEnumeration.class'  =>  class 'test.TestClassEnumeration'
 Tests Passed. 

Ver también mi otra respuesta


4

Sí, usando pocas API, puede hacerlo, así es como me gusta hacerlo, enfrenté este problema que estaba usando hibernate core y tuve que encontrar clases que estaban anotadas con una cierta anotación.

Haga de estas una anotación personalizada con la que marcará las clases que desea que se seleccionen.

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface EntityToBeScanned {

}

Luego marca tu clase con ella como

@EntityToBeScanned 
public MyClass{

}

Haga esta clase de utilidad que tiene el siguiente método

public class ClassScanner {

    public static Set<Class<?>> allFoundClassesAnnotatedWithEntityToBeScanned(){
        Reflections reflections = new Reflections(".*");
        Set<Class<?>> annotated = reflections.getTypesAnnotatedWith(EntityToBeScanned.class);
        return annotated;
    }

}

Llame al método allFoundClassesAnnotatedWithEntityToBeScanned () para obtener un conjunto de clases encontrado.

Necesitarás libs dados a continuación

<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>21.0</version>
    </dependency>
<!-- https://mvnrepository.com/artifact/org.javassist/javassist -->
<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.22.0-CR1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.reflections/reflections -->
<dependency>
    <groupId>org.reflections</groupId>
    <artifactId>reflections</artifactId>
    <version>0.9.10</version>
</dependency>

3

Debe buscar cada entrada del cargador de clases en la ruta de clase:

    String pkg = "org/apache/commons/lang";
    ClassLoader cl = ClassLoader.getSystemClassLoader();
    URL[] urls = ((URLClassLoader) cl).getURLs();
    for (URL url : urls) {
        System.out.println(url.getFile());
        File jar = new File(url.getFile());
        // ....
    }   

Si la entrada es el directorio, simplemente busque en el subdirectorio correcto:

if (jar.isDirectory()) {
    File subdir = new File(jar, pkg);
    if (!subdir.exists())
        continue;
    File[] files = subdir.listFiles();
    for (File file : files) {
        if (!file.isFile())
            continue;
        if (file.getName().endsWith(".class"))
            System.out.println("Found class: "
                    + file.getName().substring(0,
                            file.getName().length() - 6));
    }
}   

Si la entrada es el archivo y es jar, inspeccione las entradas ZIP del mismo:

else {
    // try to open as ZIP
    try {
        ZipFile zip = new ZipFile(jar);
        for (Enumeration<? extends ZipEntry> entries = zip
                .entries(); entries.hasMoreElements();) {
            ZipEntry entry = entries.nextElement();
            String name = entry.getName();
            if (!name.startsWith(pkg))
                continue;
            name = name.substring(pkg.length() + 1);
            if (name.indexOf('/') < 0 && name.endsWith(".class"))
                System.out.println("Found class: "
                        + name.substring(0, name.length() - 6));
        }
    } catch (ZipException e) {
        System.out.println("Not a ZIP: " + e.getMessage());
    } catch (IOException e) {
        System.err.println(e.getMessage());
    }
}

Ahora, una vez que tenga todos los nombres de clase dentro del paquete, puede intentar cargarlos con reflexión y analizar si son clases o interfaces, etc.


¿Qué ingresarías para un paquete en un archivo Jar?
Kyle Bridenstine

Este ejemplo no pasará por subpaquetes. Tal vez eso sea de interés para algunos ... @ mr-tea Solo especifique el paquete que está buscando. Puse esto en un proyecto, especifiqué un paquete de prueba dentro de ese proyecto, lo compilé y lo empaqueté y llamé al ejemplo del método principal del JAR. Trabajado como un encanto. :)
Christian

3

He estado tratando de usar la biblioteca Reflections, pero tuve algunos problemas para usarla, y había demasiados frascos que debería incluir solo para obtener las clases en un paquete.

Publicaré una solución que encontré en esta pregunta duplicada: ¿Cómo obtener todos los nombres de clases en un paquete?

La respuesta fue escrita por sp00m ; He agregado algunas correcciones para que funcione:

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;

public final class ClassFinder {

    private final static char DOT = '.';
    private final static char SLASH = '/';
    private final static String CLASS_SUFFIX = ".class";
    private final static String BAD_PACKAGE_ERROR = "Unable to get resources from path '%s'. Are you sure the given '%s' package exists?";

    public final static List<Class<?>> find(final String scannedPackage) {
        final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        final String scannedPath = scannedPackage.replace(DOT, SLASH);
        final Enumeration<URL> resources;
        try {
            resources = classLoader.getResources(scannedPath);
        } catch (IOException e) {
            throw new IllegalArgumentException(String.format(BAD_PACKAGE_ERROR, scannedPath, scannedPackage), e);
        }
        final List<Class<?>> classes = new LinkedList<Class<?>>();
        while (resources.hasMoreElements()) {
            final File file = new File(resources.nextElement().getFile());
            classes.addAll(find(file, scannedPackage));
        }
        return classes;
    }

    private final static List<Class<?>> find(final File file, final String scannedPackage) {
        final List<Class<?>> classes = new LinkedList<Class<?>>();
        if (file.isDirectory()) {
            for (File nestedFile : file.listFiles()) {
                classes.addAll(find(nestedFile, scannedPackage));
            }
        //File names with the $1, $2 holds the anonymous inner classes, we are not interested on them. 
        } else if (file.getName().endsWith(CLASS_SUFFIX) && !file.getName().contains("$")) {

            final int beginIndex = 0;
            final int endIndex = file.getName().length() - CLASS_SUFFIX.length();
            final String className = file.getName().substring(beginIndex, endIndex);
            try {
                final String resource = scannedPackage + DOT + className;
                classes.add(Class.forName(resource));
            } catch (ClassNotFoundException ignore) {
            }
        }
        return classes;
    }

}

Para usarlo, simplemente llame al método find como sp00n mencionado en este ejemplo: he agregado la creación de instancias de las clases si es necesario.

List<Class<?>> classes = ClassFinder.find("com.package");

ExcelReporting excelReporting;
for (Class<?> aClass : classes) {
    Constructor constructor = aClass.getConstructor();
    //Create an object of the class type
    constructor.newInstance();
    //...
}

3

Acabo de escribir una clase util, que incluye métodos de prueba, puedes tener un cheque ~

IteratePackageUtil.java:

package eric.j2se.reflect;

import java.util.Set;

import org.reflections.Reflections;
import org.reflections.scanners.ResourcesScanner;
import org.reflections.scanners.SubTypesScanner;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import org.reflections.util.FilterBuilder;

/**
 * an util to iterate class in a package,
 * 
 * @author eric
 * @date Dec 10, 2013 12:36:46 AM
 */
public class IteratePackageUtil {
    /**
     * <p>
     * Get set of all class in a specified package recursively. this only support lib
     * </p>
     * <p>
     * class of sub package will be included, inner class will be included,
     * </p>
     * <p>
     * could load class that use the same classloader of current class, can't load system packages,
     * </p>
     * 
     * @param pkg
     *            path of a package
     * @return
     */
    public static Set<Class<? extends Object>> getClazzSet(String pkg) {
        // prepare reflection, include direct subclass of Object.class
        Reflections reflections = new Reflections(new ConfigurationBuilder().setScanners(new SubTypesScanner(false), new ResourcesScanner())
                .setUrls(ClasspathHelper.forClassLoader(ClasspathHelper.classLoaders(new ClassLoader[0])))
                .filterInputsBy(new FilterBuilder().includePackage(pkg)));

        return reflections.getSubTypesOf(Object.class);
    }

    public static void test() {
        String pkg = "org.apache.tomcat.util";

        Set<Class<? extends Object>> clazzSet = getClazzSet(pkg);
        for (Class<? extends Object> clazz : clazzSet) {
            System.out.println(clazz.getName());
        }
    }

    public static void main(String[] args) {
        test();
    }
}

3

La solución de Aleksander Blomskøld no me funcionó para las pruebas parametrizadas @RunWith(Parameterized.class)al usar Maven. Las pruebas se nombraron correctamente y también se encontraron pero no se ejecutaron:

-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running some.properly.named.test.run.with.maven.SomeTest
Tests run: 0, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.123 sec

Un problema similar se ha informado aquí .

En mi caso @Parameterses crear instancias de cada clase en un paquete. Las pruebas funcionaron bien cuando se ejecutan localmente en el IDE. Sin embargo, al ejecutar Maven no se encontraron clases con la solución de Aleksander Blomskøld.

Lo hice funcionar con el siguiente recorte que se inspiró en el comentario de David Pärsson sobre la respuesta de Aleksander Blomskøld:

Reflections reflections = new Reflections(new ConfigurationBuilder()
            .setScanners(new SubTypesScanner(false /* don't exclude Object.class */), new ResourcesScanner())
            .addUrls(ClasspathHelper.forJavaClassPath()) 
            .filterInputsBy(new FilterBuilder()
            .include(FilterBuilder.prefix(basePackage))));

Set<Class<?>> subTypesOf = reflections.getSubTypesOf(Object.class);

3

¿Qué hay de esto?

public static List<Class<?>> getClassesForPackage(final String pkgName) throws IOException, URISyntaxException {
    final String pkgPath = pkgName.replace('.', '/');
    final URI pkg = Objects.requireNonNull(ClassLoader.getSystemClassLoader().getResource(pkgPath)).toURI();
    final ArrayList<Class<?>> allClasses = new ArrayList<Class<?>>();

    Path root;
    if (pkg.toString().startsWith("jar:")) {
        try {
            root = FileSystems.getFileSystem(pkg).getPath(pkgPath);
        } catch (final FileSystemNotFoundException e) {
            root = FileSystems.newFileSystem(pkg, Collections.emptyMap()).getPath(pkgPath);
        }
    } else {
        root = Paths.get(pkg);
    }

    final String extension = ".class";
    try (final Stream<Path> allPaths = Files.walk(root)) {
        allPaths.filter(Files::isRegularFile).forEach(file -> {
            try {
                final String path = file.toString().replace('/', '.');
                final String name = path.substring(path.indexOf(pkgName), path.length() - extension.length());
                allClasses.add(Class.forName(name));
            } catch (final ClassNotFoundException | StringIndexOutOfBoundsException ignored) {
            }
        });
    }
    return allClasses;
}

Luego puede sobrecargar la función:

public static List<Class<?>> getClassesForPackage(final Package pkg) throws IOException, URISyntaxException {
    return getClassesForPackage(pkg.getName());
}

Si necesita probarlo:

public static void main(final String[] argv) throws IOException, URISyntaxException {
    for (final Class<?> cls : getClassesForPackage("my.package")) {
        System.out.println(cls);
    }
    for (final Class<?> cls : getClassesForPackage(MyClass.class.getPackage())) {
        System.out.println(cls);
    }
}

Si su IDE no tiene ayuda de importación:

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.FileSystemNotFoundException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;

Funciona:

  • de tu IDE

  • para un archivo JAR

  • sin dependencias externas


2

Casi todas las respuestas usan Reflectionso leen archivos de clase del sistema de archivos. Si intenta leer clases del sistema de archivos, puede recibir errores cuando empaqueta su aplicación como JAR u otro. Además, es posible que no desee utilizar una biblioteca separada para ese propósito.

Aquí hay otro enfoque que es puro Java y no depende del sistema de archivos.

import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.StandardLocation;
import javax.tools.ToolProvider;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

public class PackageUtil {

    public static Collection<Class> getClasses(final String pack) throws Exception {
        final StandardJavaFileManager fileManager = ToolProvider.getSystemJavaCompiler().getStandardFileManager(null, null, null);
        return StreamSupport.stream(fileManager.list(StandardLocation.CLASS_PATH, pack, Collections.singleton(JavaFileObject.Kind.CLASS), false).spliterator(), false)
                .map(javaFileObject -> {
                    try {
                        final String[] split = javaFileObject.getName()
                                .replace(".class", "")
                                .replace(")", "")
                                .split(Pattern.quote(File.separator));

                        final String fullClassName = pack + "." + split[split.length - 1];
                        return Class.forName(fullClassName);
                    } catch (ClassNotFoundException e) {
                        throw new RuntimeException(e);
                    }

                })
                .collect(Collectors.toCollection(ArrayList::new));
    }
}

Java 8 no es imprescindible . Puede usar para bucles en lugar de secuencias. Y puedes probarlo así

public static void main(String[] args) throws Exception {
    final String pack = "java.nio.file"; // Or any other package
    PackageUtil.getClasses(pack).stream().forEach(System.out::println);
}

1
No es muy útil por: es necesario tener JDK para usar ToolProvider.getSystemJavaCompiler(), este código no escanea paquetes anidados.
v.ladynev

No puedo hacerlo funcionar con un paquete de un frasco externo
Enrico Giurin

1

Siempre que no esté utilizando cargadores de clases dinámicos, puede buscar el classpath y para cada entrada buscar el directorio o el archivo JAR.


1

Vale la pena mencionar

Si desea tener una lista de todas las clases en algún paquete, puede usar Reflectionla siguiente manera:

List<Class> myTypes = new ArrayList<>();

Reflections reflections = new Reflections("com.package");
for (String s : reflections.getStore().get(SubTypesScanner.class).values()) {
    myTypes.add(Class.forName(s));
}

Esto creará una lista de clases que luego podrá usar como desee.


1

Es muy posible, pero sin bibliotecas adicionales como si Reflectionsfuera difícil ...
Es difícil porque no tienes un instrumento completo para obtener el nombre de la clase.
Y tomo el código de mi ClassFinderclase:

package play.util;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * Created by LINKOR on 26.05.2017 in 15:12.
 * Date: 2017.05.26
 */
public class FileClassFinder {
private JarFile file;
private boolean trouble;
public FileClassFinder(String filePath) {
    try {
        file = new JarFile(filePath);
    } catch (IOException e) {
        trouble = true;
    }
}

public List<String> findClasses(String pkg) {
    ArrayList<String> classes = new ArrayList<>();
    Enumeration<JarEntry> entries = file.entries();
    while (entries.hasMoreElements()) {
        JarEntry cls = entries.nextElement();
        if (!cls.isDirectory()) {
            String fileName = cls.getName();
            String className = fileName.replaceAll("/",         ".").replaceAll(File.pathSeparator, ".").substring(0, fileName.lastIndexOf('.'));
            if (className.startsWith(pkg)) classes.add(className.substring(pkg.length() + 1));
        }
    }
    return classes;
}
}

0

Basado en la respuesta de @ Staale , y en un intento de no confiar en bibliotecas de terceros, implementaría el enfoque del Sistema de archivos al inspeccionar la ubicación física del primer paquete con:

import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
...
Class<?>[] foundClasses = new Class<?>[0];
final ArrayList<Class<?>> foundClassesDyn = new ArrayList<Class<?>>();

new java.io.File(
    klass.getResource(
        "/" + curPackage.replace( "." , "/")
    ).getFile()
).listFiles(
    new java.io.FileFilter() {
        public boolean accept(java.io.File file) {
            final String classExtension = ".class";

            if ( file.isFile()
                && file.getName().endsWith(classExtension)
                // avoid inner classes
                && ! file.getName().contains("$") )
            {
                try {
                    String className = file.getName();
                    className = className.substring(0, className.length() - classExtension.length());
                    foundClassesDyn.add( Class.forName( curPackage + "." + className ) );
                } catch (ClassNotFoundException e) {
                    e.printStackTrace(System.out);
                }
            }

            return false;
        }
    }
);

foundClasses = foundClassesDyn.toArray(foundClasses);

0

Si simplemente está buscando cargar un grupo de clases relacionadas, entonces Spring puede ayudarlo.

Spring puede crear una instancia de una lista o mapa de todas las clases que implementan una interfaz determinada en una línea de código. La lista o mapa contendrá instancias de todas las clases que implementan esa interfaz.

Dicho esto, como alternativa a cargar la lista de clases fuera del sistema de archivos, simplemente implemente la misma interfaz en todas las clases que desee cargar, independientemente del paquete y use Spring para proporcionarle instancias de todas ellas. De esa manera, puede cargar (e instanciar) todas las clases que desee, independientemente del paquete en el que se encuentren.

Por otro lado, si desea tenerlos todos en un paquete, simplemente haga que todas las clases de ese paquete implementen una interfaz determinada.


0

plain java: FindAllClassesUsingPlainJavaReflectionTest.java

@Slf4j
class FindAllClassesUsingPlainJavaReflectionTest {

  private static final Function<Throwable, RuntimeException> asRuntimeException = throwable -> {
    log.error(throwable.getLocalizedMessage());
    return new RuntimeException(throwable);
  };

  private static final Function<String, Collection<Class<?>>> findAllPackageClasses = basePackageName -> {

    Locale locale = Locale.getDefault();
    Charset charset = StandardCharsets.UTF_8;
    val fileManager = ToolProvider.getSystemJavaCompiler()
                                  .getStandardFileManager(/* diagnosticListener */ null, locale, charset);

    StandardLocation location = StandardLocation.CLASS_PATH;
    JavaFileObject.Kind kind = JavaFileObject.Kind.CLASS;
    Set<JavaFileObject.Kind> kinds = Collections.singleton(kind);
    val javaFileObjects = Try.of(() -> fileManager.list(location, basePackageName, kinds, /* recurse */ true))
                             .getOrElseThrow(asRuntimeException);

    String pathToPackageAndClass = basePackageName.replace(".", File.separator);
    Function<String, String> mapToClassName = s -> {
      String prefix = Arrays.stream(s.split(pathToPackageAndClass))
                            .findFirst()
                            .orElse("");
      return s.replaceFirst(prefix, "")
              .replaceAll(File.separator, ".");
    };

    return StreamSupport.stream(javaFileObjects.spliterator(), /* parallel */ true)
                        .filter(javaFileObject -> javaFileObject.getKind().equals(kind))
                        .map(FileObject::getName)
                        .map(fileObjectName -> fileObjectName.replace(".class", ""))
                        .map(mapToClassName)
                        .map(className -> Try.of(() -> Class.forName(className))
                                             .getOrElseThrow(asRuntimeException))
                        .collect(Collectors.toList());
  };

  @Test
  @DisplayName("should get classes recursively in given package")
  void test() {
    Collection<Class<?>> classes = findAllPackageClasses.apply(getClass().getPackage().getName());
    assertThat(classes).hasSizeGreaterThan(4);
    classes.stream().map(String::valueOf).forEach(log::info);
  }
}

PD: para simplificar las repeticiones para el manejo de errores, etc., estoy usando aquí vavr y lombokbibliotecas

se pueden encontrar otras implementaciones en mi repositorio daggerok / java-reflection-find-annotated-classes-or-method de GitHub


0

No pude encontrar un trabajo corto cortado por algo tan simple. Así que aquí está, lo hice yo mismo después de jugar un rato:

    Reflections reflections =
        new Reflections(new ConfigurationBuilder()
                .filterInputsBy(new FilterBuilder().includePackage(packagePath))
                .setUrls(ClasspathHelper.forPackage(packagePath))
                .setScanners(new SubTypesScanner(false)));

    Set<String> typeList = reflections.getAllTypes(); 

0

Si estás en Spring-land puedes usar PathMatchingResourcePatternResolver;

  PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
  Resource[] resources = resolver.getResources("classpath*:some/package/name/*.class");

    Arrays.asList(resources).forEach(r->{
        ...
    });

-4

No es posible, ya que es posible que no se carguen todas las clases del paquete, mientras que siempre conoce el paquete de una clase.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.