¿Por qué los operadores de asignación compuesta + =, - =, * =, / = de Java no requieren conversión?

3633

Hasta hoy, pensé que, por ejemplo:

i += j;

Era solo un atajo para:

i = i + j;

Pero si intentamos esto:

int i = 5;
long j = 8;

Entonces i = i + j;no se compilará pero i += j;se compilará bien.

¿Significa que de hecho i += j;es un atajo para algo como esto i = (type of i) (i + j)?

Honza Brabec
fuente
135
Me sorprende que Java permita esto, ya que es un lenguaje más estricto que sus predecesores. Los errores en el lanzamiento pueden conducir a fallas críticas, como fue el caso con el vuelo 501 de Ariane5, donde un lanzamiento flotante de 64 bits a un entero de 16 bits resultó en el bloqueo.
SQLDiver
103
En un sistema de control de vuelo escrito en Java, esta sería la menor de sus preocupaciones @SQLDiver
Ross Drew
10
En realidad, i+=(long)j;incluso compilará bien.
Tharindu Sathischandra
66
El impulso constante de un conjunto de desarrolladores para la precisión y otro para la facilidad de uso es realmente interesante. Casi necesitamos dos versiones del lenguaje, una que sea increíblemente precisa y otra que sea fácil de usar. Empujar Java desde ambas direcciones lo lleva a ser inadecuado para cualquiera de los grupos.
Bill K
55
si requiriera fundición, ¿dónde lo pondrías? i += (int) f;lanza f antes de la suma, por lo que no es equivalente. (int) i += f;arroja el resultado después de la asignación, tampoco equivalente. no habría lugar para colocar un elenco que significaría que desea emitir el valor después de agregar, pero antes de la asignación.
Norill Tempest

Respuestas:

2441

Como siempre con estas preguntas, el JLS tiene la respuesta. En este caso, §15.26.2 Operadores de asignación compuesta . Un extracto:

Una expresión de asignación compuesta de la forma E1 op= E2es equivalente a E1 = (T)((E1) op (E2)), donde Tes el tipo de E1, excepto que E1se evalúa solo una vez.

Un ejemplo citado de §15.26.2

[...] el siguiente código es correcto:

short x = 3;
x += 4.6;

y resulta en x teniendo el valor 7 porque es equivalente a:

short x = 3;
x = (short)(x + 4.6);

En otras palabras, su suposición es correcta.

Lukas Eder
fuente
42
Así que i+=jcompila como me revisé, pero daría como resultado una pérdida de precisión ¿verdad? Si ese es el caso, ¿por qué no permite que ocurra también en i = i + j? ¿Por qué molestarnos allí?
bad_keypoints
46
@ronnieaka: Supongo que los diseñadores de idiomas sintieron que en un caso ( i += j), es más seguro asumir que se desea la pérdida de precisión en comparación con el otro caso ( i = i + j)
Lukas Eder
12
¡No, está justo ahí, delante de mí! Lo siento, no lo noté antes. Al igual que en su respuesta, E1 op= E2 is equivalent to E1 = (T)((E1) op (E2))entonces eso es algo así como la conversión de tipos implícita hacia abajo (desde largo hasta int). Mientras que en i = i + j, tenemos que hacerlo explícitamente, es decir, proporcionar la (T)parte en E1 = ((E1) op (E2))¿No es así?
bad_keypoints
11
Una razón probable de por qué el compilador de Java agrega una conversión de tipos es porque si está intentando realizar operaciones aritméticas en tipos incompatibles, no hay forma de realizar una conversión de tipos del resultado utilizando el formulario contratado. Una tipología del resultado es generalmente más precisa que una tipología del argumento problemático. Ningún tipo de conversión haría inútil la contracción al utilizar tipos incompatibles, ya que siempre provocaría que el compilador arroje un error.
ThePyroEagle
66
No es redondeado. Está emitido (= truncado)
Lukas Eder
483

Un buen ejemplo de este casting es usar * = o / =

byte b = 10;
b *= 5.7;
System.out.println(b); // prints 57

o

byte b = 100;
b /= 2.5;
System.out.println(b); // prints 40

o

char ch = '0';
ch *= 1.1;
System.out.println(ch); // prints '4'

o

char ch = 'A';
ch *= 1.5;
System.out.println(ch); // prints 'a'
Peter Lawrey
fuente
11
@AkshatAgarwal ch es un char. 65 * 1.5 = 97.5 -> ¿Entendido?
Sajal Dutta
79
Sí, pero puedo ver a algunos principiantes que vienen aquí, leen esto y se van pensando que puedes convertir cualquier carácter de mayúsculas a minúsculas multiplicándolo por 1,5.
Dawood ibn Kareem
103
@DavidWallace Cualquier personaje siempre que sea A;)
Peter Lawrey
14
@PeterLawrey & @DavidWallace revelaré tu secreto - ch += 32 = D
Minhas Kamal
256

