java - settitle - Constructor predeterminado vs. inicialización de campo en línea
para que sirve settitle en java (5)
La única diferencia que puedo pensar es que si agregas otro constructor
public Foo(int inX){
x = inX;
}
luego, en el primer ejemplo, ya no tendrías un constructor predeterminado, mientras que en el segundo ejemplo, todavía tendrías el constructor predeterminado (e incluso podrías hacer una llamada desde nuestro nuevo constructor si quisiéramos)
¿Cuál es la diferencia entre un constructor predeterminado y simplemente inicializar los campos de un objeto directamente?
¿Qué razones hay para preferir uno de los siguientes ejemplos sobre el otro?
Ejemplo 1
public class Foo
{
private int x = 5;
private String[] y = new String[10];
}
Ejemplo 2
public class Foo
{
private int x;
private String[] y;
public Foo()
{
x = 5;
y = new String[10];
}
}
La razón para preferir el Ejemplo uno es que es la misma funcionalidad para menos código (que siempre es bueno).
Aparte de eso, no hay diferencia.
Sin embargo, si tiene constructores explícitos, preferiría poner todos los códigos de inicialización (y encadenarlos) en lugar de dividirlos entre constructores e inicializadores de campo.
Los inicializadores se ejecutan antes que los cuerpos constructores. (Lo cual tiene implicaciones si tiene inicializadores y constructores, el código de constructor se ejecuta en segundo lugar y anula un valor inicializado)
Los inicializadores son buenos cuando siempre se necesita el mismo valor inicial (como en su ejemplo, una matriz de tamaño determinado o un número entero de valor específico), pero puede funcionar a su favor o en su contra:
Si tiene muchos constructores que inicializan las variables de manera diferente (es decir, con valores diferentes), los inicializadores son inútiles porque los cambios serán anulados y desperdiciados.
Por otro lado, si tiene muchos constructores que inician con el mismo valor, puede guardar líneas de código (y hacer que su código sea ligeramente más fácil de mantener) manteniendo la inicialización en un solo lugar.
Como dijo Michael, también hay una cuestión de gustos: puede que le guste mantener el código en un solo lugar. Aunque si tiene muchos constructores, su código no está en un lugar en ningún caso, por lo que preferiría los inicializadores.
Prefiero los inicializadores de campo y recurro a un constructor predeterminado cuando hay una lógica de inicialización compleja para realizar (por ejemplo, poblar un mapa, un ivar depende de otro a través de una serie de pasos heurísticos para ejecutar, etc.).
@Michael B dijo:
... Prefiero poner todo el código de inicialización en esos (y encadenarlos) en lugar de dividirlo entre constructores e inicializadores de campo.
MichaelB (me inclino ante el representante de 71+ K) tiene mucho sentido, pero mi tendencia es mantener las inicializaciones simples en los inicializadores finales en línea y hacer la parte compleja de las inicializaciones en el constructor.
¿Deberíamos favorecer que el inicializador de campo o el constructor den un valor predeterminado a un campo?
No consideraré las excepciones que puedan surgir durante la creación de instancias de campo y la creación de instancias vagas / impacientes en el campo que toquen otras inquietudes además de las inquietudes de legibilidad y mantenimiento.
Para dos códigos que realizan la misma lógica y producen el mismo resultado, se debe favorecer el camino con la mejor legibilidad y capacidad de mantenimiento.
TL; DR
Regla de oro :
manteniendo una coherencia en la forma de elegir
cada forma tiene sus ventajas y sus inconvenientes
no dude en mezclar ambas formas en una misma clase de acuerdo con la especificidad del campo
no dude en utilizar los inicializadores de campo para las instancias no caras de los campos de
NullPointerException
para evitarNullPointerException
.en las clases con un único constructor, el modo de inicializador de campo a menudo es más legible y menos detallado
en clases con múltiples constructores, si los constructores no tienen o tienen muy pocos acoplamientos entre ellos, el modo de inicializador de campo a menudo es más legible y menos detallado.
en clases con múltiples constructores donde las clases declaran muchos campos, ya que los campos con valores predeterminados pueden estar dispersos, valorarlos en un constructor central debería ser mejor. Por lo tanto, se debe favorecer el modo constructor.
en los otros casos de clases con múltiples constructores, ninguna de las formas es realmente mejor.
Con un código muy simple, la asignación durante la declaración de campo parece mejor y lo es.
Esto es menos detallado y más directo:
public class Foo {
private int x = 5;
private String[] y = new String[10];
}
que la forma del constructor:
public class Foo{
private int x;
private String[] y;
public Foo(){
x = 5;
y = new String[10];
}
}
En clases reales con especificidades tan reales, las cosas son diferentes.
De hecho, de acuerdo con las especificidades encontradas, de una manera, el otro o cualquiera de ellos debería ser favorecido.
Generalidad
- debe mantener una coherencia en el camino de elegir una solución u otra.
No cambie las reglas de decisión entre campos de la misma clase y entre clases también.
Especificidades a favor de valorar campos en constructor:
- para un campo que no tiene el mismo valor predeterminado para todas las instancias creadas.
Usar el inicializador de campo para valorar el campo sería propenso a errores ya que el valor definido en podría sobrescribirse durante la ejecución del constructor.
- la clase declara muchos campos
Identificar los valores predeterminados de los campos es más difícil ya que los campos con valores predeterminados pueden estar visiblemente alejados.
Especificidades a favor de la valoración de los campos en su declaración:
- la clase declara un solo constructor
El código de ejemplo OP ilustra este caso.
Es menos detallado y como solo tenemos un constructor, no dispersa la lógica de instanciación.
- la clase declara múltiples constructores pero sin o muy pocos acoplamientos entre ellos.
Si las reglas de validación de la instanciación y los parámetros pasados tienen poco o ningún acoplamiento entre los constructores, estamos en una configuración similar a la del caso del constructor único: la forma del inicializador de campo a menudo es más legible y menos detallada.
- para los campos de
Collections
(Lista, Mapa, etc.).
Instanciar sus clases concretas durante su declaración es a menudo una taquigrafía deseada, ya que no cambia realmente el estado de la instancia subyacente, es más bien una forma de evitar NullPointException
y reducir el código repetitivo en el código de procesamiento.
Especificidad con una ventaja no real para una u otra forma:
- la clase declara constructores múltiples con acoplamiento entre ellos.
Tener en una clase múltiples constructores que realizan un procesamiento común favorece la aparición de un constructor central que realiza la lógica y que todos los demás constructores delegan.
Para evitar la dispersión de las reglas de inicialización de la instancia entre múltiples constructores e inicializadores de campo, parece más relevante reunir la asignación de campos con valores predeterminados y reglas de validación en este constructor central.
Entonces, la asignación de valores predeterminados en los inicializadores de campo o en el constructor central llamado por todos los demás constructores se basa principalmente en la opinión: preferencia para ver todos los valores predeterminados en la parte superior de la clase o para establecerlos en el cuerpo del constructor central.
Caso de estudio 1
Comenzaré a partir de una clase simple de Car
que actualizaré para ilustrar estos puntos.
Car
declara 4 campos y 3 constructores que tienen relación entre ellos.
1. Dar un valor predeterminado en intializadores de campo para todos los campos es indeseable
public class Car {
private String name = "Super car";
private String origin = "Mars";
private int nbSeat = 5;
private Color color = Color.black;
...
...
// Other fields
...
public Car() {
}
public Car(int nbSeat) {
this.nbSeat = nbSeat;
}
public Car(int nbSeat, Color color) {
this.nbSeat = nbSeat;
this.color = color ;
}
}
Los valores predeterminados especificados en la declaración de campos no son todos confiables. Solo los campos de name
y origin
tienen valores predeterminados.
nbSeat
campos de nbSeat
y color
se valoran primero en su declaración, y luego pueden sobreescribirse en los constructores con argumentos.
Es propenso a errores y, además de esta manera de valorar campos, la clase disminuye su nivel de fiabilidad. ¿Cómo se puede confiar en cualquier valor predeterminado asignado durante la declaración de campos mientras que ha demostrado no ser confiable para dos campos?
2. Usar al constructor para valorar todos los campos y confiar en el encadenamiento de constructores está bien
public class Car {
private String name;
private String origin;
private int nbSeat;
private Color color;
...
...
// Other fields
...
public Car() {
this(5, Color.black);
}
public Car(int nbSeat) {
this(nbSeat, Color.black);
}
public Car(int nbSeat, Color color) {
this.name = "Super car";
this.origin = "Mars";
this.nbSeat = nbSeat;
this.color = color;
}
}
Esta solución es realmente buena ya que no crea duplicación, reúne toda la lógica en un lugar: el constructor con el número máximo de parámetros.
Tiene un único inconveniente: el requisito de encadenar la llamada a otro constructor.
Pero, ¿es un inconveniente?
3. Dar un valor predeterminado en los intializadores de campo para los campos que los constructores no les asignan un nuevo valor es mejor, pero aún tiene problemas de duplicación
Valoraremos los campos en su declaración con valores predeterminados para cualquier instancia creada y usaremos el constructor para campos sin valor predeterminado para cualquier instancia creada.
Al no valorar los campos nbSeat
y color
en su declaración, distinguimos claramente los campos con los valores predeterminados y los campos sin.
public class Car {
private String name = "Super car";
private String origin = "Mars";
private int nbSeat;
private Color color;
...
...
// Other fields
...
public Car() {
nbSeat = 5;
color = Color.black;
}
public Car(int nbSeat) {
this.nbSeat = nbSeat;
color = Color.black;
}
public Car(int nbSeat, Color color) {
this.nbSeat = nbSeat;
this.color = color;
}
}
Esta solución es bastante buena, pero repite la lógica de creación de instancias en cada instancia de Car en contra de la solución anterior.
En este simple ejemplo, podríamos comenzar a entender el problema de la duplicación, pero parece un poco molesto.
En casos reales, la duplicación puede ser muy importante ya que el constructor puede realizar el cálculo y la validación.
Tener un único constructor que realice la lógica de creación de instancias resulta muy útil.
Así que, finalmente, la asignación en la declaración de los campos no siempre le ahorrará al constructor delegar en otro constructor.
Aquí hay una versión mejorada.
4. Dar un valor predeterminado en intializadores de campo para campos que los constructores no les asignan un nuevo valor y confiar en el encadenamiento de constructores está bien
public class Car {
private String name = "Super car";
private String origin = "Mars";
private int nbSeat;
private Color color;
...
...
// Other fields
...
public Car() {
this(5, Color.black);
}
public Car(int nbSeat) {
this(nbSeat, Color.black);
}
public Car(int nbSeat, Color color) {
// assignment at a single place
this.nbSeat = nbSeat;
this.color = color;
// validation rules at a single place
...
}
}
Caso de estudio 2
Modificaremos la clase de Car
original.
Ahora, Car
declara 5 campos y 3 constructores que no tienen relación entre ellos.
1. Usar el constructor para valorar campos con valores predeterminados es indeseable
public class Car {
private String name;
private String origin;
private int nbSeat;
private Color color;
private Car replacingCar;
...
...
// Other fields
...
public Car() {
initDefaultValues();
}
public Car(int nbSeat, Color color) {
initDefaultValues();
this.nbSeat = nbSeat;
this.color = color;
}
public Car(Car replacingCar) {
initDefaultValues();
this.replacingCar = replacingCar;
// specific validation rules
}
private void initDefaultValues() {
name = "Super car";
origin = "Mars";
}
}
Como no valoramos los campos de name
y origin
en su declaración y no tenemos un constructor común invocado de forma natural por otros constructores, estamos obligados a introducir un método initDefaultValues()
e invocarlo en cada constructor.
Entonces no debemos olvidarnos de llamar a este método.
2. Dar un valor predeterminado en intializadores de campo para campos que los constructores no les asignan un nuevo valor está bien
public class Car {
private String name = "Super car";
private String origin = "Mars";
private int nbSeat;
private Color color;
private Car replacingCar;
...
...
// Other fields
...
public Car() {
}
public Car(int nbSeat, Color color) {
this.nbSeat = nbSeat;
this.color = color;
}
public Car(Car replacingCar) {
this.replacingCar = replacingCar;
// specific validation rules
}
}
Aquí no necesitamos tener un método initDefaultValues()
y llamarlo.
Los inicializadores de campo hacen el trabajo.
Conclusión
En cualquier caso) Los campos de valorización en los inicializadores de campo no deberían realizarse para todos los campos, sino solo para aquellos que no pueden ser sobrescritos por un constructor.
Caso de uso 1) En el caso de constructores múltiples con procesamiento común entre ellos, se basa principalmente en la opinión.
La solución 2 ( Usar constructor para valorar todos los campos y confiar en el encadenamiento de constructores ) y la solución 4 ( Dar un valor predeterminado en intializadores de campo para campos que los constructores no les asignan un nuevo valor y confiar en el encadenamiento de constructores ) aparecen como los más legibles , soluciones sostenibles y robustas.
Para las clases que declaran muchos campos, ya que los campos con valores predeterminados pueden estar dispersos, valorarlos en un constructor central debería ser mejor.
Caso de uso 2) En el caso de constructores múltiples sin procesamiento / relación común entre ellos como en el caso del constructor único, la solución 2 ( dando un valor predeterminado en intializadores de campo para los campos que los constructores no les asignan un nuevo valor ) se ve mejor .