W3docs

Objetos Class de Java

Obtén objetos Class<T> en Java con Object.getClass(), literales .class y Class.forName. Guía práctica con ejemplos.

Todo en reflection parte de un objeto Class. Para cada tipo que carga la JVM — cada clase, interfaz, tipo de array, enum, anotación e incluso cada primitivo — existe exactamente una instancia de Class que lo describe. Ese objeto es tu punto de acceso a la estructura del tipo: su nombre, su superclase, sus miembros, sus anotaciones. Este capítulo cubre las tres formas de obtener un Class, qué contiene Class<T> y las pequeñas sorpresas que suelen confundir a la gente.

Tres formas de obtener un Class

Hay exactamente tres rutas, y cada una se adapta a una situación diferente.

1. El literal .class — conoces el tipo en tiempo de compilación.

Class<String> c1 = String.class;
Class<int[]> c2 = int[].class;
Class<Integer> c3 = int.class == Integer.class ? null : int.class;   // see "primitives" below

Es la forma más segura en tiempo de compilación y la más rápida — no hay búsqueda; el compilador incorpora la referencia directamente. Úsala siempre que puedas nombrar el tipo.

2. Object.getClass() — tienes una instancia.

Object o = "hello";
Class<?> c = o.getClass();        // class java.lang.String

getClass() devuelve la clase en tiempo de ejecución del objeto, que puede ser una subclase del tipo declarado de la variable. Object o = new ArrayList<>() hace que o.getClass() sea ArrayList.class, no Object.class. Su tipo estático es Class<?> porque el compilador solo sabe que o es algún Object.

3. Class.forName(String) — solo tienes un nombre.

Class<?> c = Class.forName("java.util.ArrayList");

Esta es la ruta dinámica: un nombre de clase completamente calificado como string, resuelto en tiempo de ejecución. Lanza ClassNotFoundException si no se puede cargar dicha clase. Es lo que usan los cargadores de plugins y los drivers JDBC. Existe una variante que carga sin inicializar: Class.forName(name, false, classLoader) omite los inicializadores estáticos hasta que la clase se usa activamente por primera vez.

Qué contiene Class<T>

Un objeto Class es una descripción completa. Los métodos principales:

Class<?> c = ArrayList.class;

c.getName();              // "java.util.ArrayList"        (binary name)
c.getSimpleName();        // "ArrayList"
c.getCanonicalName();     // "java.util.ArrayList"        (source-like)
c.getPackageName();       // "java.util"
c.getSuperclass();        // class java.util.AbstractList
c.getInterfaces();        // [List, RandomAccess, Cloneable, Serializable]
c.getModifiers();         // int bitset → Modifier.isPublic(...) etc.
c.isInterface();          // false
c.isEnum(); c.isArray(); c.isPrimitive(); c.isAnnotation();

Desde un Class puedes acceder a todos los miembros: getDeclaredFields(), getDeclaredMethods(), getDeclaredConstructors(), además de sus contrapartes públicas/heredadas get… (la distinción explicada en el capítulo de introducción). Los capítulos siguientes profundizan en cada uno.

Nombre binario vs. nombre simple vs. nombre canónico

Los métodos de nombrado difieren en aspectos que pueden causar problemas al registrar o comparar:

