¿Cómo funciona la palabra clave "final" en Java? (Todavía puedo modificar un objeto).

480

En Java usamos finalpalabras clave con variables para especificar que sus valores no se deben cambiar. Pero veo que puedes cambiar el valor en el constructor / métodos de la clase. Nuevamente, si la variable es, staticentonces es un error de compilación.

Aquí está el código:

import java.util.ArrayList;
import java.util.List;

class Test {
  private final List foo;

  public Test()
  {
      foo = new ArrayList();
      foo.add("foo"); // Modification-1
  }
  public static void main(String[] args) 
  {
      Test t = new Test();
      t.foo.add("bar"); // Modification-2
      System.out.println("print - " + t.foo);
  }
}

El código anterior funciona bien y sin errores.

Ahora cambie la variable como static:

private static final List foo;

Ahora es un error de compilación. ¿Cómo funciona esto finalrealmente?

GS
fuente
dado que foo no es visible, ¿cómo podría compilarse?
Björn Hallström
55
@therealprashant eso no es cierto. Las variables estáticas privadas son válidas, son accesibles desde métodos estáticos dentro de la clase en la que se definen. Una variable estática significa que la variable existe una vez y no está vinculada a una instancia de una clase.
mbdavis
3
@mbdavis ¡Oh, sí! gracias. Pero aún así no eliminaré el comentario para ayudar a las personas que piensan como yo y luego su comentario los hará pensar en la dirección correcta.
2015
@therealprashant está bien, no te preocupes!
mbdavis

Respuestas:

518

Siempre se le permite inicializar una finalvariable. El compilador se asegura de que pueda hacerlo solo una vez.

Tenga en cuenta que los métodos de llamada en un objeto almacenado en una finalvariable no tienen nada que ver con la semántica de final. En otras palabras: se finaltrata solo de la referencia en sí, y no del contenido del objeto referenciado.

Java no tiene concepto de inmutabilidad de objeto; Esto se logra diseñando cuidadosamente el objeto, y es un esfuerzo lejos de ser trivial.

Marko Topolnik
fuente
12
intente hacer t.foo = new ArrayList (); en el método principal y obtendrá un error de compilación ... la referencia foo está vinculada a un solo objeto final de ArrayList ... no puede apuntar a ninguna otra ArrayList
Code2Interface
50
hmmm Se trata de referencia, no de valor. ¡Gracias!
GS
2
Tengo una pregunta. Alguien que conozco afirmó que "final" también hace que la variable se almacene en la pila. ¿Es esto correcto? He buscado en todas partes y no pude encontrar ninguna referencia que pueda aprobar o desaprobar este reclamo. He buscado en la documentación de Java y Android. También se buscó el "modelo de memoria Java". Tal vez funciona de esta manera en C / C ++, pero no creo que funcione de esta manera en Java. ¿Estoy en lo correcto?
Desarrollador de Android
44
@androiddeveloper Nada en Java puede controlar explícitamente la colocación de la pila / montón. Más específicamente, la colocación de la pila según lo decidido por el compilador JIT de HotSpot está sujeta a análisis de escape , que es mucho más complicado que verificar si una variable lo es final. Los objetos mutables también se pueden asignar en pila. finalSin embargo, los campos pueden ayudar al análisis de escape, pero esa es una ruta bastante indirecta. También tenga en cuenta que efectivamente las variables finales tienen un tratamiento idéntico al marcado finalen el código fuente.
Marko Topolnik
55
finalestá presente en el archivo de clase y tiene consecuencias semánticas significativas para optimizar el tiempo de ejecución. También puede incurrir en costos porque el JLS tiene una fuerte garantía sobre la consistencia de los finalcampos de un objeto. Por ejemplo, el procesador ARM debe usar una instrucción de barrera de memoria explícita al final de cada constructor de una clase que tenga finalcampos. Sin embargo, en otros procesadores esto no es necesario.
Marko Topolnik
574

Esta es una pregunta de entrevista favorita . Con estas preguntas, el entrevistador trata de averiguar qué tan bien comprende el comportamiento de los objetos con respecto a los constructores, métodos, variables de clase (variables estáticas) y variables de instancia.

