Java Reflection: Llamar a Constructores
Instancia clases Java de forma reflexiva con Constructor.newInstance y Class.getDeclaredConstructor.
Crear un objeto sin escribir new es el truco reflexivo detrás de todo contenedor de inyección de dependencias, deserializador y cargador de plugins: tienes una Class y necesitas una instancia. El objeto Constructor<T> representa un constructor y crea instancias con newInstance(args...). Este capítulo cubre cómo encontrar constructores, llamarlos con argumentos, acceder a constructores private, y por qué el atajo antiguo Class.newInstance() está obsoleto.
Si eres nuevo en reflection, comienza con la introducción a reflection y luego vuelve aquí. Los mecanismos que se describen a continuación son similares a los que viste para llamar métodos y leer campos de forma reflexiva.
Encontrar constructores
Los constructores se buscan solo por tipos de parámetros — no hay nombre, ya que todos los constructores comparten el nombre de la clase:
Class<User> c = User.class;
Constructor<User> noArg = c.getDeclaredConstructor(); // ()
Constructor<User> twoArg = c.getDeclaredConstructor(String.class, int.class); // (String, int)
Constructor<?>[] pub = c.getConstructors(); // public only
Constructor<?>[] all = c.getDeclaredConstructors(); // any access levelComo en cualquier parte de reflection, los tipos de parámetros deben coincidir exactamente (int.class, no Integer.class), y getConstructor solo ve los public mientras que getDeclaredConstructor también ve los private/protected/de paquete. Nota que Constructor<T> es genérico en la clase que construye, por lo que newInstance devuelve una T con tipo (a diferencia del Object sin tipo de Method.invoke).
Construir instancias con newInstance
Constructor<User> ctor = User.class.getDeclaredConstructor(String.class, int.class);
User u = ctor.newInstance("ada", 36); // returns a typed UserLos argumentos funcionan igual que en Method.invoke: un Object[] varargs, con primitivos autoboxed. La diferencia es que no hay objeto destino — un constructor crea el destino. Las excepciones lanzadas por el cuerpo del constructor se envuelven en InvocationTargetException, exactamente igual que con los métodos; desenvuelve con getCause().
Acceder a constructores privados
Los singletons, clases utilitarias y builders suelen ocultar su constructor. Reflection lo ignora por completo con setAccessible(true):
Constructor<Singleton> ctor = Singleton.class.getDeclaredConstructor();
ctor.setAccessible(true); // bypass the private modifier
Singleton fresh = ctor.newInstance(); // a SECOND instance — breaks the singleton!Esto es genuinamente poderoso y genuinamente peligroso: anula la garantía del singleton, el contrato de "sin instancias" de una clase utilitaria, y cualquier invariante que el constructor protegía. (Un singleton de tipo enum es la única forma que reflection no puede instanciar — Constructor.newInstance rechaza explícitamente los tipos enum con IllegalArgumentException, lo que es parte de por qué el "singleton enum" es el patrón recomendado.)
Por qué Class.newInstance() está obsoleto
Verás código antiguo que usa el atajo clazz.newInstance():
User u = User.class.newInstance(); // DEPRECATED since Java 9Está obsoleto por dos razones reales:
- Solo llama al constructor sin argumentos. No hay forma de pasar argumentos.
- Maneja mal las excepciones. Si el constructor sin argumentos lanza una excepción checked,
Class.newInstance()la propaga sin declararla — anulando el análisis de excepciones checked del compilador.
El reemplazo siempre es:
User u = User.class.getDeclaredConstructor().newInstance();Esta línea es un poco más larga, llama a un constructor que elegiste explícitamente, y envuelve las excepciones del constructor en InvocationTargetException para que nada escape sin declarar. Úsala como el idioma estándar incluso para el caso sin argumentos.
Un ejemplo práctico: una pequeña fábrica reflexiva
El programa construye objetos de tres formas: un constructor público con múltiples argumentos, un constructor private accedido mediante setAccessible, y el idioma moderno sin argumentos — luego muestra un constructor que lanza excepciones siendo envuelto, y la firma del atajo obsoleto como contraste.
Qué extraer de la ejecución:
- La fábrica genérica
buildcreóWidgetyHiddena partir de unaClassmás un array de tipos de parámetros — sin nombrar ningún tipo en una expresiónnew. Esa firma,<T> T build(Class<T>, Class<?>[], Object...), es esencialmente lo que parece el núcleo de instanciación de un contenedor de DI: dale un tipo y argumentos, y obtendrás una instancia. getDeclaredConstructor().newInstance()produjo elWidgetpredeterminado, demostrando el reemplazo moderno deClass.newInstance(). Siempre prefiere este: te permite elegir el constructor y enruta las excepciones del constructor a través deInvocationTargetExceptionen lugar de filtrar excepciones checked no declaradas.- La instancia reflexiva de
Hiddenno era el mismo objeto queHidden.INSTANCE(same instance? false).setAccessible(true)pasó directamente por el constructorprivatey creó una segunda instancia — prueba concreta de que reflection puede romper la garantía central de un singleton. Los singletons defensivos lanzan una excepción desde el constructor si ya existe una instancia; los enums son inmunes por construcción. - El constructor que rechazó un tamaño negativo lanzó
IllegalArgumentExceptiondesde su cuerpo, y eso se manifestó comoInvocationTargetExceptioncon la causa real dentro — el mismo envoltorio queMethod.invoke. La validación en tiempo de construcción se preserva a través de reflection; solo tienes que desenvolver para verla. Constructor<T>devolvió unaTcon tipo (Widget,Hidden) sin cast, a diferencia delObjectsin tipo deMethod.invoke. Dado que el constructor es genérico en la clase que construye, la fábrica mantiene su seguridad de tipos en su límite aunque todo el interior sea reflexivo.