5 erreurs commises par 99% des développeurs Java
Source :
Medium Dans cet article, vous découvrirez les erreurs les plus courantes commises par de nombreux développeurs Java. En tant que programmeur Java, je sais à quel point il est mauvais de passer beaucoup de temps à corriger des bogues dans votre code. Parfois, cela prend plusieurs heures. Cependant, de nombreuses erreurs apparaissent du fait que le développeur ignore les règles de base, c'est-à-dire qu'il s'agit d'erreurs de très bas niveau. Aujourd'hui, nous allons examiner quelques erreurs de codage courantes, puis expliquer comment les corriger. J'espère que cela vous aidera à éviter des problèmes dans votre travail quotidien.
Comparaison d'objets à l'aide d'Objects.equals
Je suppose que vous connaissez cette méthode. De nombreux développeurs l'utilisent fréquemment. Cette technique, introduite dans JDK 7, vous aide à comparer rapidement des objets et à éviter efficacement les vérifications ennuyeuses des pointeurs nuls. Mais cette méthode est parfois mal utilisée. Voici ce que je veux dire :
Long longValue = 123L;
System.out.println(longValue==123);
System.out.println(Objects.equals(longValue,123));
Pourquoi remplacer
== par
Objects.equals() produirait-il un mauvais résultat ? En effet, le compilateur
== obtiendra le type de données sous-jacent correspondant au type d'empaquetage
longValue , puis le comparera à ce type de données sous-jacent. Cela équivaut à ce que le compilateur convertisse automatiquement les constantes en type de données de comparaison sous-jacent. Après avoir utilisé la méthode
Objects.equals() , le type de données de base par défaut de la constante du compilateur est
int . Vous trouverez ci-dessous le code source
de Objects.equals() où
a.equals(b) utilise
Long.equals() et détermine le type de l'objet. Cela se produit parce que le compilateur a supposé que la constante était de type
int , le résultat de la comparaison doit donc être faux.
public static boolean equals(Object a, Object b) {
return (a == b) || (a != null && a.equals(b));
}
public boolean equals(Object obj) {
if (obj instanceof Long) {
return value == ((Long)obj).longValue();
}
return false;
}
Connaissant la raison, corriger l'erreur est très simple. Déclarez simplement le type de données des constantes, comme
Objects.equals(longValue,123L) . Les problèmes ci-dessus ne se poseront pas si la logique est stricte. Ce que nous devons faire, c'est suivre des règles de programmation claires.
Format de date incorrect
Dans le développement quotidien, vous devez souvent modifier la date, mais de nombreuses personnes utilisent le mauvais format, ce qui conduit à des choses inattendues. Voici un exemple :
Instant instant = Instant.parse("2021-12-31T00:00:00.00Z");
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("YYYY-MM-dd HH:mm:ss")
.withZone(ZoneId.systemDefault());
System.out.println(formatter.format(instant));
Celui-ci utilise le format
AAAA-MM-jj pour changer la date de 2021 à 2022. Tu ne devrais pas faire ça. Pourquoi? En effet,
le modèle Java DateTimeFormatter « YYYY » est basé sur la norme ISO-8601, qui définit l'année comme le jeudi de chaque semaine. Mais le 31 décembre 2021 tombant un vendredi, le programme indique à tort 2022. Pour éviter cela, vous devez utiliser le format
aaaa-MM-jj pour formater la date . Cette erreur se produit rarement, seulement avec l'arrivée de la nouvelle année. Mais dans mon entreprise, cela a provoqué un échec de production.
Utilisation de ThreadLocal dans ThreadPool
Si vous créez
une variable ThreadLocal , alors un thread accédant à cette variable créera une variable locale de thread. De cette façon, vous pouvez éviter les problèmes de sécurité des threads. Cependant, si vous utilisez
ThreadLocal sur
un pool de threads , vous devez être prudent. Votre code peut produire des résultats inattendus. Pour un exemple simple, disons que nous avons une plate-forme de commerce électronique et que les utilisateurs doivent envoyer un e-mail pour confirmer l'achat de produits.
private ThreadLocal<User> currentUser = ThreadLocal.withInitial(() -> null);
private ExecutorService executorService = Executors.newFixedThreadPool(4);
public void executor() {
executorService.submit(()->{
User user = currentUser.get();
Integer userId = user.getId();
sendEmail(userId);
});
}
Si nous utilisons
ThreadLocal pour enregistrer les informations utilisateur, une erreur cachée apparaîtra. Étant donné qu'un pool de threads est utilisé et que les threads peuvent être réutilisés, lors de l'utilisation
de ThreadLocal pour obtenir des informations utilisateur, il peut afficher par erreur les informations de quelqu'un d'autre. Pour résoudre ce problème, vous devez utiliser des sessions.
Utilisez HashSet pour supprimer les données en double
Lors du codage, nous avons souvent besoin de déduplication. Lorsque l’on pense à la déduplication, la première chose à laquelle beaucoup de gens pensent est l’utilisation
d’un HashSet . Cependant, une utilisation imprudente
de HashSet peut entraîner l’échec de la déduplication.
User user1 = new User();
user1.setUsername("test");
User user2 = new User();
user2.setUsername("test");
List<User> users = Arrays.asList(user1, user2);
HashSet<User> sets = new HashSet<>(users);
System.out.println(sets.size());
Certains lecteurs attentifs devraient pouvoir deviner la raison de cet échec.
HashSet utilise un code de hachage pour accéder à la table de hachage et utilise la méthode equals pour déterminer si les objets sont égaux. Si l'objet défini par l'utilisateur ne remplace pas la méthode hashcode et la méthode
égale , alors la méthode hashcode et la méthode
égale de l'objet parent seront utilisées par défaut. Cela amènera
le HashSet à supposer qu’il s’agit de deux objets différents, provoquant l’échec de la déduplication.
Élimination d'un thread de pool "mangé"
ExecutorService executorService = Executors.newFixedThreadPool(1);
executorService.submit(()->{
double result = 10/0;
});
Le code ci-dessus simule un scénario dans lequel une exception est levée dans le pool de threads. Le code métier doit assumer diverses situations, il est donc très probable qu'il lèvera
une RuntimeException pour une raison quelconque . Mais s'il n'y a pas de traitement spécial ici, alors cette exception sera « mangée » par le pool de threads. Et vous n’aurez même aucun moyen de vérifier la cause de l’exception. Par conséquent, il est préférable d’intercepter les exceptions dans le pool de processus.
Chaînes en Java - vue intérieure
Source :
Medium L'auteur de cet article a décidé d'examiner en détail la création, les fonctionnalités et les caractéristiques des chaînes en Java.
Création
Une chaîne en Java peut être créée de deux manières différentes : implicitement, en tant que chaîne littérale, et explicitement, à l'aide du mot-clé
new . Les chaînes littérales sont des caractères placés entre guillemets doubles.
String literal = "Michael Jordan";
String object = new String("Michael Jordan");
Bien que les deux déclarations créent un objet chaîne, il existe une différence dans la manière dont ces deux objets sont situés dans la mémoire tas.
Représentation interne
Auparavant, les chaînes étaient stockées sous la forme
char[] , ce qui signifie que chaque caractère était un élément distinct dans le tableau de caractères. Comme ils étaient représentés au format de codage de caractères
UTF-16 , cela signifiait que chaque caractère occupait deux octets de mémoire. Ce n'est pas très correct, car les statistiques d'utilisation montrent que la plupart des objets chaîne sont constitués uniquement de caractères
Latin-1 . Les caractères Latin-1 peuvent être représentés à l'aide d'un seul octet de mémoire, ce qui peut réduire considérablement l'utilisation de la mémoire, jusqu'à 50 %. Une nouvelle fonctionnalité de chaîne interne a été implémentée dans le cadre de la version JDK 9 basée sur
JEP 254 appelée Compact Strings. Dans cette version,
char[] a été remplacé par
byte[] et un champ d'indicateur d'encodeur a été ajouté pour représenter l'encodage utilisé (Latin-1 ou UTF-16). Après cela, le codage est effectué en fonction du contenu de la chaîne. Si la valeur contient uniquement des caractères Latin-1, alors le codage Latin-1 est utilisé (la classe
StringLatin1 ) ou le codage UTF-16 est utilisé (la classe
StringUTF16 ).
Allocation de mémoire
Comme indiqué précédemment, il existe une différence dans la manière dont la mémoire est allouée à ces objets sur le tas. L'utilisation du nouveau mot-clé explicite est assez simple puisque la JVM crée et alloue de la mémoire pour la variable sur le tas. Par conséquent, l’utilisation d’une chaîne littérale suit un processus appelé internement. L'internement de chaînes est le processus consistant à mettre des chaînes dans un pool. Il utilise une méthode permettant de stocker une seule copie de chaque valeur de chaîne individuelle, qui doit être immuable. Les valeurs individuelles sont stockées dans le pool String Intern. Ce pool est un magasin
Hashtable qui stocke une référence à chaque objet chaîne créé à l'aide de littéraux et de son hachage. Bien que la valeur de chaîne se trouve sur le tas, sa référence peut être trouvée dans le pool interne. Cela peut être facilement vérifié à l’aide de l’expérience ci-dessous. Ici, nous avons deux variables avec la même valeur :
String firstName1 = "Michael";
String firstName2 = "Michael";
System.out.println(firstName1 == firstName2);
Lors de l'exécution du code, lorsque la JVM rencontre
firstName1 , elle recherche la valeur de chaîne dans le pool de chaînes interne
Michael . S'il ne le trouve pas, une nouvelle entrée est créée pour l'objet dans le pool interne. Lorsque l'exécution atteint
firstName2 , le processus se répète à nouveau et cette fois, la valeur peut être trouvée dans le pool en fonction de la variable
firstName1 . De cette façon, au lieu de dupliquer et de créer une nouvelle entrée, le même lien est renvoyé. La condition d’égalité est donc satisfaite. En revanche, si une variable avec la valeur
Michael est créée à l'aide du mot-clé new, aucun internement ne se produit et la condition d'égalité n'est pas satisfaite.
String firstName3 = new String("Michael");
System.out.println(firstName3 == firstName2);
Le stage peut être utilisé avec
la méthode firstName3
intern() , bien que cela ne soit généralement pas préféré.
firstName3 = firstName3.intern();
System.out.println(firstName3 == firstName2);
L'internement peut également se produire lors de la concaténation de deux chaînes littérales à l'aide de l' opérateur
+ .
String fullName = "Michael Jordan";
System.out.println(fullName == "Michael " + "Jordan");
Nous voyons ici qu'au moment de la compilation, le compilateur ajoute les deux littéraux et supprime l' opérateur
+ de l'expression pour former une seule chaîne, comme indiqué ci-dessous. Au moment de l'exécution,
fullName et le « littéral ajouté » sont internés et la condition d'égalité est satisfaite.
System.out.println(fullName == "Michael Jordan");
Égalité
D'après les expériences ci-dessus, vous pouvez voir que seuls les littéraux de chaîne sont internés par défaut. Cependant, une application Java n'aura certainement pas que des chaînes littérales, puisqu'elle peut recevoir des chaînes provenant de différentes sources. Par conséquent, l’utilisation de l’opérateur d’égalité n’est pas recommandée et peut produire des résultats indésirables. Les tests d'égalité ne doivent être effectués que par la méthode
des égalités . Il effectue l'égalité en fonction de la valeur de la chaîne plutôt que de l'adresse mémoire où elle est stockée.
System.out.println(firstName1.equals(firstName2));
System.out.println(firstName3.equals(firstName2));
Il existe également une version légèrement modifiée de la méthode equals appelée
equalsIgnoreCase . Cela peut être utile à des fins insensibles à la casse.
String firstName4 = "miCHAEL";
System.out.println(firstName4.equalsIgnoreCase(firstName1));
Immutabilité
Les chaînes sont immuables, ce qui signifie que leur état interne ne peut pas être modifié une fois créées. Vous pouvez modifier la valeur d'une variable, mais pas la valeur de la chaîne elle-même. Chaque méthode de la classe
String qui traite de la manipulation d'un objet (par exemple,
concat ,
substring ) renvoie une nouvelle copie de la valeur plutôt que de mettre à jour la valeur existante.
String firstName = "Michael";
String lastName = "Jordan";
firstName.concat(lastName);
System.out.println(firstName);
System.out.println(lastName);
Comme vous pouvez le constater, aucune modification n'est apportée aux variables : ni
firstName ni
lastName . Les méthodes de classe
String ne changent pas l’état interne, elles créent une nouvelle copie du résultat et renvoient le résultat comme indiqué ci-dessous.
firstName = firstName.concat(lastName);
System.out.println(firstName);
GO TO FULL VERSION