import java.util.ArrayList;
import java.util.List;

class Test {
    private final List foo;

    public Test() {
        foo = new ArrayList();
        foo.add("foo"); // Modification-1
    }

    public void setFoo(List foo) {
       //this.foo = foo; Results in compile time error.
    }
}

En el caso anterior, hemos definido un constructor para 'Test' y le hemos dado un método 'setFoo'.

Acerca del constructor: el constructor se puede invocar solo una vez por creación de objeto utilizando la newpalabra clave. No puede invocar al constructor varias veces, porque el constructor no está diseñado para hacerlo.

Acerca del método: se puede invocar un método tantas veces como desee (incluso nunca) y el compilador lo sabe.

escenario 1

private final List foo;  // 1

fooes una variable de instancia Cuando creamos Testun objeto de clase, la variable de instancia foose copiará dentro del objeto de Testclase. Si asignamos foodentro del constructor, entonces el compilador sabe que el constructor se invocará solo una vez, por lo que no hay problema para asignarlo dentro del constructor.

Si asignamos foodentro de un método, el compilador sabe que un método puede llamarse varias veces, lo que significa que el valor tendrá que cambiarse varias veces, lo que no está permitido para una finalvariable. ¡Entonces el compilador decide que el constructor es una buena opción! Puede asignar un valor a una variable final solo una vez.

Escenario 2

private static final List foo = new ArrayList();

fooahora es una variable estática . Cuando creamos una instancia de Testclase, foono se copiará al objeto porque fooes estático. Ahora foono es una propiedad independiente de cada objeto. Esta es una propiedad de Testclase. Pero foopueden ser vistos por varios objetos y si cada objeto que se crea utilizando la newpalabra clave invocará finalmente al Testconstructor que cambia el valor en el momento de la creación de múltiples objetos (Remember static foono se copia en cada objeto, sino que se comparte entre varios objetos) .)

Escenario 3

t.foo.add("bar"); // Modification-2

Arriba Modification-2es de su pregunta. En el caso anterior, no está cambiando el primer objeto al que se hace referencia, pero está agregando contenido dentro del foocual está permitido. El compilador se queja si intenta asignar un new ArrayList()a la foovariable de referencia.
Regla Si ha inicializado una finalvariable, no puede cambiarla para referirse a un objeto diferente. (En este caso ArrayList)

las clases finales no pueden subclasificarse, los métodos
finales no pueden anularse. (Este método está en la superclase) los métodos
finales pueden anularse. (Lea esto en forma gramatical. Este método está en una subclase)

AmitG
fuente
1
Solo para aclarar. En el escenario 2, ¿está diciendo que foose establecería varias veces a pesar de la designación final si foose establece en la clase Prueba y se crean múltiples instancias de Prueba?
Rawr
No entendí la última línea para el escenario 2: Pero foopuede ser ... múltiples objetos). ¿ Eso significa que si creo múltiples objetos a la vez, entonces qué objeto está inicializando la variable final depende de la ejecución?
Saumya Suhagiya
1
Creo que una forma útil de pensar sobre el escenario 3 es que está asignando finala la dirección de memoria a la que hace referencia foouna ArrayList. No está asignando finala la dirección de memoria referenciada por el primer elemento de foo(o cualquier elemento para el caso). Por lo tanto no puedes cambiar foopero puedes cambiar foo[0].
Pinkerton
@Rawr Tal como está, el escenario 2 causaría un error en tiempo de compilación porque de foo = new ArrayList();- foose refiere a la variable estática porque estamos dentro de la misma clase.
flow2k
Soy un desarrollador de C ++ que aprende Java. ¿Es seguro pensar finalen una variable como lo mismo que la constpalabra clave en C ++?
Doug Barbieri
213

La palabra clave final tiene numerosas formas de uso:

  • Una clase final no se puede subclasificar.
  • Las subclases no pueden anular un método final
  • Una variable final solo se puede inicializar una vez

Otro uso:

  • Cuando se define una clase interna anónima dentro del cuerpo de un método, todas las variables declaradas finales en el alcance de ese método son accesibles desde la clase interna