TipogetName()getSimpleName()getCanonicalName()
Stringjava.lang.StringStringjava.lang.String
int[][Iint[]int[]
String[][Ljava.lang.String;String[]java.lang.String[]
Map.Entry anidadojava.util.Map$EntryEntryjava.util.Map.Entry
clase anónimaOuter$1"" (vacío)null

getName() es el nombre binario — la forma interna de la JVM, con $ para el anidamiento y la críptica codificación de arrays [I / [L…;. Es lo que espera Class.forName. getCanonicalName() es la forma fuente que escribirías, y es null para tipos que no puedes nombrar en el código fuente (locales, clases anónimas). Usa getName() para round-trips con forName; usa getSimpleName()/getCanonicalName() para salida legible.

Los primitivos y los arrays también tienen objetos Class

Cada primitivo tiene su propio Class, distinto de su wrapper:

int.class == Integer.class      // false — two different Class objects
int.class.getName()             // "int"
Integer.TYPE == int.class       // true — TYPE is the primitive Class

void incluso tiene void.class (y Void.TYPE). Las clases de array son sintetizadas por la JVM: int[].class, String[][].class. arrayClass.getComponentType() elimina una dimensión (String[].class.getComponentType() es String.class). Estas distinciones importan cuando comparas tipos de parámetros en getMethodgetMethod("foo", int.class) y getMethod("foo", Integer.class) encuentran sobrecargas distintas.

Identidad de clase y cargadores de clases

La identidad de un objeto Class no es solo su nombre — es el par (nombre, cargador de clase que lo define). El mismo archivo .class cargado por dos cargadores de clase distintos produce dos objetos Class distintos e incompatibles. Un cast entre ellos lanza ClassCastException aunque los nombres coincidan. Esto es en su mayoría invisible en una aplicación sencilla (un solo cargador), pero es la raíz de muchos puzzles de "¡pero es la misma clase!" en servidores de aplicaciones, OSGi y sistemas de hot-reload. En reflection cotidiana, trata los objetos Class como singletons por tipo y compáralos con ==.

Ejemplo práctico: examinando tipos de tres formas

El programa obtiene objetos Class a través de las tres rutas y luego examina varios tipos — una clase normal, una interfaz, un array, un primitivo y un tipo anidado — para mostrar las diferencias de nombrado y estructurales.

java— editable, runs on the server

Lo que se puede extraer de la ejecución:

  • Las tres rutas convergen en el mismo tipo de objeto: un literal .class, una llamada a getClass() y una búsqueda con forName produjeron cada uno un Class completamente utilizable. La ruta que eliges depende de lo que sabes (el tipo, una instancia o solo un nombre) — el resultado tiene las mismas capacidades.
  • getClass() sobre la variable Greeter g devolvió Robot, no Greeter. El tipo declarado es irrelevante; getClass() siempre reporta la clase concreta en tiempo de ejecución. Por eso el despacho polimórfico y la inspección reflectiva ven el mismo tipo "real".
  • Los tres métodos de nombrado divergieron exactamente donde predice la tabla: String[] imprimió el nombre binario [Ljava.lang.String; con getName(), pero la forma legible String[] con las formas simple y canónica. Si alguna vez necesitas pasar un nombre de vuelta a forName, debe ser la forma de getName().
  • int.class == Integer.class fue false mientras que Integer.TYPE == int.class fue true. El primitivo y su wrapper son objetos Class distintos, e Integer.TYPE es simplemente un alias del primitivo. Confundirlos es la causa clásica de NoSuchMethodException cuando buscas una sobrecarga por tipo de parámetro.
  • Robot.class == new Robot().getClass() fue true: dentro de un solo cargador de clases, un tipo se corresponde con exactamente un objeto Class, por lo que == es la comparación correcta. Nunca necesitas .equals() en objetos Class en código de un solo cargador.

Errores comunes

  • forName ejecuta inicializadores estáticos (en su forma de un argumento). Cargar una clase puede tener efectos secundarios. Usa la forma de tres argumentos con initialize=false si solo quieres inspeccionar.
  • getSimpleName() puede estar vacío (clases anónimas) y getCanonicalName() puede ser null (locales, anónimas). No asumas que siempre son identificadores imprimibles.
  • Los genéricos se borran. List<String>.class es ilegal; solo existe List.class. Un Class no lleva información de argumentos de tipo — eso vive en Type/ParameterizedType, una API de reflection separada (y más avanzada). Consulta restricciones de genéricos para entender por qué funciona así el borrado.

Con el objeto Class en mano, el siguiente capítulo abre el primer cajón de miembros: campos — cómo inspeccionarlos, leerlos y escribirlos, incluso cuando son privados o finales.

Práctica

Práctica
Tienes 'Object o = new java.util.LinkedList<String>();' declarado como 'Object'. Llamas a 'o.getClass().getName()'. ¿Qué string devuelve y por qué no es 'java.lang.Object'?
Tienes 'Object o = new java.util.LinkedList<String>();' declarado como 'Object'. Llamas a 'o.getClass().getName()'. ¿Qué string devuelve y por qué no es 'java.lang.Object'?
Was this page helpful?