Constructores en Java
Usa constructores para inicializar objetos Java: predeterminados, parametrizados, sobrecargados y encadenamiento de constructores.
Un constructor es un método especial que se ejecuta una sola vez cuando se crea un objeto. Su función es llevar la nueva instancia de "todos los campos en su valor predeterminado" a "lista para usar". Hasta ahora has estado asignando campos uno por uno después de new Dog(); un constructor te permite agrupar ese trabajo en la llamada a new.
Anatomía de un constructor
Un constructor se parece a un método, con tres diferencias: tiene el mismo nombre que la clase, no tiene tipo de retorno (ni siquiera void), y solo es llamado por new:
public class Point {
int x, y;
public Point(int x, int y) { // constructor
this.x = x;
this.y = y;
}
}
Point p = new Point(3, 4); // runs the constructorLa lista de argumentos va dentro de () después del nombre de la clase en la expresión new. Java hace coincidir esa lista de argumentos con un constructor de la clase, igual que hace coincidir una llamada a método con un método.
El constructor predeterminado
Si no declaras ningún constructor, Java proporciona a la clase un constructor predeterminado — un constructor sin argumentos que no hace nada:
public class Empty { } // compiler generates a no-arg constructor
Empty e = new Empty(); // worksAsí es como new Dog() funcionaba en los ejemplos anteriores aunque nunca escribimos public Dog() { ... }. En el momento en que declaras cualquier constructor propio, el predeterminado gratuito desaparece:
public class Point {
int x, y;
public Point(int x, int y) { this.x = x; this.y = y; }
}
Point p = new Point(); // ERROR — no no-arg constructor existsSi quieres ambos, declara ambos — consulta la sobrecarga a continuación.
¿Por qué usar un constructor?
Dos razones.
1. Campos obligatorios. Un constructor te permite hacer que algunos campos sean no opcionales. Con la asignación de campo público y luego asignación, nada impide que un llamador cree una Person sin name. Con un constructor Person(String name), no es posible.
2. Invariantes. Un constructor puede validar sus argumentos antes de que el objeto exista en forma utilizable. Si un Circle requiere un radio positivo, el constructor puede lanzar una excepción con un valor negativo — el objeto defectuoso nunca se crea.
public class Circle {
double radius;
public Circle(double radius) {
if (radius <= 0) throw new IllegalArgumentException("radius must be > 0");
this.radius = radius;
}
}El llamador ahora no puede obtener un Circle con un radio no positivo.
Constructores sobrecargados
Una clase puede tener múltiples constructores con diferentes listas de parámetros, exactamente como los métodos sobrecargados:
public class Rectangle {
double width, height;
public Rectangle() { this(1, 1); }
public Rectangle(double side) { this(side, side); } // a square
public Rectangle(double w, double h) { this.width = w; this.height = h; }
}Ahora los tres compilan:
Rectangle a = new Rectangle(); // 1 x 1
Rectangle b = new Rectangle(5); // 5 x 5
Rectangle c = new Rectangle(3, 4); // 3 x 4Constructores de copia
Una sobrecarga común es el constructor de copia — uno que toma otra instancia de la misma clase y construye una copia independiente de ella. Java no tiene constructor de copia incorporado (a diferencia de C++), así que escribes el tuyo propio cuando lo necesitas:
public class Point {
int x, y;
public Point(int x, int y) { this.x = x; this.y = y; }
public Point(Point other) { this(other.x, other.y); } // copy constructor
}
Point a = new Point(3, 4);
Point b = new Point(a); // a separate Point with the same valuesDebido a que b es un objeto nuevo, cambiar b.x después no afecta a a. Para clases cuyos campos son a su vez objetos mutables, copia también los campos (una copia profunda) si quieres que la copia sea completamente independiente.
this(...) — encadenamiento de constructores
Dentro de un constructor, this(args) llama a otro constructor de la misma clase. Así es como el ejemplo de Rectangle anterior evitó duplicar el código de asignación de campos: los dos constructores de conveniencia delegan al completo.
Dos reglas:
this(...)debe ser la primera sentencia en el cuerpo del constructor.- Un constructor puede encadenarse a un solo otro constructor.
public Rectangle() { this(1, 1); } // ok
public Rectangle(double s) { System.out.println("hi"); this(s, s); } // ERRORLa razón de la regla de "primera sentencia" es que la JVM tiene que inicializar completamente el objeto exactamente una vez antes de que se ejecute cualquier otro código.
super(...) — llamar al padre
Cuando una clase extiende a otra, cada constructor llama explícitamente a un constructor padre con super(args), o implícitamente llama al constructor sin argumentos del padre. Cubriremos esto completamente en los capítulos de herencia y palabra clave super; por ahora, solo debes saber que super(...) existe y tiene la misma regla de primera sentencia que this(...).
Los constructores no pueden devolver un valor
Un constructor no tiene tipo de retorno — ni siquiera void. Si escribes uno:
public void Point(int x, int y) { ... } // not a constructor!…es un método normal que resulta llamarse Point. El compilador no te advertirá; new Point(3, 4) entonces fallará al compilar porque el verdadero constructor que busca no existe. Este es un error tipográfico sorprendentemente común.
Orden de inicialización
Para una sola clase, el orden es:
- Se asignan los valores predeterminados de los campos (p. ej., los campos
intse vuelven0). - Los inicializadores de campo en línea (
int count = 5;) y cualquier bloque inicializador de instancia se ejecutan en el orden en que aparecen. - Se ejecuta el cuerpo del constructor.
public class Demo {
int a = compute("a-init", 1);
int b;
{ b = compute("b-block", 2); } // instance initializer
public Demo() {
System.out.println("constructor; a=" + a + ", b=" + b);
}
static int compute(String label, int v) {
System.out.println(label);
return v;
}
}new Demo() imprime a-init, b-block, y luego constructor; a=1, b=2.
En la práctica, casi toda la inicialización pertenece al cuerpo del constructor. Los bloques inicializadores de instancia son raros en código real; existen principalmente para situaciones como las clases anónimas (tratadas más adelante).
Un ejemplo completo
¿Qué sigue?
Dentro de los constructores ya has visto this.field = field para distinguir un parámetro de un campo. El capítulo sobre la palabra clave this explica con precisión qué es this y los pocos lugares donde necesitas escribirlo explícitamente.