Una variable de clase estática existirá desde el inicio de la JVM, y debe inicializarse en la clase. El mensaje de error no aparecerá si haces esto.

czupe
fuente
24
Esta es, con mucho, mi respuesta favorita. Simple y directo, esto es lo que esperaría leer en documentos en línea sobre Java.
RAnders00
Entonces, ¿en las variables estáticas podemos inicializar tantas veces como queramos?
jorge saraiva
1
@jorgesaraiva sí, las variables estáticas no son constantes.
czupe
1
@jorgesaraiva Puede asignar (no inicializar ) staticcampos (siempre que no lo sean final) tantas veces como desee. Vea este wiki para conocer la diferencia entre asignación e inicialización .
56

La finalpalabra clave se puede interpretar de dos maneras diferentes dependiendo de para qué se utiliza:

Tipos de valor: para ints, s, doubleetc., garantizará que el valor no pueda cambiar,

Tipos de referencia: para referencias a objetos, finalasegura que la referencia nunca cambiará, lo que significa que siempre se referirá al mismo objeto. No garantiza en absoluto que los valores dentro del objeto sean referidos para permanecer igual.

Como tal, final List<Whatever> foo;asegura que foosiempre se refiere a la misma lista, pero el contenido de dicha lista puede cambiar con el tiempo.

Smallhacker
fuente
23

Si hace fooestática, debe inicializarla en el constructor de la clase (o en línea donde la defina) como en los siguientes ejemplos.

Constructor de clase (no instancia):

private static final List foo;

static
{
   foo = new ArrayList();
}

En línea:

private static final List foo = new ArrayList();

El problema aquí no es cómo funciona el finalmodificador, sino cómo funciona el staticmodificador.

El finalmodificador impone una inicialización de su referencia para cuando finalice la llamada a su constructor (es decir, debe inicializarlo en el constructor).

Cuando inicializa un atributo en línea, se inicializa antes de que se ejecute el código que ha definido para el constructor, por lo que obtiene los siguientes resultados:

  • si fooes static, foo = new ArrayList()se ejecutará antes de que se ejecute el static{}constructor que ha definido para su clase
  • si foono es así static, foo = new ArrayList()se ejecutará antes de ejecutar su constructor

Cuando no inicializa un atributo en línea, el finalmodificador exige que lo inicialice y que debe hacerlo en el constructor. Si también tiene un staticmodificador, el constructor tendrá que inicializar el atributo es el bloque de inicialización de clase: static{}.

El error que obtiene en su código se debe al hecho de que static{}se ejecuta cuando se carga la clase, antes del momento en que crea una instancia de un objeto de esa clase. Por lo tanto, no habrá inicializado foocuando se crea la clase.

Piense en el static{}bloque como un constructor de un objeto de tipo Class. Aquí es donde debe hacer la inicialización de los static finalatributos de su clase (si no se hace en línea).

Nota al margen:

El finalmodificador asegura la constancia solo para tipos primitivos y referencias.

Cuando declaras un finalobjeto, lo que obtienes es una final referencia a ese objeto, pero el objeto en sí no es constante.

Lo que realmente está logrando al declarar un finalatributo es que, una vez que declara un objeto para su propósito específico (como el final Listque ha declarado), eso y solo ese objeto se usará para ese propósito: no podrá cambiar List fooa otro List, pero aún puede alterarlo Listagregando / eliminando elementos (el Listque esté usando será el mismo, solo con su contenido alterado).

lucian.pantelimon
fuente
8

Esta es una muy buena pregunta de entrevista. A veces incluso pueden preguntarte cuál es la diferencia entre un objeto final y un objeto inmutable.

1) Cuando alguien menciona un objeto final, significa que la referencia no se puede cambiar, pero su estado (variables de instancia) se puede cambiar.

2) Un objeto inmutable es aquel cuyo estado no se puede cambiar, pero se puede cambiar su referencia. Ex:

    String x = new String("abc"); 
    x = "BCG";

La variable de referencia x se puede cambiar para señalar una cadena diferente, pero el valor de "abc" no se puede cambiar.

3) Las variables de instancia (campos no estáticos) se inicializan cuando se llama a un constructor. Entonces puede inicializar valores a sus variables dentro de un constructor.

