Pourquoi les opérateurs d'affectation composés + =, - =, * =, / = de Java ne nécessitent-ils pas de transtypage?

3633

Jusqu'à aujourd'hui, je pensais que par exemple:

i += j;

Était juste un raccourci pour:

i = i + j;

Mais si nous essayons ceci:

int i = 5;
long j = 8;

Alors i = i + j;ne compilera pas mais i += j;compilera bien.

Cela signifie-t-il qu'il s'agit en fait d' i += j;un raccourci pour quelque chose comme ça i = (type of i) (i + j)?

Honza Brabec
la source
135
Je suis surpris que Java le permette, étant un langage plus strict que ses prédécesseurs. Des erreurs de transtypage peuvent entraîner une défaillance critique, comme ce fut le cas avec le vol 501 d'Ariane5, où un flottement de 64 bits en un entier de 16 bits a entraîné le crash.
SQLDiver
103
Dans un système de contrôle de vol écrit en Java, ce serait le moindre de vos soucis @SQLDiver
Ross Drew
10
En fait, i+=(long)j;même compilera bien.
Tharindu Sathischandra
6
La pression constante d'un ensemble de développeurs pour la précision et un autre pour la facilité d'utilisation est vraiment intéressante. Nous avons presque besoin de deux versions du langage, une qui est incroyablement précise et une qui est facile à utiliser. Pousser Java des deux directions le déplace vers l'inadéquation pour l'un ou l'autre groupe.
Bill K
5
si cela nécessitait un casting, où le mettriez-vous? i += (int) f;jette f avant l'addition, donc ce n'est pas équivalent. (int) i += f;jette le résultat après l'affectation, pas équivalent non plus. il n'y aurait pas de place pour placer une conversion qui signifierait que vous souhaitez convertir la valeur après l'ajout, mais avant l'affectation.
Norill Tempest

Réponses:

2441

Comme toujours avec ces questions, le JLS détient la réponse. Dans ce cas, §15.26.2 Opérateurs d'affectation composée . Un extrait:

Une expression d'affectation composée du formulaire E1 op= E2est équivalente à E1 = (T)((E1) op (E2)), où Test le type de E1, sauf qu'elle E1n'est évaluée qu'une seule fois.

Un exemple cité du §15.26.2

[...] le code suivant est correct:

short x = 3;
x += 4.6;

et se traduit par x ayant la valeur 7 car il est équivalent à:

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

En d'autres termes, votre hypothèse est correcte.

Lukas Eder
la source
42
i+=jCompile donc comme je l'ai vérifié moi-même, mais cela entraînerait une perte de précision non? Si tel est le cas, pourquoi ne permet-il pas que cela se produise également dans i = i + j? Pourquoi nous déranger là-bas?
bad_keypoints
46
@ronnieaka: Je suppose que les concepteurs de langage ont estimé que dans un cas ( i += j), il est plus sûr de supposer que la perte de précision est souhaitée par opposition à l'autre ( i = i + j)
Lukas Eder
12
Non, c'est juste là, devant moi! Désolé de ne pas l'avoir remarqué plus tôt. Comme dans votre réponse, E1 op= E2 is equivalent to E1 = (T)((E1) op (E2))c'est un peu comme la conversion de type implicite vers le bas (de long en entier). Alors que dans i = i + j, nous devons le faire explicitement, c'est-à-dire fournir la (T)partie dans E1 = ((E1) op (E2))N'est-ce pas?
bad_keypoints
11
Une raison probable pour laquelle le compilateur Java ajoute un transtypage est que si vous essayez d'effectuer une arithmétique sur des types incompatibles, il n'y a aucun moyen d'effectuer un transtypage du résultat en utilisant le formulaire contracté. Un transtypage du résultat est généralement plus précis qu'un transtypage de l'argument problématique. Aucun transtypage ne rendrait la contraction inutile lors de l'utilisation de types incompatibles, car cela entraînerait toujours une erreur du compilateur.
ThePyroEagle
6
Ce n'est pas arrondi. C'est coulé (= tronqué)
Lukas Eder
483

Un bon exemple de ce casting utilise * = 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
la source
11
@AkshatAgarwal ch est un char. 65 * 1,5 = 97,5 -> compris?
Sajal Dutta
79
Oui, mais je peux juste voir un débutant venir ici, lire ceci et partir en pensant que vous pouvez convertir n'importe quel caractère de majuscule en minuscule en le multipliant par 1,5.
Dawood ibn Kareem
103
@DavidWallace N'importe quel personnage tant qu'il est A;)
Peter Lawrey
14
@PeterLawrey & @DavidWallace Je vais révéler votre secret - ch += 32 = D
Minhas Kamal
256

Très bonne question. La spécification du langage Java confirme votre suggestion.

Par exemple, le code suivant est correct:

short x = 3;
x += 4.6;

et se traduit par x ayant la valeur 7 car il est équivalent à:

