Java – Common Gotchas

Dans le même esprit que les autres plates-formes, il semblait logique de donner suite à cette question: Quelles sont les erreurs non évidentes les plus courantes en Java? Des choses qui semblent devoir fonctionner, mais pas.

Je ne donnerai pas de directives sur la façon de structurer les réponses, ou sur ce qui est “trop ​​facile” à considérer comme un piège, puisque c’est le but du vote.

Voir également:

  • Perl – Common getchas
  • .NET – Common getchas

Comparer l’égalité des objects en utilisant == au lieu de .equals() – qui se comporte complètement différemment pour les primitives.

Ce gotcha s’assure que les nouveaux venus sont confus lorsque "foo" == "foo" mais new Ssortingng("foo") != new Ssortingng("foo") .

 "a,b,c,d,,,".split(",").length 

renvoie 4, pas 7 comme vous pourriez (et je l’ai certainement fait ) attendre. split ignore toutes les chaînes vides renvoyées. Cela signifie:

 ",,,a,b,c,d".split(",").length 

retourne 7! Pour obtenir ce que je pense être le comportement le moins “étonnant”, vous devez faire quelque chose d’assez étonnant:

 "a,b,c,d,,,".split(",",-1).length 

pour obtenir 7.

Remplacement égal à () mais pas hashCode ()

Il peut avoir des résultats vraiment inattendus lors de l’utilisation de cartes, d’ensembles ou de listes.

Je pense qu’une méthode très sournoise est la méthode Ssortingng.subssortingng . Cela réutilise le même tableau sous-jacent char[] que la chaîne d’origine avec un offset et une length .

Cela peut entraîner des problèmes de mémoire très difficiles à voir. Par exemple, vous pouvez parsingr des fichiers extrêmement volumineux ( XML peut-être) pour quelques petits bits. Si vous avez converti l’intégralité du fichier en une Ssortingng (plutôt que d’utiliser un Reader pour parcourir le fichier) et utiliser la subssortingng pour récupérer les bits souhaités, vous transportez toujours l’intégralité du tableau char[] scènes J’ai vu cela se produire à plusieurs resockets et cela peut être très difficile à repérer.

En fait, c’est un exemple parfait de la raison pour laquelle l’ interface ne peut jamais être complètement séparée de l’ implémentation . Et ce fut une introduction parfaite (pour moi) il y a quelques années sur la raison pour laquelle vous devriez vous méfier de la qualité du code tiers.

SimpleDateFormat n’est pas thread-safe.

Essayez de lire Java Puzzlers qui est plein de trucs effrayants, même si une grande partie n’est pas des choses que vous rencontrez chaque jour. Mais cela va détruire une grande partie de votre confiance dans la langue.

Il y en a deux qui m’énervent un peu.

Date / Calendrier

Tout d’abord, les classes de date et de calendrier Java sont sérieusement endommagées. Je sais qu’il y a des propositions pour les réparer, j’espère juste qu’elles réussissent.

Calendar.get (Calendar.DAY_OF_MONTH) est basé sur 1
Calendar.get (Calendar.MONTH) est basé sur 0

Auto-boxing empêchant la reflection

L’autre est Integer vs int (cela vaut pour toute version primitive d’un object). Ceci est spécifiquement une gêne causée par le fait de ne pas penser à Integer comme étant différent de int (puisque vous pouvez les traiter la plupart du temps en raison de la mise en boîte automatique).

 int x = 5; int y = 5; Integer z = new Integer(5); Integer t = new Integer(5); System.out.println(5 == x); // Prints true System.out.println(x == y); // Prints true System.out.println(x == z); // Prints true (auto-boxing can be so nice) System.out.println(5 == z); // Prints true System.out.println(z == t); // Prints SOMETHING 

Puisque z et t sont des objects, même s’ils ont la même valeur, ils sont (probablement) des objects différents. Ce que vous vouliez vraiment dire c’est:

 System.out.println(z.equals(t)); // Prints true 

Celui-ci peut être pénible à traquer. Vous allez déboguer quelque chose, tout semble correct, et vous finissez par trouver que votre problème est que 5! = 5 lorsque les deux sont des objects.