4) "Pero veo que puedes cambiar el valor en el constructor / métodos de la clase". - No se puede cambiar dentro de un método.

5) Una variable estática se inicializa durante la carga de clases. Por lo tanto, no se puede inicializar dentro de un constructor, se debe hacer incluso antes. Por lo tanto, debe asignar valores a una variable estática durante la declaración misma.

user892871
fuente
7

La finalpalabra clave en java se usa para restringir al usuario. La finalpalabra clave java se puede usar en muchos contextos. Final puede ser:

  1. variable
  2. método
  3. clase

La finalpalabra clave se puede aplicar con las variables, una finalvariable que no tiene valor, se llama finalvariable en blanco o finalvariable no inicializada . Se puede inicializar solo en el constructor. La finalvariable en blanco puede ser statictambién la que se inicializará solo en el staticbloque.

Variable final de Java:

Si realiza alguna variable como final, no puede cambiar el valor de la finalvariable (será constante).

Ejemplo de finalvariable

Hay un límite de velocidad de la variable final, vamos a cambiar el valor de esta variable, pero no se puede cambiar porque la variable final una vez asignado un valor nunca se puede cambiar.

class Bike9{  
    final int speedlimit=90;//final variable  
    void run(){  
        speedlimit=400;  // this will make error
    }  

    public static void main(String args[]){  
    Bike9 obj=new  Bike9();  
    obj.run();  
    }  
}//end of class  

Clase final de Java:

Si haces alguna clase como final, no puedes extenderla .

Ejemplo de clase final

final class Bike{}  

class Honda1 extends Bike{    //cannot inherit from final Bike,this will make error
  void run(){
      System.out.println("running safely with 100kmph");
   }  

  public static void main(String args[]){  
      Honda1 honda= new Honda();  
      honda.run();  
      }  
  }  

Método final de Java:

Si realiza algún método como final, no puede anularlo .

Ejemplo de finalmétodo (run () en Honda no puede anular run () en Bike)

class Bike{  
  final void run(){System.out.println("running");}  
}  

class Honda extends Bike{  
   void run(){System.out.println("running safely with 100kmph");}  

   public static void main(String args[]){  
   Honda honda= new Honda();  
   honda.run();  
   }  
}  

compartido desde: http://www.javatpoint.com/final-keyword

Ali Ziaee
fuente
7

Vale la pena mencionar algunas definiciones sencillas:

Clases / Métodos

Puede declarar algunos o todos los métodos de una clase como final, para indicar que el método no puede ser anulado por subclases.

Variables

Una vez finalque se ha inicializado una variable, siempre contiene el mismo valor.

final básicamente, evitar sobrescribir / sobrescribir por cualquier cosa (subclases, variable "reasignar"), según el caso.

ivanleoncz
fuente
1
Creo que la definición final sobre las variables es un poco corta; "En Java, cuando la palabra clave final se usa con una variable de tipos de datos primitivos (int, float, etc.), el valor de la variable no se puede cambiar, pero cuando final se usa con variables no primitivas (tenga en cuenta que las variables no primitivas siempre son referencias a objetos en Java), los miembros del objeto referido pueden cambiarse. final para variables no primitivas solo significa que no pueden cambiarse para referirse a ningún otro objeto ". geeksforgeeks.org/g-fact-48
ceyun
También válido, especialmente para mencionar como casos primitivos y no primitivos. Tks
ivanleoncz
4

finales una palabra clave reservada en Java para restringir al usuario y se puede aplicar a variables miembro, métodos, clases y variables locales. Las variables finales a menudo se declaran con la staticpalabra clave en Java y se tratan como constantes. Por ejemplo:

public static final String hello = "Hello";

Cuando usamos la finalpalabra clave con una declaración de variable, el valor almacenado dentro de esa variable no se puede cambiar más tarde.

Por ejemplo:

public class ClassDemo {
  private final int var1 = 3;
  public ClassDemo() {
    ...
  }
}

Nota : Una clase declarada como final no se puede extender o heredar (es decir, no puede haber una subclase de la superclase). También es bueno tener en cuenta que los métodos declarados como finales no pueden ser anulados por subclases.