short x = 3;
x = (short)(x + 4.6);
Thirler
la source
15
Ou plus amusant: "int x = 33333333; x + = 1.0f;".
supercat
5
@supercat, de quelle ruse s'agit-il? Une conversion élargie qui est incorrectement arrondie, suivie d'un ajout qui ne change pas réellement le résultat, converti à nouveau en int pour produire un résultat qui est le plus inattendu pour les esprits humains normaux.
neXus
1
@neXus: À mon humble avis, les règles de conversion auraient dû être traitées double->floatcomme un élargissement, étant donné que les valeurs de type floatidentifient les nombres réels de manière moins spécifique que celles de type double. Si l'on considère doublecomme une adresse postale complète et floatcomme un code postal à 5 ​​chiffres, il est possible de satisfaire une demande de code postal à partir d'une adresse complète, mais il n'est pas possible de spécifier avec précision une demande d'adresse complète à l'aide d'un seul code postal . Convertir une adresse
municipale en
1
... quelqu'un qui a besoin d'une adresse complète ne demanderait généralement pas seulement un code postal. La conversion de float->doubleéquivaut à convertir le code postal américain 90210 avec "US Post Office, Beverly Hills CA 90210".
supercat
181

Oui,

essentiellement quand on écrit

i += l; 

le compilateur convertit cela en

i = (int)(i + l);

Je viens de vérifier le .classcode du fichier.

Vraiment une bonne chose à savoir

Umesh Awasthi
la source
3
Pouvez-vous me dire de quel fichier il s'agit?
nanofarad
6
@hexafraction: que voulez-vous dire par fichier de classe? si vous posez des questions sur le fichier de classe que j'ai mentionné dans mon article, c'est la version conforme de votre classe java
Umesh Awasthi
3
Oh, vous avez mentionné "le" code du fichier de classe, ce qui m'a amené à croire qu'un fichier de classe spécifique était impliqué. Je comprends ce que tu veux dire maintenant.
nanofarad
@Bogdan Cela ne devrait pas être un problème avec les polices correctement utilisées. Un programmeur qui choisit la mauvaise police pour la programmation devrait clairement réfléchir à la façon de procéder ...
glglgl
7
@glglgl Je ne suis pas d'accord sur le fait que l'on devrait s'appuyer sur la police pour distinguer dans ces cas ... mais tout le monde a la liberté de choisir ce qui pense le mieux.
Bogdan Alexandru
92

vous devez convertir de longà int explicitlydans le cas où i = i + l il compilera et donnera une sortie correcte. comme

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.

mais dans ce cas, +=cela fonctionne très bien, car l'opérateur effectue implicitement le transtypage de type de variable droite en type de variable gauche, il n'est donc pas nécessaire de transtyper explicitement.

dku.rajkumar
la source
7
Dans ce cas, la «distribution implicite» pourrait être avec perte. En réalité, comme l'indique @LukasEder dans sa réponse, le casting vers intest effectué après le +. Le compilateur (devrait?) Jeter un avertissement si elle avait vraiment jeté l' longà int.
Romain
63

Le problème ici concerne la conversion de type.

Lorsque vous ajoutez int et long,

  1. L'objet int est converti en long et les deux sont ajoutés et vous obtenez un objet long.
  2. mais un objet long ne peut pas être implicitement converti en int. Donc, vous devez le faire explicitement.

Mais il +=est codé de telle manière qu'il tape du casting.i=(int)(i+m)

Dinesh Sachdev 108
la source
54

En Java, les conversions de type sont effectuées automatiquement lorsque le type de l'expression sur le côté droit d'une opération d'affectation peut être promu en toute sécurité au type de la variable sur le côté gauche de l'affectation. Ainsi, nous pouvons sans risque attribuer:

 octet -> court -> int -> long -> float -> double. 

La même chose ne fonctionnera pas dans l'autre sens. Par exemple, nous ne pouvons pas convertir automatiquement un long en un entier car le premier nécessite plus de stockage que le second et par conséquent des informations peuvent être perdues. Pour forcer une telle conversion, nous devons effectuer une conversion explicite.
Type - Conversion

tinker_fairy
la source
2
Hé, mais longest 2 fois plus grand que float.
Afficher le nom
11
Un floatne peut pas contenir toutes les intvaleurs possibles et un doublene peut pas contenir toutes les longvaleurs possibles .
Alex MDC
2
Qu'entendez-vous par «converti en toute sécurité»? De la dernière partie de la réponse, je peux déduire que vous vouliez parler de conversion automatique (transtypage implicite), ce qui n'est bien sûr pas vrai en cas de flottement -> long. flotteur pi = 3,14f; long b = pi; entraînera une erreur du compilateur.
Luke
1
Vous feriez mieux de différencier les types primitifs à virgule flottante des types primitifs entiers. Ce n'est pas la même chose.
ThePyroEagle
Java a des règles de conversion simplistes qui nécessitent l'utilisation de transtypages dans de nombreux modèles où le comportement sans transtypages correspondrait autrement aux attentes, mais n'exige pas de transtypages dans de nombreux schémas qui sont généralement erronés. Par exemple, un compilateur acceptera double d=33333333+1.0f;sans se plaindre, même si le résultat 33333332.0 ne serait probablement pas ce qui était prévu (incidemment, la réponse arithmétiquement correcte de 33333334.0f serait représentable comme l'un floatou l' autre int).
supercat
46

