Java: des choses qui peuvent sembler curieuses à un développeur expérimenté

Bon moment de la journée!

L'article a été écrit dans le sillage de la publication «Choses que vous [peut-être] ne saviez pas sur Java» par un autre auteur, que je qualifierais de «pour les débutants». En le lisant et en le commentant, j'ai réalisé qu'il y avait un certain nombre de choses assez intéressantes que j'avais apprises, déjà en programmation en java depuis plus d'un an. Peut-être que ces choses sembleront curieuses à quelqu'un d'autre.

Les faits qui, de mon point de vue, peuvent être utiles aux débutants, je les ai supprimés dans les "spoilers". Certaines choses peuvent être intéressantes pour les plus expérimentés. Par exemple, je ne savais moi-même qu'au moment de l'écriture que Boolean.hashCode (true) == 1231 et Boolean.hashCode (false) == 1237.

pour les débutants
  • Boolean.hashCode (true) == 1231
  • Boolean.hashCode (false) == 1237
  • Float.hashCode (valeur) == Float.floatToIntBits (valeur)
  • Double.hashCode (valeur) - xor des premier et deuxième demi-mots 32 bits Double.doubleToLongBits (valeur)

Object.hashCode () n'est plus l'adresse d'un objet en mémoire


Avertissement: Il s'agit d'un détail jvm d'Oracle (HotSpot).

Il était une fois ce fut le cas.
Depuis jdk1.2.1 / docs / api / java / lang / Object.html # hashCode ():
Autant que cela est raisonnablement pratique, la méthode hashCode définie par la classe Object renvoie des entiers distincts pour des objets distincts. (Ceci est généralement implémenté en convertissant l'adresse interne de l'objet en un entier, mais cette technique d'implémentation n'est pas requise par le langage de programmation JavaTM.)

Puis ils l'ont refusé. C'est ce que javadoc dit pour jdk 12 .

vladimir_dolzhenko a suggéré que l'ancien comportement peut être restauré en utilisant -XX: hashCode = 4. Et le changement de comportement lui-même était presque de la version Java 1.2.

Integer.valueOf (15) == Integer.valueOf (15); Integer.valueOf (128)! = Integer.valueOf (128)


Avertissement: cela fait partie de jls .

Il est clair que lors de la comparaison de deux wrappers avec l'opérateur == (! =), Aucune mise en boîte automatique ne se produit. D'une manière générale, c'est la première égalité qui confond. Le fait est que pour les valeurs entières i: -129 <i <128 Les objets encapsuleurs entiers sont mis en cache. Par conséquent, pour i de cette plage, Integer.valueOf (i) ne crée pas à chaque fois un nouvel objet, mais renvoie un objet déjà créé. Pour les i qui ne tombent pas dans cette plage, Integer.valueOf (i) crée toujours un nouvel objet. Par conséquent, si vous ne surveillez pas de près ce qui est exactement et comment exactement il est comparé, vous pouvez écrire du code qui semble fonctionner et même couvert de tests, mais contenant en même temps un tel "rake".

Dans jvm d'Oracle (HotSpot), la limite supérieure de mise en cache peut être modifiée via la propriété "java.lang.Integer.IntegerCache.high" .

dans certains cas, les valeurs des champs statiques finaux primitifs ou de chaîne d'une autre classe sont résolues au moment de la compilation


Cela semble déroutant et la déclaration est un peu longue. Le sens est le suivant. Si nous avons une classe qui définit les constantes de types ou chaînes primitifs comme champs statiques finaux avec initialisation immédiate,
class AnotherClass { public final static String CASE_1 = "case_1"; public final static String CASE_2 = "case_2"; } 

