Por que os operadores de atribuição composta + =, - =, * =, / = do Java não requerem conversão?

3633

Até hoje, pensei que, por exemplo:

i += j;

Foi apenas um atalho para:

i = i + j;

Mas se tentarmos isso:

int i = 5;
long j = 8;

Então i = i + j;não irá compilar, mas i += j;irá compilar bem.

Isso significa que, de fato, i += j;é um atalho para algo assim i = (type of i) (i + j)?

Honza Brabec
fonte
135
Estou surpreso que o Java permita isso, sendo uma linguagem mais rígida que seus antecessores. Erros na conversão podem levar a falhas críticas, como foi o caso do vôo 501 do Ariane5, em que uma conversão de 64 bits para um número inteiro de 16 bits resultou na falha.
SQLDiver 5/01/15
103
Em um sistema de controle de vôo escrito em Java, esta seria a menor das suas preocupações @SQLDiver
Ross de Drew
10
Na verdade, i+=(long)j;mesmo compilará bem.
Tharindu Sathischandra
6
O esforço constante de um conjunto de desenvolvedores pela precisão e outro pela facilidade de uso é realmente interessante. Quase precisamos de duas versões do idioma, uma que seja incrivelmente precisa e que seja fácil de usar. Empurrar o Java de ambas as direções o leva a ser inadequado para ambos os grupos.
Bill K
5
se fosse necessário transmitir, onde você colocaria? i += (int) f;lança f antes da adição, portanto não é equivalente. (int) i += f;lança o resultado após a atribuição, também não é equivalente. não haveria lugar para colocar uma conversão que significaria que você deseja converter o valor após adicionar, mas antes da atribuição.
Norill Tempest 24/10/19

Respostas:

2441

Como sempre com essas perguntas, o JLS mantém a resposta. Nesse caso, §15.26.2 Operadores de atribuição composta . Um extrato:

Uma expressão de atribuição composta do formulário E1 op= E2é equivalente a E1 = (T)((E1) op (E2)), onde Té o tipo de E1, exceto que E1é avaliada apenas uma vez.

Um exemplo citado em §15.26.2

[...] o seguinte código está correto:

short x = 3;
x += 4.6;

e resulta em x tendo o valor 7 porque é equivalente a:

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

Em outras palavras, sua suposição está correta.

Lukas Eder
fonte
42
Então, i+=jcompila como eu me verifiquei, mas resultaria em perda de precisão, certo? Se for esse o caso, por que não permite que isso aconteça em i = i + j também? Por que nos incomodar lá?
bad_keypoints
46
@ronnieaka: Eu estou supondo que os projetistas da linguagem sentia que em um caso ( i += j), é mais seguro assumir que a perda de precisão é desejado em oposição ao outro caso ( i = i + j)
Lukas Eder
12
Não, está bem ali, na minha frente! Desculpe, eu não percebi isso antes. Como na sua resposta, E1 op= E2 is equivalent to E1 = (T)((E1) op (E2))isso é como uma conversão de texto implícita para baixo (do longo para o int). Enquanto em i = i + j, temos que fazê-lo explicitamente, ou seja, fornecer a (T)parte em E1 = ((E1) op (E2))Não é?
bad_keypoints
11
Uma razão provável pela qual o compilador Java adiciona uma conversão de texto é porque, se você está tentando executar aritmética em tipos incompatíveis, não há como executar uma conversão de texto do resultado usando o formulário contratado. Uma conversão tipográfica do resultado geralmente é mais precisa do que uma conversão tipográfica do argumento problemático. Nenhum typecast tornaria a contração inútil ao usar tipos incompatíveis, pois sempre causaria o erro do compilador.
ThePyroEagle
6
Não é arredondado. É de elenco (= truncada)
Lukas Eder
483

Um bom exemplo dessa transmissão é usar * = ou / =

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

ou

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

ou

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

ou

char ch = 'A';
ch *= 1.5;
System.out.println(ch); // prints 'a'
Peter Lawrey
fonte
11
@AkshatAgarwal ch é um personagem. 65 * 1,5 = 97,5 -> Entendeu?
Sajal Dutta 5/05
79
Sim, mas posso ver algum iniciante chegando aqui, lendo isso e saindo pensando que você pode converter qualquer caractere de maiúsculo para minúsculo, multiplicando-o por 1,5.
Dawood ibn Kareem
103
@DavidWallace Qualquer personagem enquanto ele está A;)
Peter Lawrey
14
@PeterLawrey & @DavidWallace vou revelar o seu segredo - ch += 32 = D
Minhas Kamal
256