Parfois, une telle question peut être posée lors d'un entretien.

Par exemple, lorsque vous écrivez:

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

il n'y a pas de transtypage automatique. En C ++, il n'y aura pas d'erreur lors de la compilation du code ci-dessus, mais en Java, vous obtiendrez quelque chose comme Incompatible type exception.

Donc, pour l'éviter, vous devez écrire votre code comme ceci:

int a = 2;
long b = 3;
a += b;// No compilation error or any exception due to the auto typecasting
Stopfan
la source
6
Merci pour la perspicacité concernant la comparaison de l' oputilisation en C ++ avec son utilisation en Java. J'aime toujours voir ces petites anecdotes et je pense qu'elles contribuent à la conversation qui peuvent souvent être laissées de côté.
Thomas
2
Cependant, la question elle - même est intéressante, la poser dans une interview est stupide. Cela ne prouve pas que la personne peut produire un code de bonne qualité - cela prouve simplement qu'elle avait assez de patience pour se préparer à un examen de certificat Oracle. Et "éviter" les types incompatibles en utilisant une auto-conversion dangereuse et en cachant ainsi l'éventuelle erreur de débordement, prouve probablement même que la personne n'est pas en mesure de produire probablement un code de bonne qualité. Merde aux auteurs Java pour toutes ces conversions automatiques et boxe automatique et tout!
Honza Zidek
25

La principale différence est qu'avec a = a + b, il n'y a pas de transtypage en cours, et donc le compilateur se fâche contre vous pour ne pas transtyper. Mais avec a += b, ce qu'il fait vraiment, c'est la conversion ben un type compatible avec a. Donc si tu le fais

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

Ce que vous faites vraiment, c'est:

int a=5;
long b=10;
a=a+(int)b;
System.out.println(a);
takra
la source
5
Les opérateurs d'affectation composés effectuent une conversion plus étroite du résultat de l'opération binaire, et non de l'opérande de droite. Ainsi, dans votre exemple, «a + = b» n'est pas équivalent à «a = a + (int) b» mais, comme expliqué par d'autres réponses ici, à «a = (int) (a + b)».
Lew Bloch
13

Point subtil ici ...

Il y a un transtypage implicite pour i+jquand jest un double et iest un int. Java convertit TOUJOURS un entier en double lorsqu'il y a une opération entre eux.

Pour clarifier i+=jiest un entier et jest un double peut être décrit comme

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

Voir: cette description du casting implicite

Vous voudrez peut - être cataloguée jà (int)dans ce cas pour plus de clarté.

Gabe Nones
la source
1
Je pense qu'un cas plus intéressant pourrait être int someInt = 16777217; float someFloat = 0.0f; someInt += someFloat;. Ajout de zéro à someIntne devrait affecter sa valeur, mais la promotion someIntde floatpeut changer sa valeur.
supercat
5

La spécification du langage Java définit E1 op= E2comme équivalent à E1 = (T) ((E1) op (E2))Test un type de E1et E1est évaluée une fois .

C'est une réponse technique, mais vous vous demandez peut-être pourquoi c'est un cas. Eh bien, considérons le programme suivant.

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

Qu'est-ce que ce programme imprime?

Avez-vous deviné 3? Dommage, ce programme ne se compilera pas. Pourquoi? Eh bien, il se trouve que l'ajout d'octets en Java est défini pour renvoyer unint . Je pense que cela est dû au fait que la machine virtuelle Java ne définit pas d'opérations d'octets pour économiser sur les bytecodes (il y en a un nombre limité, après tout), l'utilisation d'opérations entières à la place est un détail d'implémentation exposé dans un langage.

Mais si a = a + bcela ne fonctionne pas, cela signifierait a += bqu'il ne fonctionnerait jamais pour les octets s'il E1 += E2était défini comme tel E1 = E1 + E2. Comme le montre l'exemple précédent, ce serait effectivement le cas. En tant que hack pour faire +=fonctionner l'opérateur pour les octets et les courts métrages, il y a une distribution implicite impliquée. Ce n'est pas un hack génial, mais au cours du travail Java 1.0, l'accent était mis sur la sortie du langage pour commencer. Maintenant, en raison de la compatibilité descendante, ce hack introduit dans Java 1.0 n'a pas pu être supprimé.

Konrad Borowski
la source