lorsqu'il est utilisé dans d'autres classes,
 class TheClass { // ... public static int getCaseNumber(String caseName) { switch (caseName) { case AnotherClass.CASE_1: return 1; case AnotherClass.CASE_2: return 2; default: throw new IllegalArgumentException("value of the argument caseName does not belong to the allowed value set"); } } } 

les valeurs de ces constantes ("case_1", "case_2") sont résolues au moment de la compilation. Et ils sont insérés dans le code en tant que valeurs et non en tant que liens. Autrement dit, si nous utilisons de telles constantes de la bibliothèque, puis que nous obtenons une nouvelle version de la bibliothèque dans laquelle les valeurs des constantes ont changé, nous devons recompiler le projet. Sinon, les anciennes valeurs constantes peuvent continuer à être utilisées dans le code.

Ce comportement est observé dans tous les endroits où des expressions constantes (par exemple, switch / case) doivent être utilisées, ou le compilateur est autorisé à convertir des expressions en constantes et il peut le faire.

Ces champs ne peuvent pas être utilisés dans des expressions constantes dès que l'on supprime l'initialisation immédiate en transférant l'initialisation au bloc statique.

pour les débutants

Dans certaines conditions, le garbage collector peut ne jamais fonctionner.


Par conséquent, aucune finalize () ne sera lancée. Par conséquent, vous ne devez pas écrire de code qui repose sur le fait que finalize () fonctionnera toujours. Oui, et si l'objet est entré dans la poubelle avant la fin du programme, il ne sera probablement pas collecté par le collecteur.

La méthode finalize () pour un objet spécifique ne peut être appelée qu'une seule fois.


Dans finalize (), nous pouvons rendre l'objet visible à nouveau, et le garbage collector ne le "supprimera" pas cette fois. Lorsque cet objet tombe à nouveau dans la poubelle, il sera "compilé" sans appeler finalize (). Si une exception est levée dans finalize () et que l'objet n'est toujours visible par personne, il sera alors «assemblé». Finalize () ne sera plus appelé.

le flux dans lequel finaliser () sera appelé n'est pas connu à l'avance


Il est seulement garanti que ce thread sera exempt de verrous visibles par le programme principal.

la présence d'une méthode finalize () surchargée sur les objets ralentit le processus de récupération de place


Ce qui se trouve à la surface est la nécessité de vérifier la disponibilité des objets - une fois avant d'appeler finalize (), une fois dans l'une des exécutions de récupération de place suivantes.

il est vraiment difficile de lutter contre les blocages lors de la finalisation ()


Dans finalize () non trivial, des verrous peuvent être nécessaires, ce qui, étant donné les spécificités décrites ci-dessus, est très difficile à déboguer.

Object.finalize () puisque la version 9 de java est marquée comme obsolète!


Ce qui n'est pas surprenant, compte tenu des spécificités décrites ci-dessus.

initialisation de singleton paresseux classique: double verrouillage requis


Il y a une idée fausse sur ce sujet selon laquelle l'approche suivante (idiome à double vérification), qui semble très logique, fonctionne toujours:
 public class UnsafeDCLFactory { private Singleton instance; public Singleton get() { if (instance == null) { // read 1, check 1 synchronized (this) { if (instance == null) { // read 2, check 2 instance = new Singleton(); } } } return instance; // read 3 } } 

Nous regardons si l'objet est créé (lire 1, vérifier 1). Si oui, retournez-le. Sinon, définissez le verrou, assurez-vous que l'objet n'est pas créé, créez l'objet (le verrou est supprimé) et renvoyez l'objet.

L'approche ne fonctionne pas pour les raisons suivantes. (lire 1, vérifier 1) et (lire 3) ne sont pas synchronisés. Selon le concept du modèle de mémoire java, les modifications apportées dans un autre thread peuvent ne pas être visibles par notre thread tant que nous ne nous synchronisons pas. Merci mk2 pour le commentaire, voici la description correcte du problème:
Oui, read1 et read3 ne sont pas synchronisés, mais le problème ne se trouve pas dans un autre thread. Et le fait que les lectures non synchronisées puissent être réorganisées, c'est-à-dire read1! = null, mais read3 == null. Et en même temps, en raison de "instance = new Singleton ();" nous pouvons obtenir une référence à l'objet avant qu'il ne soit complètement construit, et c'est vraiment un problème de synchronisation avec un autre thread, mais pas read1 et read3, mais read3 et access aux membres d'instance.
Il est traité soit en ajoutant la synchronisation lors de la lecture, soit en marquant la variable dans laquelle vit le lien vers le singleton, volatile. (La solution avec volatile ne fonctionne qu'avec java 5+. Avant cela, java avait un modèle de mémoire avec une incertitude dans cette situation.) Voici une version de travail (avec une optimisation supplémentaire - la variable locale `res` a été ajoutée pour réduire le nombre de lectures du champ volatile).
 public class SafeLocalDCLFactory { private volatile Singleton instance; public Singleton getInstance() { Singleton res = instance; // read 1 if (res == null) { // check 1 synchronized (this) { res = instance; // read 2 if (res == null) { // check 2 res = new Singleton(); instance = res; } } } return res; } } 

Le code est tiré d'ici , sur le site d'Alexei Shipilev. Vous trouverez plus de détails sur ce problème.

"Initialisation à la demande idiome titulaire" - une très belle initialisation "paresseux" de singleton


java initialise les classes (objets Class) uniquement si nécessaire et, bien sûr, une seule fois. Et vous pouvez en profiter! C'est exactement ce que fait le mécanisme d'idiome du titulaire d'initialisation à la demande. (Le code est d'ici .)
 public class Something { private Something() {} private static class LazyHolder { static final Something INSTANCE = new Something(); } public static Something getInstance() { return LazyHolder.INSTANCE; } } 

La classe LazyHolder ne sera initialisée que lors du premier appel de Something.getInstance (). Jvm veillera à ce que cela ne se produise qu'une seule fois et, de plus, très efficacement - si la classe est déjà initialisée, il n'y aura pas de surcharge. Par conséquent, LazyHolder.INSTANCE sera également initialisé une fois, "lazy" et thread-safe.
morceau de spécification sur les frais généraux
Si cette procédure d'initialisation se termine normalement et que l'objet Class est entièrement initialisé et prêt à être utilisé, alors l'appel de la procédure d'initialisation n'est plus nécessaire et peut être éliminé du code - par exemple, en le corrigeant ou en régénérant autrement le code .
Source

D'une manière générale, les singletones ne sont pas considérés comme la meilleure pratique.

Le matériel n'est pas fini. Donc, si les mains «atteignent» et ce qui a déjà été écrit sera demandé, j'écrirai en quelque sorte davantage sur ce sujet.

Merci pour les commentaires constructifs. Plusieurs endroits de l'article ont été agrandis grâce à sergey-gornostaev , vladimir_dolzhenko , OlehKurpiak , mk2 .

Source: https://habr.com/ru/post/fr482950/


All Articles