web-dev-qa-db-fra.com

Pourquoi Java nécessite-t-il un transtypage explicite sur une variable finale si elle a été copiée à partir d'un tableau?

En commençant par le code suivant ...

byte foo = 1;
byte fooFoo = foo + foo;

Lorsque j'essaie de compiler ce code, j'obtiens l'erreur suivante ...

Erreur: (5, 27) Java: types incompatibles: possible conversion avec perte de l'int en octet

... mais si foo est final ...

final byte foo = 1;
final byte fooFoo = foo + foo;

le fichier sera compilé avec succès.

Passons au code suivant ...

final byte[] fooArray = new byte[1];
fooArray[0] = 1;

final byte foo = fooArray[0];
fooArray[0] = 127;

System.out.println("foo is: " + foo);

... imprimera

foo is: 1

... ce qui est bien. La valeur est copiée dans une variable finale et ne peut plus être modifiée. Jouer avec la valeur dans le tableau ne change pas la valeur de foo (comme prévu ...).

Pourquoi les éléments suivants nécessitent-ils un casting?

final byte[] fooArray = new byte[1];
fooArray[0] = 1;
final byte foo = fooArray[0];
final byte fooFoo = foo + foo;

En quoi est-ce différent du deuxième exemple de cette question? Pourquoi le compilateur me donne-t-il l'erreur suivante?

Erreur: (5, 27) Java: types incompatibles: possible conversion avec perte de l'int en octet

Comment cela peut-il arriver?

57
Koray Tugay

Le JLS ( §5.2 ) a des règles spéciales pour la conversion des affectations avec expressions constantes:

De plus, si l'expression est une expression constante ( §15.28 ) de type byte, short, char ou int:

  • Une conversion primitive rétrécissante peut être utilisée si le type de la variable est byte, short ou char, et que la valeur de l'expression constante est représentable dans le type de la variable variable.

Si nous suivons le lien ci-dessus, nous les voyons dans la définition de expression constante:

  • Littéraux de type primitif et littéraux de type String
  • Les opérateurs additifs + et -
  • Noms simples ( §6.5.6.1 ) qui font référence à des variables constantes ( §4.12.4 ).

Si nous suivons le deuxième lien ci-dessus, nous voyons que

Une variable de type primitif ou de type String, c'est-à-dire final et initialisée avec une expression constante à la compilation ( §15.28 ), est appelée a variable constante.

Il s'ensuit que foo + foo peut uniquement être affecté à fooFoo si foo est une variable constante. Pour appliquer cela à vos cas:

  • byte foo = 1; ne définit pas une variable constante car ce n'est pas final.

  • final byte foo = 1; définit une variable constante, car c'est final et initialisé avec un expression constante (un littéral primitif).

  • final byte foo = fooArray[0]; ne définit pas une variable constante car elle n'est pas initialisée avec une expression constante.

Notez que si fooFoo est lui-même final n'a pas d'importance.

45
shmosel

La valeur 1 s'intègre parfaitement dans un octet; 1 + 1 aussi; et lorsque la variable est finale, le compilateur peut faire pliage constant . (en d'autres termes: le compilateur n'utilise pas foo lors de cette opération +; mais les valeurs "brutes" 1)

Mais lorsque la variable n'est pas définitive, toutes les règles intéressantes sur les conversions et les promotions entrent en jeu (voir ici ; vous voulez lire la section 5.12 sur l'élargissement des conversions primitives).

Pour la deuxième partie: rendre un tableau final vous permet toujours de changer n'importe lequel de ses champs; donc encore; aucun pliage constant possible; de sorte que l'opération "d'élargissement" se déclenche à nouveau.

17
GhostCat

C'est en effet ce que le compilateur fait en repliement constant lorsqu'il est utilisé avec final, comme nous pouvons le voir dans le code octet:

    byte f = 1;
    // because compiler still use variable 'f', so `f + f` will 
    // be promoted to int, so we need cast
    byte ff = (byte) (f + f);
    final byte s = 3;
    // here compiler will directly compute the result and it know
    // 3 + 3 = 6 is a byte, so no need cast
    byte ss = s + s;
    //----------------------
    L0
    LINENUMBER 12 L0
    ICONST_1 // set variable to 1
    ISTORE 1 // store variable 'f'
    L1
    LINENUMBER 13 L1
    ILOAD 1 // use variable 'f'
    ILOAD 1
    IADD
    I2B        
    ISTORE 2 // store 'ff'
    L2

    LINENUMBER 14 L2
    ICONST_3 // set variable to 3
    ISTORE 3 // store 's'
    L3
    LINENUMBER 15 L3
    BIPUSH 6 // compiler just compute the result '6' and set directly
    ISTORE 4 // store 'ss'

Et si vous changez votre octet final en 127, il se plaindra également:

    final byte s = 127;
    byte ss = s + s;

dans ce cas, le compilateur calcule le résultat et le connaît hors limites, il se plaindra donc toujours de leur incompatibilité.

Plus:

Et ici est une autre question sur le pliage constant avec une chaîne:

7
Tony