Muy buena pregunta La especificación del lenguaje Java confirma su sugerencia.

Por ejemplo, el siguiente código es correcto:

short x = 3;
x += 4.6;

y resulta en x teniendo el valor 7 porque es equivalente a:

short x = 3;
x = (short)(x + 4.6);
Thirler
fuente
15
O más divertido: "int x = 33333333; x + = 1.0f;".
supercat
55
@supercat, ¿qué engaño es esto? Una conversión de ampliación que se redondea incorrectamente, seguida de una adición que en realidad no cambia el resultado, volviendo a int para producir un resultado que es más inesperado para las mentes humanas normales.
neXus
1
@neXus: En mi humilde opinión, las reglas de conversión deberían haberse tratado double->floatcomo una ampliación, sobre la base de que los valores de tipo floatidentifican números reales menos específicamente que los de tipo double. Si se ve doublecomo una dirección postal completa y floatcomo un código postal de 5 dígitos, es posible satisfacer una solicitud de un código postal dada una dirección completa, pero no es posible especificar con precisión una solicitud de una dirección completa dada solo un código postal . Convertir una dirección de calle en un código postal es una operación con pérdida, pero ...
supercat
1
... alguien que necesita una dirección completa generalmente no estaría pidiendo solo un código postal. La conversión de float->doublees equivalente a convertir el código postal 90210 de EE. UU. Con "US Post Office, Beverly Hills CA 90210".
supercat
181

Si,

básicamente cuando escribimos

i += l; 

el compilador convierte esto a

i = (int)(i + l);

Acabo de comprobar el .classcódigo del archivo.

Realmente es algo bueno saber

Umesh Awasthi
fuente
3
¿Me puede decir qué archivo de clase es este?
nanofarad
66
@hexafraction: ¿qué quieres decir con archivo de clase? si preguntas sobre el archivo de clase que mencioné en mi publicación, entonces es la versión completa de tu clase de Java
Umesh Awasthi
3
Oh, mencionaste "el" código de archivo de clase, lo que me llevó a creer que estaba involucrado un archivo de clase específico. Entiendo lo que quieres decir ahora.
nanofarad
@Bogdan Eso no debería ser un problema con las fuentes utilizadas correctamente. Un programador que elige cuándo la fuente incorrecta para la programación debe pensar claramente sobre cómo proceder ...
#
77
@glglgl No estoy de acuerdo con que uno deba confiar en la fuente para distinguir en esos casos ... pero todos tienen la libertad de elegir lo que mejor les parezca.
Bogdan Alexandru
92

debe convertir desde longa int explicitlyen caso de i = i + l que se compile y dé la salida correcta. me gusta

i = i + (int)l;

o

i = (int)((long)i + l); // this is what happens in case of += , dont need (long) casting since upper casting is done implicitly.

pero en caso de +=que simplemente funcione bien porque el operador realiza implícitamente el tipo de conversión de tipo de variable derecha a tipo de variable izquierda, por lo que no es necesario emitir explícitamente.

dku.rajkumar
fuente
77
En este caso, el "elenco implícito" podría ser con pérdidas. En realidad, como estados @LukasEder en su respuesta, el elenco que intse lleva a cabo después de la +. El compilador (debe?) Lanza una advertencia si realmente ha desechado el longque int.
Romain
63

El problema aquí implica la conversión de tipos.

Cuando agregas int y long,

  1. El objeto int se convierte en largo y ambos se agregan y obtienes un objeto largo.
  2. pero el objeto largo no se puede convertir implícitamente en int. Entonces, tienes que hacer eso explícitamente.

Pero +=está codificado de tal manera que sí lo hace.i=(int)(i+m)

Dinesh Sachdev 108
fuente
54

En Java, las conversiones de tipo se realizan automáticamente cuando el tipo de expresión en el lado derecho de una operación de asignación se puede promover de forma segura al tipo de la variable en el lado izquierdo de la asignación. Por lo tanto, podemos asignar con seguridad:

 byte -> corto -> int -> largo -> flotante -> doble. 

Lo mismo no funcionará al revés. Por ejemplo, no podemos convertir automáticamente un largo a int porque el primero requiere más almacenamiento que el segundo y, en consecuencia, se puede perder información. Para forzar tal conversión debemos llevar a cabo una conversión explícita.
Tipo - Conversión