Los beneficios de usar la palabra clave final se abordan en este hilo .

Desta Haileselassie Hagos
fuente
2
the value stored inside that variable cannot be changed latterEs parcialmente cierto. Es cierto solo para tipos de datos primitivos. En caso de que algún objeto se haga como final, como la lista de arrays, su valor puede cambiar pero no la referencia. ¡Gracias!
GS
3

Supongamos que tiene dos huchas, rojo y blanco. Usted asigna estas cajas de dinero solo a dos hijos y no se les permite intercambiar sus cajas. Entonces, tienes huchas rojas o blancas (final), no puedes modificar la caja pero puedes poner dinero en tu caja. A nadie le importa (Modificación-2).

huseyin
fuente
2

Lee todas las respuestas.

Hay otro caso de usuario donde finalse puede usar la palabra clave, es decir, en un argumento de método:

public void showCaseFinalArgumentVariable(final int someFinalInt){

   someFinalInt = 9; // won't compile as the argument is final

}

Se puede usar para variables que no se deben cambiar.

Pritam Banerjee
fuente
1

Cuando lo hace estático final, debe inicializarse en un bloque de inicialización estático

    private static final List foo;

    static {
        foo = new ArrayList();
    }

    public Test()
    {
//      foo = new ArrayList();
        foo.add("foo"); // Modification-1
    }
Evgeniy Dorofeev
fuente
1

La finalpalabra clave indica que una variable solo puede inicializarse una vez. En su código, solo está realizando una inicialización de final para que se cumplan los términos. Esta declaración realiza la inicialización solitaria de foo. Tenga en cuenta que final! = Inmutable, solo significa que la referencia no puede cambiar.

foo = new ArrayList();

Cuando declara fooque static finalla variable debe inicializarse cuando se carga la clase y no puede confiar en la instanciación (también llamada llamada al constructor) para inicializar fooya que los campos estáticos deben estar disponibles sin una instancia de una clase. No hay garantía de que se haya llamado al constructor antes de usar el campo estático.

Cuando ejecuta su método en el static finalescenario, la Testclase se carga antes de instanciar ten este momento, no hay instanciación de foosignificado, no se ha inicializado, por lo que foose establece en el valor predeterminado para todos los objetos que es null. En este punto, supongo que su código arroja un NullPointerExceptioncuando intenta agregar un elemento a la lista.

Kevin Bowersox
fuente
1

En primer lugar, el lugar en su código donde está inicializando (es decir, asignando por primera vez) foo está aquí:

foo = new ArrayList();

foo es un objeto (con tipo Lista), por lo que es un tipo de referencia , no un tipo de valor (como int). Como tal, contiene una referencia a una ubicación de memoria (por ejemplo, 0xA7D2A834) donde se almacenan los elementos de la Lista. Líneas como esta

foo.add("foo"); // Modification-1

no cambie el valor de foo (que, de nuevo, es solo una referencia a una ubicación de memoria). En cambio, simplemente agregan elementos en esa ubicación de memoria referenciada. Para violar la palabra clave final , debería intentar reasignar foo de la siguiente manera:

foo = new ArrayList();

Eso te daría un error de compilación.


Ahora, con eso fuera del camino, piense en lo que sucede cuando agrega la palabra clave estática .

Cuando NO tiene la palabra clave estática, cada objeto que crea una instancia de la clase tiene su propia copia de foo. Por lo tanto, el constructor asigna un valor a una copia nueva en blanco de la variable foo, que está perfectamente bien.

Sin embargo, cuando TIENES la palabra clave estática, solo existe un foo en la memoria que está asociado con la clase. Si fuera a crear dos o más objetos, el constructor intentaría reasignar ese foo cada vez, violando la palabra clave final .

Niko Bellic
fuente
1
  1. Como la variable final no es estática, se puede inicializar en el constructor. Pero si lo hace estático, el constructor no puede inicializarlo (porque los constructores no son estáticos).
  2. No se espera que la adición a la lista se detenga al hacer la lista final. finalsolo une la referencia a un objeto en particular. Usted es libre de cambiar el "estado" de ese objeto, pero no el objeto en sí.
Ankit
fuente
1