Être capable de dire

 List stuff = new ArrayList(); stuff.add(5); 

est tellement gentil Cela rendait Java beaucoup plus utilisable pour ne pas avoir à mettre toutes ces nouvelles lignes “Integer (5)” et “((Integer) list.get (3)). IntValue () partout. Mais ces avantages viennent avec ce piège.

 List list = new java.util.ArrayList(); list.add(1); list.remove(1); // throws... 

Les anciennes API n’étaient pas conçues avec la boxe en tête, donc surchargées de primitives et d’objects.

Celui-ci je viens de tomber sur:

 double[] aList = new double[400]; List l = Arrays.asList(aList); //do intense stuff with l 

Quelqu’un voit le problème?


Qu’est-ce qui se passe, Arrays.asList() attend un tableau de types d’object (Double [], par exemple). Ce serait bien si elle venait de lancer une erreur pour l’ocde précédente. Cependant, asList() peut également prendre des arguments comme ceux-ci:

 Arrays.asList(1, 9, 4, 4, 20); 

Donc, le code crée une List avec un élément – un double[] .

J’aurais dû me dire quand il a fallu 0 ms pour sortinger un tableau d’éléments de 750000 …

Celui-ci m’a trompé à quelques resockets et j’ai entendu pas mal de développeurs java expérimentés perdre beaucoup de temps.

ClassNotFoundException — vous savez que la classe est dans le classpath, MAIS vous n’êtes PAS sûr de savoir pourquoi la classe ne se charge PAS.

En fait, cette classe a un bloc statique. Il y avait une exception dans le bloc statique et quelqu’un a manqué l’exception. ils ne devraient pas. Ils devraient lancer ExceptionInInitializerError. Alors, cherchez toujours des blocs statiques pour vous faire trébucher. Il est également utile de déplacer n’importe quel code dans des blocs statiques pour passer aux méthodes statiques, de sorte que le débogage de la méthode est beaucoup plus facile avec un débogueur.

Des flotteurs

Je ne sais pas plusieurs fois j’ai vu

 floata == floatb 

où le test “correct” devrait être

 Math.abs(floata - floatb) < 0.001 

Je souhaite vraiment que BigDecimal avec une syntaxe littérale soit le type décimal par défaut ...

Pas vraiment spécifique à Java, car de nombreux langages (mais pas tous) l’implémentent de cette manière, mais l’opérateur % n’est pas un véritable opérateur modulo, car il fonctionne avec des nombres négatifs. Cela en fait un opérateur restant , et peut entraîner des sursockets si vous n’en avez pas conscience.

Le code suivant apparaît pour imprimer soit “pair” ou “impair”, mais ce n’est pas le cas.

 public static void main(Ssortingng[] args) { Ssortingng a = null; int n = "number".hashCode(); switch( n % 2 ) { case 0: a = "even"; break; case 1: a = "odd"; break; } System.out.println( a ); } 

Le problème est que le code de hachage pour “nombre” est négatif, donc l’opération n % 2 dans le commutateur est également négative. Comme il n’y a pas de cas dans le commutateur pour traiter le résultat négatif, la variable a n’est jamais définie. Le programme imprime null .

Assurez-vous de savoir comment l’opérateur % travaille avec des nombres négatifs, quelle que soit la langue dans laquelle vous travaillez.

La manipulation de composants Swing en dehors du thread de dissortingbution d’événements peut conduire à des bogues extrêmement difficiles à trouver. C’est une chose que nous (en tant que programmeurs expérimentés avec 3 ans d’expérience en Java) oublions souvent! Parfois, ces bugs se glissent après avoir bien écrit le code et après avoir refait le travail avec négligence …

Voir ce tutoriel pourquoi vous devez .

Chaînes immuables, ce qui signifie que certaines méthodes ne modifient pas l’object d’origine mais renvoient plutôt une copie d’object modifiée. Au début de Java, je l’oubliais tout le temps et je me demandais pourquoi la méthode replace ne semblait pas fonctionner sur mon object ssortingng.

 Ssortingng text = "foobar"; text.replace("foo", "super"); System.out.print(text); // still prints "foobar" instead of "superbar" 