Muito boa pergunta. A especificação da linguagem Java confirma sua sugestão.

Por exemplo, o seguinte código está correto:

short x = 3;
x += 4.6;

e resulta em x tendo o valor 7 porque é equivalente a:

short x = 3;
x = (short)(x + 4.6);
Thirler
fonte
15
Ou mais divertido: "int x = 33333333; x + = 1.0f;".
supercat
5
@ supercat, que truque é esse? Uma conversão ampliada incorretamente arredondada, seguida de uma adição que não altera o resultado, convertendo para int novamente para produzir um resultado inesperado para as mentes humanas normais.
NeXus
1
@neXus: IMHO, as regras de conversão deveriam ter sido tratadas double->floatcomo ampliadoras, com base em que os valores do tipo floatidentificam números reais menos especificamente do que os do tipo double. Se alguém visualizar doublecomo um endereço postal completo e floatum código postal de cinco dígitos, é possível atender a uma solicitação de código postal com um endereço completo, mas não é possível especificar com precisão uma solicitação de endereço completo, apenas com um código postal . Convertendo um endereço com um código postal é uma operação com perdas, mas ...
supercat
1
... alguém que precisa de um endereço completo geralmente não solicita apenas um código postal. A conversão de float->doubleé equivalente a converter o código postal dos EUA 90210 com "Agência dos EUA, Beverly Hills CA 90210".
Supercat 21/09
181

Sim,

basicamente quando escrevemos

i += l; 

o compilador converte isso em

i = (int)(i + l);

Acabei de verificar o .classcódigo do arquivo.

Realmente uma coisa boa de saber

Umesh Awasthi
fonte
3
Você pode me dizer qual é o arquivo de classe?
Nanofarad
6
@hexafraction: o que você quer dizer com arquivo de classe? se você perguntar sobre o arquivo de classe eu mencionei no meu post que é a versão Cumprida de sua classe java
Umesh Awasthi
3
Ah, você mencionou "o" código do arquivo de classe, o que me levou a acreditar que um arquivo de classe específico estava envolvido. Eu entendo o que você quer dizer agora.
Nanofarad
@ Bogdan Isso não deve ser um problema com as fontes usadas corretamente. Um programador que escolhe fonte whe errado para a programação deve pensar claramente sobre como proceder ...
glglgl
7
@glglgl Não concordo que se deva confiar na fonte para distinguir nesses casos ... mas todo mundo tem a liberdade de escolher o que acha melhor.
Bogdan Alexandru
92

você precisa converter de longpara int explicitlyem caso de i = i + l compilação e saída correta. gostar

i = i + (int)l;

ou

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

mas, no caso +=disso, funciona bem, porque o operador faz implicitamente a conversão de tipo do tipo da variável direita para o tipo da variável esquerda, portanto, não é necessário converter explicitamente.

dku.rajkumar
fonte
7
Nesse caso, o "elenco implícito" pode ser com perdas. Na realidade, como @LukasEder afirma em sua resposta, o elenco para inté realizado após o +. O compilador lançaria (deveria?) Um aviso se realmente lançou o longpara int.
Romain
63

O problema aqui envolve a conversão de tipos.

Quando você adiciona int e long,

  1. O objeto int é convertido para comprido e ambos são adicionados e você obtém um objeto comprido.
  2. mas o objeto longo não pode ser implicitamente convertido para int. Então, você precisa fazer isso explicitamente.

Mas +=é codificado de tal maneira que faz a conversão de tipo.i=(int)(i+m)

Dinesh Sachdev 108
fonte
54

No Java, as conversões de tipo são executadas automaticamente quando o tipo da expressão no lado direito de uma operação de atribuição pode ser promovido com segurança para o tipo de variável no lado esquerdo da atribuição. Assim, podemos atribuir com segurança:

 byte -> curto -> int -> longo -> flutuar -> duplo. 

O mesmo não funcionará ao contrário. Por exemplo, não podemos converter automaticamente um longo para um int porque o primeiro requer mais armazenamento que o segundo e, consequentemente, as informações podem ser perdidas. Para forçar essa conversão, devemos realizar uma conversão explícita.
Tipo - Conversão