Los siguientes son contextos diferentes donde se usa final.

Variables finales Una variable final solo se puede asignar una vez. Si la variable es una referencia, esto significa que la variable no se puede volver a vincular para hacer referencia a otro objeto.

class Main {
   public static void main(String args[]){
      final int i = 20;
      i = 30; //Compiler Error:cannot assign a value to final variable i twice
   }
}

A la variable final se le puede asignar un valor más tarde (no es obligatorio asignarle un valor cuando se declara), pero solo una vez.

Clases finales Una clase final no se puede extender (heredar)

final class Base { }
class Derived extends Base { } //Compiler Error:cannot inherit from final Base

public class Main {
   public static void main(String args[]) {
   }
}

Métodos finales Las subclases no pueden anular un método final.

//Error in following program as we are trying to override a final method.
class Base {
  public final void show() {
       System.out.println("Base::show() called");
    }
}     
class Derived extends Base {
    public void show() {  //Compiler Error: show() in Derived cannot override
       System.out.println("Derived::show() called");
    }
}     
public class Main {
    public static void main(String[] args) {
        Base b = new Derived();;
        b.show();
    }
}
roottraveller
fuente
1

Pensé en escribir una respuesta actualizada y en profundidad aquí.

final La palabra clave se puede utilizar en varios lugares.

  1. clases

A final classsignifica que ninguna otra clase puede extender esa clase final. Cuando Java Run Time ( JRE ) sabe que una referencia de objeto está en el tipo de una clase final (digamos F), sabe que el valor de esa referencia solo puede estar en el tipo de F.

Ex:

F myF;
myF = new F();    //ok
myF = someOther;  //someOther cannot be in type of a child class of F.
                  //because F cannot be extended.

Entonces, cuando ejecuta cualquier método de ese objeto, ese método no necesita ser resuelto en tiempo de ejecución usando una tabla virtual . es decir, el polimorfismo en tiempo de ejecución no se puede aplicar. Entonces el tiempo de ejecución no se preocupa por eso. Lo que significa que ahorra tiempo de procesamiento, lo que mejorará el rendimiento.

  1. métodos

A final methodde cualquier clase significa que cualquier clase secundaria que extienda esa clase no puede anular ese método o métodos finales. Por lo tanto, el comportamiento del tiempo de ejecución en este escenario también es bastante similar al comportamiento anterior que mencioné para las clases.

  1. campos, variables locales, parámetros del método

Si se especificó algún tipo de arriba como final, significa que el valor ya está finalizado, por lo que el valor no se puede cambiar .

Ex:

Para campos, parámetros locales

final FinalClass fc = someFC; //need to assign straight away. otherwise compile error.
final FinalClass fc; //compile error, need assignment (initialization inside a constructor Ok, constructor can be called only once)
final FinalClass fc = new FinalClass(); //ok
fc = someOtherFC; //compile error
fc.someMethod(); //no problem
someOtherFC.someMethod(); //no problem

Para parámetros del método

void someMethod(final String s){
    s = someOtherString; //compile error
}

Esto simplemente significa que el valor del valor de finalreferencia no se puede cambiar. es decir, solo se permite una inicialización. En este escenario, en tiempo de ejecución, dado que JRE sabe que los valores no se pueden cambiar, carga todos estos valores finalizados (de referencias finales) en la caché L1 . Debido a que no es necesario para cargar la espalda y otra vez de la memoria principal . De lo contrario, se carga en el caché L2 y se carga de vez en cuando desde la memoria principal. Por lo tanto, también es una mejora del rendimiento.

Entonces, en los 3 escenarios anteriores, cuando no hemos especificado la finalpalabra clave en lugares que podemos usar, no debemos preocuparnos, las optimizaciones del compilador lo harán por nosotros. También hay muchas otras cosas que las optimizaciones del compilador hacen por nosotros. :)

Supun Wijerathne
fuente
0

Sobre todo son correctos. Además, si no desea que otros creen subclases de su clase, declare su clase como final. Luego se convierte en el nivel de la hoja de la jerarquía del árbol de clases que nadie puede extender más. Es una buena práctica evitar una gran jerarquía de clases.

Shehan Simen
fuente