Je pense que les gros problèmes qui me déconcentreraient quand j’étais un jeune programmeur étaient les exceptions de modification simultanée lors de la suppression d’un tableau que vous étiez en train d’itérer:

  List list = new ArrayList(); Iterator it = list.iterator(); while(it.hasNext()){ //some code that does some stuff list.remove(0); //BOOM! } 

si vous avez une méthode qui porte le même nom que le constructeur, BUT a un type de retour. Bien que cette méthode ressemble à un constructeur (à un noob), ce n’est pas le cas.

passer des arguments à la méthode principale – il faut un certain temps pour que les noobs s’y habituent.

qui passe . comme argument de classpath pour exécuter un programme dans le répertoire en cours.

Se rendre compte que le nom d’un tableau de chaînes n’est pas évident

hashCode et égaux: beaucoup de développeurs Java avec plus de 5 ans d’expérience ne comprennent pas tout à fait.

Set vs liste

Jusqu’à JDK 6, Java ne disposait pas de NavigableSets pour vous permettre de parcourir facilement un ensemble et une carte.

Division entière

 1/2 == 0 not 0.5 

Utiliser le ? génériques génériques.

Les gens le voient et pensent qu’ils doivent, par exemple, utiliser une List Quand ils veulent une List ils peuvent append quelque chose, sans s’arrêter de penser qu’une List déjà. Ensuite, ils se demandent pourquoi le compilateur ne les laissera pas utiliser add() , car une List Signifie vraiment “une liste d’un type spécifique que je ne connais pas”, donc la seule chose que vous pouvez faire avec cette List est Object instances d’ Object de celui-ci.

(un) boxe et longue / longue confusion. Contrairement à l’expérience pré-Java 5, vous pouvez obtenir une exception NullPointerException sur la deuxième ligne ci-dessous.

 Long msec = getSleepMsec(); Thread.sleep(msec); 

Si getSleepTime () renvoie une valeur null, unboxing lève.

Le hachage par défaut est non déterministe, donc s’il est utilisé pour des objects dans un HashMap, l’ordre des entrées dans cette carte peut varier d’une exécution à l’autre.

Comme démonstration simple, le programme suivant peut donner des résultats différents en fonction de son exécution:

 public static void main(Ssortingng[] args) { System.out.println(new Object().hashCode()); } 

La quantité de mémoire allouée au segment de mémoire ou si vous l’exécutez dans un débogueur peut modifier le résultat.

Lorsque vous créez un duplicate ou une slice d’un ByteBuffer , il n’hérite pas de la valeur de la propriété order du tampon parent. Le code comme celui-ci ne fera pas ce que vous attendez:

 ByteBuffer buffer1 = ByteBuffer.allocate(8); buffer1.order(ByteOrder.LITTLE_ENDIAN); buffer1.putInt(2, 1234); ByteBuffer buffer2 = buffer1.duplicate(); System.out.println(buffer2.getInt(2)); // Output is "-771489792", not "1234" as expected 

Parmi les pièges les plus courants, bien connus mais qui continuent de mordre occasionnellement les programmeurs, il y a le classique if (a = b) qui se trouve dans tous les langages de type C.

En Java, il ne peut bien sûr fonctionner que si a et b sont booléens. Mais je vois trop souvent les tests des débutants comme if (a == true) (alors que if (a) est plus court, plus lisible et plus sûr …) et parfois écrire par erreur if (a = true) , se demander pourquoi t travailler.
Pour ceux qui ne l’obtiennent pas: la dernière instruction assigne d’abord true à a , puis fait le test, qui réussit toujours!

Celui qui mord beaucoup de débutants, et même certains programmeurs plus expérimentés (l’ont trouvé dans notre code), le if (str == "foo") . Notez que je me suis toujours demandé pourquoi Sun remplaçait le signe + pour les chaînes mais pas le ==, du moins pour les cas simples (sensibles à la casse).

Pour les débutants: == compare les références, pas le contenu des chaînes. Vous pouvez avoir deux chaînes de même contenu, stockées dans des objects différents (références différentes), donc == sera faux.

Exemple simple:

 final Ssortingng F = "Foo"; Ssortingng a = F; Ssortingng b = F; assert a == b; // Works! They refer to the same object Ssortingng c = "F" + F.subssortingng(1); // Still "Foo" assert c.equals(a); // Works assert c == a; // Fails 

Et j’ai aussi vu if (a == b & c == d) ou quelque chose comme ça. Cela fonctionne (curieusement) mais nous avons perdu le raccourci de l’opérateur logique (n’essayez pas d’écrire: if (r != null & r.isSomething()) !).

Pour les débutants: lors de l’évaluation d’ a && b , Java n’évalue pas b si a est faux. Dans a & b , Java évalue les deux parties, puis effectue l’opération. mais la deuxième partie peut échouer.

[EDIT] Bonne suggestion de J Coombs, j’ai mis à jour ma réponse.

Le système de type non unifié contredit l’idée d’orientation de l’object. Même si tout ne doit pas nécessairement être des objects alloués au tas, le programmeur doit toujours être autorisé à traiter les types primitifs en appelant des méthodes sur ceux-ci.

L’implémentation du système de type générique avec effacement de type est horrible et la plupart des étudiants ne sont pas familiarisés avec les génériques pour la première fois en Java: Pourquoi faut-il toujours transtyper si le paramètre type est déjà fourni? Oui, ils ont assuré la compatibilité avec le passé, mais à un coût plutôt stupide.

En commençant, voici celui que j’ai attrapé aujourd’hui. Cela concernait la confusion long / long .

 public void foo(Object obj) { if (grass.isGreen()) { Long id = grass.getId(); foo(id); } } private void foo(long id) { Lawn lawn = bar.getLawn(id); if (lawn == null) { throw new IllegalStateException("grass should be associated with a lawn"); } } 

De toute évidence, les noms ont été modifiés pour protéger les innocents 🙂

Un autre point que je voudrais souligner est le lecteur (trop répandu) pour rendre les API génériques. Utiliser un code générique bien conçu est bien. Concevoir votre propre est compliqué. Très compliqué!

Il suffit de regarder la fonctionnalité de sorting / filtrage dans le nouveau JTable Swing. C’est un cauchemar complet. Il est évident que vous êtes susceptible de vouloir enchaîner les filtres dans la vie réelle, mais j’ai trouvé cela impossible sans simplement utiliser la version typée brute des classes fournies.

  System.out.println(Calendar.getInstance(TimeZone.getTimeZone("Asia/Hong_Kong")).getTime()); System.out.println(Calendar.getInstance(TimeZone.getTimeZone("America/Jamaica")).getTime()); 

La sortie est la même.

IMHO 1. Utilisation de vector.add (Collection) au lieu de vector.addall (Collection). Le premier ajoute l’object collection au vecteur et le second ajoute le contenu de la collection. 2. Bien que cela ne soit pas lié à la programmation, l’utilisation de parseurs xml provenant de sources multiples telles que xerces, jdom. S’appuyer sur des parsingurs différents et avoir leurs bocaux dans le chemin de classe est un cauchemar.

Je me suis amusé à déboguer une fois TreeSet, car je n’étais pas au courant de cette information de l’API:

Notez que l’ordre géré par un ensemble (qu’un comparateur explicite soit fourni ou non) doit être cohérent avec les égaux s’il doit implémenter correctement l’interface Set. (Voir Comparable ou Comparator pour une définition précise de la cohérence avec les égaux.) En effet, l’interface Set est définie en termes d’opération equals, mais une instance TreeSet effectue toutes les comparaisons clés en utilisant sa méthode compareTo (ou compare). les clés jugées égales par cette méthode sont, du sharepoint vue de l’ensemble, égales. Le comportement d’un ensemble est bien défini même si son classement n’est pas cohérent avec les égaux; il ne parvient tout simplement pas à respecter le contrat général de l’interface Set. http://download.oracle.com/javase/1.4.2/docs/api/java/util/TreeSet.html

Des objects avec des implémentations égales / hashcode correctes ont été ajoutés et jamais revus, car l’implémentation compareTo était incompatible avec les égaux.