Hasta ahora pensé que efectivamente final y final son más o menos equivalentes y que el JLS los trataría de manera similar, si no idéntica, en el comportamiento real. Entonces encontré este escenario artificial:
final int a = 97;
System.out.println(true ? a : 'c'); // outputs a
// versus
int a = 97;
System.out.println(true ? a : 'c'); // outputs 97
Aparentemente, el JLS hace una diferencia importante entre los dos aquí y no estoy seguro de por qué.
Leí otros hilos como
- Diferencia entre final y efectivamente final
- Efectivamente variable final vs variable final
- ¿Qué significa que una variable sea "efectivamente final"?
pero no entran en tantos detalles. Después de todo, en un nivel más amplio, parecen ser bastante equivalentes. Pero profundizando, aparentemente difieren.
¿Qué está causando este comportamiento? ¿Alguien puede proporcionar algunas definiciones de JLS que expliquen esto?
Editar: encontré otro escenario relacionado:
final String a = "a";
System.out.println(a + "b" == "ab"); // outputs true
// versus
String a = "a";
System.out.println(a + "b" == "ab"); // outputs false
Entonces, la pasantía de cadenas también se comporta de manera diferente aquí (no quiero usar este fragmento en código real, solo tengo curiosidad por el comportamiento diferente).
fuente
byte
,short
ochar
, y el otro operando es una expresión constante de tipoint
cuyo valor es representable en el tipo T , entonces el tipo de la expresión condicional es T ". --- Incluso encontré un documento de Java 1.0 en Berkeley. Mismo texto . --- Sí, siempre ha sido así.Respuestas:
En primer lugar, estamos hablando solo de variables locales . Efectivamente final no se aplica a los campos. Esto es importante, ya que la semántica de los
final
campos es muy distinta y está sujeta a grandes optimizaciones del compilador y promesas del modelo de memoria, consulte $ 17.5.1 sobre la semántica de los campos finales.A nivel superficial
final
yeffectively final
para las variables locales son de hecho idénticas. Sin embargo, el JLS hace una clara distinción entre los dos, que en realidad tiene una amplia gama de efectos en situaciones especiales como esta.Premisa
De JLS§4.12.4 sobre
final
variables:Dado que
int
es primitiva, la variablea
es una variable constante .Además, del mismo capítulo sobre
effectively final
:Así que desde la forma en que esto está redactado, es claro que en el otro ejemplo,
a
se no se considera una variable constante, ya que es no es definitivo , pero solamente con eficacia final.Comportamiento
Ahora que tenemos la distinción, busquemos qué está pasando y por qué la salida es diferente.
Estás usando el operador condicional
? :
aquí, así que tenemos que verificar su definición. Desde JLS§15.25 :En este caso, estamos hablando de expresiones condicionales numéricas , de JLS§15.25.2 :
Y esa es la parte en la que los dos casos se clasifican de manera diferente.
efectivamente final
La versión que
effectively final
corresponde a esta regla:Cuál es el mismo comportamiento que si lo hiciera
5 + 'd'
, es decirint + char
, que resulta enint
. Ver JLS§5.6Así que todo se promueve a
int
quea
es unint
hecho. Eso explica la salida de97
.final
La versión con la
final
variable coincide con esta regla:La variable final
a
es de tipoint
y expresión constante (porque lo esfinal
). Es representable comochar
, por lo tanto, el resultado es de tipochar
. Con eso concluye la salidaa
.Ejemplo de cadena
El ejemplo con la igualdad de cadenas se basa en la misma diferencia central, las
final
variables se tratan como expresión / variable constante yeffectively final
no lo es.En Java, la internación de cadenas se basa en expresiones constantes, por lo tanto
"a" + "b" + "c" == "abc"
es
true
también (no use esta construcción en código real).Consulte JLS§3.10.5 :
Fácil de pasar por alto, ya que se trata principalmente de literales, pero en realidad también se aplica a expresiones constantes.
fuente
... ? a : 'c'
comportarse igual sia
es una variable o una constante . Aparentemente, no hay nada de malo en la expresión. --- Por el contrario,a + "b" == "ab"
es una mala expresión , porque las cadenas deben compararse usandoequals()
( ¿Cómo comparo cadenas en Java? ). El hecho de que funcione "accidentalmente" cuandoa
es una constante , es solo una peculiaridad del interior de los literales de cadena."a" + "b" + "c" == "abc"
debe estartrue
en cualquier implementación Java válida.a + "b" == "ab"
sigue siendo una expresión incorrecta . Incluso si sabe quea
es una constante , es demasiado propenso a errores como para no llamarequals()
. O tal vez frágil es una palabra mejor, es decir, es muy probable que se desmorone cuando el código se mantenga en el futuro.(final) String str = "a"; Stream.of(null, null). <Runnable>map( x -> () -> System.out.println(str)) .reduce((a,b) -> () -> System.out.println(a == b)) .ifPresent(Runnable::run);
cambia su resultado cuandostr
es (no)final
.Otro aspecto es que si la variable se declara final en el cuerpo del método, tiene un comportamiento diferente al de una variable final pasada como parámetro.
public void testFinalParameters(final String a, final String b) { System.out.println(a + b == "ab"); } ... testFinalParameters("a", "b"); // Prints false
mientras
public void testFinalVariable() { final String a = "a"; final String b = "b"; System.out.println(a + b == "ab"); // Prints true } ... testFinalVariable();
sucede porque el compilador sabe que usar
final String a = "a"
laa
variable siempre tendrá el"a"
valor para quea
y"a"
pueda intercambiarse sin problemas. De manera diferente, sia
no está definidofinal
o está definidofinal
pero su valor se asigna en tiempo de ejecución (como en el ejemplo anterior donde final es ela
parámetro), el compilador no sabe nada antes de su uso. Entonces, la concatenación ocurre en tiempo de ejecución y se genera una nueva cadena, sin usar el grupo interno.Básicamente, el comportamiento es: si el compilador sabe que una variable es una constante, puede usarla de la misma manera que usa la constante.
Si la variable no está definida como final (o es final pero su valor está definido en tiempo de ejecución) no hay razón para que el compilador la maneje como una constante también si su valor es igual a una constante y su valor nunca cambia.
fuente
finel
palabra clave aplicada a un parámetro, tiene una semántica diferente a lafinal
aplicada a una variable local, etc ...final String a; a = "a";
y obtener el mismo comportamiento