objetos - variables de instancia java
¿Debo crear una instancia de las variables de instancia en la declaración o en el constructor? (13)
Ambos métodos son aceptables. Tenga en cuenta que, en este último caso, b=new B()
puede no inicializarse si hay otro constructor presente. Piense en el código inicializador fuera del constructor como un constructor común y el código se ejecuta.
¿Hay alguna ventaja para cualquiera de los dos enfoques?
Ejemplo 1:
class A {
B b = new B();
}
Ejemplo 2:
class A {
B b;
A() {
b = new B();
}
}
Creo que el ejemplo 2 es preferible. Creo que la mejor práctica es declarar fuera del constructor e inicializar en el constructor.
El ejemplo 2 es menos flexible. Si agrega otro constructor, también debe recordar crear una instancia del campo en ese constructor. Simplemente cree una instancia del campo directamente, o introduzca la carga perezosa en algún lugar de un captador.
Si la creación de instancias requiere algo más que una simple new
, use un bloque de inicialización. Esto se ejecutará independientemente del constructor utilizado. P.ej
public class A {
private Properties properties;
{
try {
properties = new Properties();
properties.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("file.properties"));
} catch (IOException e) {
throw new ConfigurationException("Failed to load properties file.", e); // It''s a subclass of RuntimeException.
}
}
// ...
}
El segundo es un ejemplo de inicialización perezosa. La primera es una inicialización más simple, son esencialmente las mismas.
Hay una razón más sutil para inicializar fuera del constructor que nadie ha mencionado antes (muy específico, debo decir). Si está utilizando herramientas UML para generar diagramas de clase a partir del código (ingeniería inversa), la mayoría de las herramientas creo que notarán la inicialización del Ejemplo 1 y lo transferirán a un diagrama (si prefiere que muestre los valores iniciales, como Hago). No tomarán estos valores iniciales del Ejemplo 2. Nuevamente, esta es una razón muy específica: si está trabajando con herramientas UML, pero una vez que aprendí eso, estoy tratando de tomar todos mis valores predeterminados fuera del constructor a menos que, como era mencionado anteriormente, existe un problema de posible excepción o de lógica complicada.
La segunda opción es preferible ya que permite utilizar diferentes lógicas en los controladores para la creación de instancias de clase y utilizar el encadenamiento de los controladores. P.ej
class A {
int b;
// secondary ctor
A(String b) {
this(Integer.valueOf(b));
}
// primary ctor
A(int b) {
this.b = b;
}
}
Así que la segunda opción es más flexible.
Me quemé de una manera interesante hoy:
class MyClass extends FooClass {
String a = null;
public MyClass() {
super(); // Superclass calls init();
}
@Override
protected void init() {
super.init();
if (something)
a = getStringYadaYada();
}
}
¿Ves el error? Resulta que se llama al inicializador a a = null
después de llamar al constructor de superclase. Como el constructor de superclase llama a init (), la inicialización de a
va seguida de la inicialización a a = null
.
No he visto lo siguiente en las respuestas:
Una posible ventaja de tener la inicialización en el momento de la declaración podría ser el IDE actual, en el que puede saltar fácilmente a la declaración de una variable (en su mayoría, Ctrl-<hover_over_the_variable>-<left_mouse_click>
) desde cualquier parte del código. A continuación, verá inmediatamente el valor de esa variable. De lo contrario, debe "buscar" el lugar donde se realiza la inicialización (principalmente: constructor).
Esta ventaja es, por supuesto, secundaria a todos los otros razonamientos lógicos, pero para algunas personas esa "característica" podría ser más importante.
Otra opción sería utilizar la inyección de dependencia .
class A{
B b;
A(B b) {
this.b = b;
}
}
Esto elimina la responsabilidad de crear el objeto B
del constructor de A
Esto hará que su código sea más comprobable y más fácil de mantener a largo plazo. La idea es reducir el acoplamiento entre las dos clases A
y B
Un beneficio que esto le brinda es que ahora puede pasar cualquier objeto que extienda B
(o implemente B
si es una interfaz) al constructor de A y funcionará. Una desventaja es que abandonas la encapsulación del objeto B
, por lo que se expone al llamador del constructor A
Tendrá que considerar si los beneficios valen esta compensación, pero en muchos casos lo son.
Supongo que es casi una cuestión de gusto, siempre que la inicialización sea simple y no necesite ninguna lógica.
El enfoque del constructor es un poco más frágil si no usa un bloque de inicialización, porque si luego agrega un segundo constructor y se olvida de inicializar b allí, obtendrá un nulo b solo cuando use ese último constructor.
Consulte http://java.sun.com/docs/books/tutorial/java/javaOO/initial.html para obtener más detalles sobre la inicialización en Java (y para obtener explicaciones sobre los bloques de inicializador y otras funciones de inicialización no conocidas).
Usar inyección de dependencia o inicialización perezosa siempre es preferible, como ya se explicó detalladamente en otras respuestas.
Cuando no quieres o no puedes usar esos patrones, y para los tipos de datos primitivos, hay tres razones convincentes en las que puedo pensar por qué es preferible inicializar los atributos de clase fuera del constructor:
- repetición evitada = si tiene más de un constructor, o cuando necesitará agregar más, no tendrá que repetir la inicialización una y otra vez en todos los cuerpos de los constructores;
- legibilidad mejorada = puede ver fácilmente con un vistazo qué variables deberán inicializarse desde fuera de la clase;
- líneas reducidas de código = por cada inicialización realizada en la declaración habrá una línea menos en el constructor.
mi "regla" personal (que casi nunca se rompe) es:
- Declarar todas las variables al inicio de un bloque.
- hacer que todas las variables sean definitivas a menos que no puedan ser
- declarar una variable por línea
- nunca inicializar una variable donde se declara
- solo inicializa algo en un constructor cuando necesita datos del constructor para hacer la inicialización
Entonces tendría un código como:
public class X
{
public static final int USED_AS_A_CASE_LABEL = 1; // only exception - the compiler makes me
private static final int A;
private final int b;
private int c;
static
{
A = 42;
}
{
b = 7;
}
public X(final int val)
{
c = val;
}
public void foo(final boolean f)
{
final int d;
final int e;
d = 7;
// I will eat my own eyes before using ?: - personal taste.
if(f)
{
e = 1;
}
else
{
e = 2;
}
}
}
De esta manera, siempre estoy 100% seguro de dónde buscar las declaraciones de variables (al comienzo de un bloque) y sus asignaciones (tan pronto como tenga sentido después de la declaración). Esto también es potencialmente más eficiente ya que nunca inicializa una variable con un valor que no se usa (por ejemplo, declare e init vars y luego lance una excepción antes de que la mitad de esas vars necesiten tener un valor). Tampoco terminará haciendo una inicialización sin sentido (como int i = 0; y luego, antes de usar "i", haga i = 5 ;.
Valoro mucho la coherencia, por lo que seguir esta "regla" es algo que hago todo el tiempo y hace que sea mucho más fácil trabajar con el código, ya que no tienes que buscar para encontrar cosas.
Su experiencia puede ser diferente.
- No hay diferencia: el compilador coloca la inicialización de la variable de instancia en el constructor (es).
- La primera variante es más legible.
- No se puede tener un manejo de excepciones con la primera variante.
Además, hay un bloque de inicialización, que el compilador también pone en el (los) constructor (es):
{ a = new A(); }
Comprueba la explicación y el consejo de Sun.
De este tutorial :
Sin embargo, las declaraciones de campo no forman parte de ningún método, por lo que no pueden ejecutarse como lo son las declaraciones. En su lugar, el compilador de Java genera automáticamente el código de inicialización del campo de instancia y lo coloca en el constructor o constructores para la clase. El código de inicialización se inserta en un constructor en el orden en que aparece en el código fuente, lo que significa que un inicializador de campo puede usar los valores iniciales de los campos declarados antes.
Además, es posible que desee inicializar perezosamente su campo. En los casos en que la inicialización de un campo es una operación costosa, puede inicializarla tan pronto como sea necesario:
ExpensiveObject o;
public ExpensiveObject getExpensiveObject() {
if (o == null) {
o = new ExpensiveObject();
}
return o;
}
Y en última instancia (como lo señala Bill), por el bien de la administración de dependencias, es mejor evitar el uso del new
operador en cualquier parte dentro de su clase. En su lugar, es preferible utilizar la inyección de dependencia, es decir, dejar que otra persona (otra clase / marco) ejemplifique e inyecte las dependencias en su clase.