1
votes

Comment dire à Java qu'une variable ne peut pas être nulle?

J'ai un programme qui ressemble essentiellement à ceci:

boolean[] stuffNThings;
int state=1;
for(String string:list){
   switch(state){
      case 1:
         if(/*condition*/){
            // foo
            break;
         }else{
            stuffNThings=new boolean[/*size*/];
            state=2;
         }
      // intentional fallthrough
      case 2:
         // bar
         stuffNThings[0]=true;
   }
}

Comme vous, un humain, pouvez le voir, le cas 2 ne se produit que lorsqu'il y avait auparavant un état 1 et qu'il est passé à l'état 2 après l'initialisation du tableau. Mais Eclipse et le compilateur Java ne voient pas cela, car cela leur ressemble à une logique assez complexe. Alors Eclipse se plaint:

La variable locale stuffNThings n'a peut-être pas été initialisée. "

Et si je change " boolean [] stuffNThings; " en " boolean [] stuffNThings = null; ", cela passe à ce message d'erreur:

Accès au pointeur nul potentiel: la variable stuffNThings peut être nulle à cet emplacement.

Je ne peux pas non plus l'initialiser en haut, car la taille du tableau n'est déterminée qu'après la boucle finale à l'état 1.

Java pense que le tableau pourrait être nul là, mais je sais que ça ne peut pas. Existe-t-il un moyen de le dire à Java? Ou suis-je forcément obligé de mettre un contrôle inutile null autour de lui? L'ajout de cela rend le code plus difficile à comprendre, car il semble qu'il puisse y avoir un cas où la valeur n'est pas réellement définie sur true .


6 commentaires

Accès potentiel au pointeur nul: la variable stuffNThings peut être nulle à cet emplacement. - est-ce un avertissement ou une erreur? Cela ne devrait pas être une erreur, vous pouvez donc l'ignorer.


Ce code semble trop complexe et l'intention n'est pas claire. Que veut-il faire?


@Eran Eclipse me donne une erreur, mais lorsque je clique sur "Continuer", le programme fonctionne normalement. Fascinant.


Où utilisez-vous stuffNThings ? Dans la boucle, après la boucle. Ma supposition (non informée) est que le code pourrait paraître plus lisible. Peut-être quelque chose pour le forum examen de code .


@SteveSmith Le code réel code à travers un fichier et fait d'abord une liste de choses basée sur la première partie du fichier, puis un tableau 2D dont les entrées ont quelque chose à voir avec les autres basées sur la partie inférieure de la liste . Ainsi, dans l'état 1, il ajoute à la liste puis utilise la taille de la liste pour initialiser le tableau 2D avant de passer à l'état 2 et de marquer les corrélations.


@JoopEggen Il est utilisé dans la boucle si "state" vaut 2, puis après la boucle.


4 Réponses :


5
votes

Java pense que le tableau pourrait être nul ici, mais je sais que ce n'est pas le cas.

Strictement parlant, Java pense que la variable pourrait être non initialisée . Si elle n'est pas définitivement initialisée, la valeur ne doit pas être observable .