tinker_fairy
fonte
2
Ei, mas longé 2 vezes maior que float.
Exibir nome
11
A floatnão pode conter todos os intvalores possíveis e a doublenão pode armazenar todos os longvalores possíveis .
Alex MDC
2
O que você quer dizer com "convertido com segurança"? Da última parte da resposta, posso deduzir que você quis dizer conversão automática (conversão implícita), o que obviamente não é verdade no caso de flutuação -> longa. float pi = 3,14f; b longo = pi; resultará em erro do compilador.
Lucas
1
Seria melhor diferenciar tipos primitivos de ponto flutuante com tipos primários inteiros. Eles não são a mesma coisa.
ThePyroEagle
Java possui regras de conversão simplistas que exigem o uso de transmissões em muitos padrões, onde o comportamento sem transmissões corresponderia às expectativas, mas não requer transmissões em muitos padrões que geralmente são errôneos. Por exemplo, um compilador aceitará double d=33333333+1.0f;sem reclamação, mesmo que o resultado 33333332.0 provavelmente não seja o que se pretendia (aliás, a resposta aritmeticamente correta de 33333334.0f seria representável como floatou int).
supercat
46

Às vezes, essa pergunta pode ser feita em uma entrevista.

Por exemplo, quando você escreve:

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

não há conversão automática de texto. Em C ++, não haverá nenhum erro ao compilar o código acima, mas em Java você terá algo parecido Incompatible type exception.

Portanto, para evitá-lo, você deve escrever seu código assim:

int a = 2;
long b = 3;
a += b;// No compilation error or any exception due to the auto typecasting
Stopfan
fonte
6
Obrigado pela compreensão sobre a comparação do opuso em C ++ com o uso em Java. Eu sempre gosto de ver essas curiosidades e acho que elas contribuem com algo para a conversa que pode ser deixada de fora.
Thomas
2
No entanto, a pergunta em si é interessante, perguntar isso em uma entrevista é estúpido. Isso não prova que a pessoa pode produzir um código de boa qualidade - apenas prova que ele teve paciência suficiente para se preparar para um exame de certificado Oracle. E "evitar" os tipos incompatíveis usando uma conversão automática perigosa e, assim, ocultar o possível erro de transbordamento, provavelmente prova que a pessoa não é capaz de produzir um código de boa qualidade provável. Condene os autores de Java por todas essas conversões automáticas e boxe automático e tudo!
Honza Zidek 26/03
25

A principal diferença é que a = a + b, com , não há transmissão de texto em andamento e, portanto, o compilador fica com raiva de você por não ter feito a transmissão. Mas com a += bo que realmente está fazendo é converter bpara um tipo compatível a. Então se você faz

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

O que você realmente está fazendo é:

int a=5;
long b=10;
a=a+(int)b;
System.out.println(a);
takra
fonte
5
Operadores de atribuição composta realizam uma conversão restrita do resultado da operação binária, não do operando do lado direito. Portanto, no seu exemplo, 'a + = b' não é equivalente a 'a = a + (int) b', mas, como explicado por outras respostas aqui, a 'a = (int) (a + b)'.
Lew Bloch
13

Ponto sutil aqui ...

Há uma conversão implícita de i+jquando jé um duplo e ié um int. Java SEMPRE converte um número inteiro em um duplo quando houver uma operação entre eles.

Para esclarecer i+=jonde ié um número inteiro e jé um duplo, pode ser descrito como

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

Veja: esta descrição da conversão implícita

Você pode querer estereotipado jpara (int), neste caso, para maior clareza.

Gabe Nones
fonte
1
Eu acho que pode ser um caso mais interessante int someInt = 16777217; float someFloat = 0.0f; someInt += someFloat;. Adicionar zero a someIntnão deve afetar seu valor, mas promover someInta floatpode mudar seu valor.
Supercat
5

Especificação de linguagem Java define E1 op= E2como equivalente a E1 = (T) ((E1) op (E2))onde Té um tipo de E1e E1é avaliada uma vez .

Essa é uma resposta técnica, mas você pode estar se perguntando por que esse é o caso. Bem, vamos considerar o seguinte programa.

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

O que esse programa imprime?

Você adivinhou 3? Pena que este programa não será compilado. Por quê? Bem, acontece que a adição de bytes em Java é definida para retornar umint . Acredito que isso ocorreu porque a Java Virtual Machine não define operações de byte para economizar em bytecodes (há um número limitado delas, afinal), usar operações inteiras é um detalhe de implementação exposto em uma linguagem.

Mas se a = a + bnão funcionar, isso significaria a += bque nunca funcionaria para bytes se E1 += E2fosse definido E1 = E1 + E2. Como mostra o exemplo anterior, esse seria realmente o caso. Como um truque para fazer o +=operador trabalhar para bytes e curtos, há uma conversão implícita envolvida. Não é tão bom assim, mas, durante o trabalho do Java 1.0, o foco estava em liberar a linguagem para começar. Agora, devido à compatibilidade com versões anteriores, esse hack introduzido no Java 1.0 não pôde ser removido.

Konrad Borowski
fonte