tinker_fairy
fuente
2
Hola, pero longes 2 veces más grande que float.
Nombre para mostrar el
11
A floatno puede retener todos los intvalores posibles , y doubleno puede retener todos los longvalores posibles .
Alex MDC
2
¿Qué quieres decir con "convertido con seguridad"? De la última parte de la respuesta puedo deducir que se refería a la conversión automática (conversión implícita) que, por supuesto, no es cierto en caso de flotación -> largo. flotador pi = 3.14f; largo b = pi; resultará en un error del compilador.
Lucas
1
Sería mejor diferenciar los tipos primitivos de coma flotante con los tipos primitivos enteros. No son lo mismo.
ThePyroEagle
Java tiene reglas de conversión simplistas que requieren el uso de modelos en muchos patrones donde el comportamiento sin modelos de otro modo coincidiría con las expectativas, pero no requiere modelos en muchos patrones que generalmente son erróneos. Por ejemplo, un compilador aceptará double d=33333333+1.0f;sin quejas, aunque el resultado 33333332.0 probablemente no sea lo que se pretendía (por cierto, la respuesta aritméticamente correcta de 33333334.0f sería representable como floato int).
supercat
46

A veces, tal pregunta se puede hacer en una entrevista.

Por ejemplo, cuando escribes:

int a = 2;
long b = 3;
a = a + b;

No hay encasillamiento automático. En C ++ no habrá ningún error al compilar el código anterior, pero en Java obtendrá algo así Incompatible type exception.

Para evitarlo, debe escribir su código de esta manera:

int a = 2;
long b = 3;
a += b;// No compilation error or any exception due to the auto typecasting
Stopfan
fuente
66
Gracias por la información sobre la comparación del opuso en C ++ con su uso en Java. Siempre me gusta ver estas curiosidades y creo que contribuyen con algo a la conversación que a menudo se puede omitir.
Thomas
2
Sin embargo, la pregunta en sí es interesante, preguntar esto en una entrevista es estúpido. No prueba que la persona pueda producir un código de buena calidad, solo prueba que tuvo suficiente paciencia para prepararse para un examen de certificado de Oracle. Y "evitar" los tipos incompatibles mediante el uso de una conversión automática peligrosa y, por lo tanto, ocultar el posible error de desbordamiento, probablemente incluso prueba que la persona no puede producir un código probable de buena calidad. ¡Malditos sean los autores de Java por todas estas conversiones automáticas y boxeo automático y todo!
Honza Zidek
25

La principal diferencia es que con a = a + bno hay ningún tipo de conversión de texto, por lo que el compilador se enoja con usted por no hacerlo. Pero con a += blo que realmente está haciendo es convertir ba un tipo compatible a. Entonces si lo haces

int a=5;
long b=10;
a+=b;
System.out.println(a);

Lo que realmente estás haciendo es:

int a=5;
long b=10;
a=a+(int)b;
System.out.println(a);
takra
fuente
55
Los operadores de asignación compuesta realizan una conversión de reducción del resultado de la operación binaria, no del operando de la derecha. Entonces, en su ejemplo, 'a + = b' no es equivalente a 'a = a + (int) b' sino, como se explica en otras respuestas aquí, a 'a = (int) (a + b)'.
Lew Bloch el
13

Punto sutil aquí ...

Hay un tipo de letra implícito para i+jcuando jes un doble y ies un int. Java SIEMPRE convierte un número entero en un doble cuando hay una operación entre ellos.

Para aclarar i+=jdónde ies un número entero y jun doble se puede describir como

i = <int>(<double>i + j)

Ver: esta descripción del casting implícito

Es posible que desee encasillado ja (int)en este caso, para mayor claridad.

Gabe Nones
fuente
1
Creo que podría ser un caso más interesante int someInt = 16777217; float someFloat = 0.0f; someInt += someFloat;. Agregar cero a someIntno debería afectar su valor, pero promocionar someInta floatpuede cambiar su valor.
supercat
5

La especificación del lenguaje Java define E1 op= E2ser equivalente a E1 = (T) ((E1) op (E2))donde Tes un tipo de E1y E1se evalúa una vez .

Esa es una respuesta técnica, pero puede que se pregunte por qué es así. Bueno, consideremos el siguiente programa.

public class PlusEquals {
    public static void main(String[] args) {
        byte a = 1;
        byte b = 2;
        a = a + b;
        System.out.println(a);
    }
}

¿Qué imprime este programa?

¿Adivinaste 3? Lástima, este programa no se compilará. ¿Por qué? Bueno, sucede que la adición de bytes en Java se define para devolver unint . Esto, creo que fue porque la máquina virtual Java no define operaciones de byte para guardar en códigos de byte (después de todo, hay un número limitado de ellas), el uso de operaciones enteras es un detalle de implementación expuesto en un lenguaje.

Pero si a = a + bno funciona, eso significaría a += bque nunca funcionaría para bytes si se E1 += E2definiera como tal E1 = E1 + E2. Como muestra el ejemplo anterior, ese sería el caso. Como un truco para hacer que el +=operador funcione para bytes y cortos, hay un elenco implícito involucrado. No es un gran truco, pero durante el trabajo de Java 1.0, el enfoque se centró en liberar el lenguaje para empezar. Ahora, debido a la compatibilidad con versiones anteriores, este truco introducido en Java 1.0 no se pudo eliminar.

Konrad Borowski
fuente