(Que la variable soit initialisée silencieusement à null ou laissée dans un état indéterminé est un détail d'implémentation. Le fait est que le langage dit que vous ne devriez pas être autorisé pour voir la valeur.)

Mais de toute façon, la solution est de l'initialiser à null . C'est redondant, mais il n'y a aucun moyen de dire à Java de "juste me faire confiance, il sera initialisé".


Dans les variantes où vous obtenez des messages "Potential null pointer access":

  1. C'est un avertissement, pas une erreur.
  2. Vous pouvez ignorer ou supprimer un avertissement. (Si votre analyse d'exactitude est erronée, vous risquez d'obtenir des NPE. Mais c'est votre choix.)
  3. Vous pouvez désactiver certains ou tous les avertissements avec les commutateurs du compilateur.
  4. Vous pouvez supprimer un avertissement spécifique avec une annotation @SuppressWarnings :

    • Pour Eclipse, utilisez @SuppressWarnings ("null") .
    • Pour Android, utilisez @SuppressWarnings ("ConstantConditions") .

      Malheureusement, les balises d'avertissement ne sont pas entièrement standardisées. Cependant, un compilateur doit ignorer silencieusement un @SuppressWarnings pour une balise d'avertissement qu'il ne reconnaît pas.

  5. Vous pourrez peut-être restructurer le code.

Dans votre exemple, le code utilise le basculement. Les gens font rarement cela parce que cela conduit à un code difficile à comprendre. Donc, je ne suis pas surpris que vous puissiez trouver des exemples de cas marginaux impliquant un drop-through où un compilateur obtient les avertissements NPE un peu faux.

Quoi qu'il en soit, vous pouvez facilement éviter d'avoir à effectuer une opération de transfert en restructurant votre code. Copiez le code dans le cas cas 2: à la fin du cas cas 1: . Fixé. Continuez.


Notez que l'erreur "éventuellement non initialisée" n'est pas que le compilateur Java soit "stupide". Il y a tout un chapitre du JLS sur les règles de l ' affectation définie , etc. Un compilateur Java n'est pas autorisé à être intelligent à ce sujet, car cela signifierait que le même code Java serait légal ou non légal, selon l'implémentation du compilateur. Ce serait mauvais pour la portabilité du code.

Ce que nous avons en fait ici est un compromis de conception de langage. Le langage vous empêche d'utiliser des variables qui ne sont (vraiment) pas initialisées. Mais pour ce faire, le compilateur "stupide" doit parfois vous empêcher d'utiliser des variables dont vous (le programmeur intelligent) savez qu'elles seront initialisées ... parce que les règles le disent.

(Les alternatives sont pires: soit aucune vérification à la compilation des variables non initialisées conduisant à des plantages matériels dans des endroits imprévisibles, soit des vérifications différentes pour différents compilateurs.)


0 commentaires

0
votes

Une non-réponse distincte: quand le code est "tellement" compliqué qu'un compilateur IDE / java ne le "voit" pas, alors c'est une bonne indication que votre code est trop compliqué de toute façon . Au moins pour moi, ce que vous avez dit n'était pas évident. J'ai dû lire de haut en bas à plusieurs reprises pour me convaincre que la déclaration donnée dans la question était correcte.

Vous avez un if dans un commutateur dans un for. Un code propre et une "couche d'abstraction unique" vous le diraient: ce n'est pas un bon point de départ.

Regardez votre code. Ce que vous avez là, c'est une machine à états déguisée. Demandez-vous s'il vaut la peine de refactoriser cela à plus grande échelle, par exemple en le transformant en une machine à états explicite .

Une autre idée moins intrusive: utilisez une liste au lieu d'un tableau. Ensuite, vous pouvez simplement créer une liste vide et y ajouter des éléments si nécessaire.


4 commentaires

Je pourrais bien sûr faire une boucle sur le fichier deux fois et abandonner après la première rencontre de quelque chose qui déclenche l'état 2 dans mon exemple. Mais alors je devrais lire le début du fichier deux fois, ce qui est mauvais pour les performances. La liste peut fonctionner, mais pour l'instant je vais l'essayer avec le tableau 2D, après avoir appliqué la solution de mon auto-réponse.


@ FabianRöling Ce n'est pas ce que je dis! Je dis que vous implémentez une machine à états, et que la POO connaît de meilleures façons d'implémenter des machines à états que des instructions de commutation dans des boucles.


Après vendredi 14h00 UTC, je peux vous donner le code et la tâche, si vous voulez le regarder et me dire si vous connaissez une meilleure solution (je n'en connais pas actuellement), pour l'instant c'est interdit, car c'est ma finale examen.


@ FabianRöling Gardez à l'esprit que cette communauté ne consiste pas uniquement à résoudre votre problème. Il s'agit de créer du contenu utile pour les futurs lecteurs. Et ma réponse aux futurs lecteurs est la suivante: lorsque vous avez besoin d'une machine à statuts, implémentez une machine à états, au lieu d'utiliser des ifs dans les commutateurs de fors.



0
votes

Après avoir simplement essayé d'exécuter le code indépendamment de la plainte d'Eclipse, j'ai remarqué qu'il fonctionne effectivement sans problème. Donc, apparemment, c'était juste un avertissement réglé au niveau "erreur", bien qu'il ne soit pas critique.
Il y avait un bouton «Configurer la gravité du problème», donc j'ai réglé la gravité de «Accès au pointeur nul potentiel» sur «Avertissement» (et ajusté certains autres niveaux en conséquence). Maintenant, Eclipse le marque juste comme un avertissement et exécute le code sans se plaindre.


0 commentaires

0
votes

Plus compréhensible serait:

BitSet stuffNThings = new BitSet(/*max size*/);

Deux boucles, une pour l'initialisation, et une pour jouer avec les choses pourraient ou non être plus claires.

Il est plus facile sur l'analyse de flux (par rapport à un commutateur avec fall-through).

De plus, au lieu d'un booléen [] , un BitSet pourrait également être utilisé ( car il n'est pas de taille fixe en tant que tableau).

    boolean[] stuffNThings;
    boolean initialized = false;
    for (String string: list) {
        if (!initialized) {
            if (!/*condition*/) {
                stuffNThings = new boolean[/*size*/];
                initailized = true;
            }
        }
        if (initialized) {
            // bar
            stuffNThings[0] = true;
        }
    }


1 commentaires

Eclipse ne se plaindrait-il pas de la même chose "pourrait ne pas avoir été initialisé" dans cette version?