J'ai essayé d'expliquer Effective Java 3rd Edition "presque tous les chapitres" en "japonais facile à lire".
Je pense que Effective Java est un livre incontournable pour devenir un ingénieur Java "à part entière". Surtout les ingénieurs qui sont en mesure de rendre publiques des API ne peuvent pas en créer de décentes à moins de comprendre ce qui est écrit dans ce livre.
Il va sans dire que c'est un livre merveilleux, mais d'un autre côté, j'ai aussi le sentiment que c'est un livre difficile à comprendre, "Bref, c'est ce que c'est".
La cause est probablement la suivante.
L'expression est rond-point.
Il y a 90 éléments au total, mais la structure des éléments est difficile à comprendre.
Les exemples de code peuvent être inutilement complexes.
L'essence du contenu lui-même n'est pas très difficile, mais je pense que c'est dommage que le seuil de ce livre soit relevé pour cette raison.
Par conséquent, dans cet article, je voudrais expliquer (presque) tous les éléments en "japonais facile à lire" autant que possible.
Cependant, j'ai omis des explications pour les éléments qui sont trop évidents ou faciles à lire. De plus, mon opinion personnelle est mitigée. J'espère que vous pouvez le voir sur cette prémisse.
Effective Java 3rd Edition est écrit pour Java 9. Par conséquent, je publierai la documentation officielle de Java 9 au cas où (car il est difficile à atteindre de manière inattendue).
[Java 9] Haut du document officiel
https://docs.oracle.com/javase/jp/9/
[Java 9] JDK Javadoc (suivi de l'écran supérieur)
https://docs.oracle.com/javase/jp/9/docs/toc.htm
[Java 9] Spécifications du langage Java (suivies de l'écran supérieur)
https://docs.oracle.com/javase/specs/jls/se9/html/index.html
Chapitre 1 Introduction
Il ne contient que les définitions des termes utilisés dans les livres. Vous n'êtes pas obligé de le lire. (fin)
Chapitre 2 Création et disparition d'objets
Élément 1 Considérez une méthode de fabrique statique au lieu d'un constructeur.
OK: BigInteger.probablePrime (int, Random) ← méthode de fabrique statique. Facile à comprendre.
Considérez d'abord la méthode de fabrique statique, et si elle est subtile, choisissez un constructeur.
◆ Avantages
** ① Vous pouvez donner un nom descriptif. ** **
Le constructeur présente les inconvénients suivants.
Le nom du constructeur est limité à un (nom de classe).
La seule distinction entre les constructeurs est la différence d'arguments. En conséquence, il est difficile pour les utilisateurs de comprendre les différences entre les constructeurs.
La méthode de l'usine statique ne présente pas cet inconvénient.
** ② Il n'est pas nécessaire de créer un nouvel objet. Réutilisez le même objet. ** **
** ③ Vous pouvez renvoyer un objet de ce sous-type au lieu du type de retour lui-même. ** **
Par exemple, java.util.Collections a une méthode de fabrique statique appelée ʻemptyList () `. Il présente les caractéristiques suivantes.
ʻEmptyList () `retourne une instance de la classe interne EmptyList. La classe interne EmptyList n'est pas publique et peut être masquée de l'extérieur.
Le type de retour de ʻemptyList () est l'interface List`.
Grâce à cela, l'API Collections est très simple. En particulier···
Le réalisateur n'a pas besoin d'expliquer à l'utilisateur comment utiliser EmptyList (API).
Les utilisateurs n'ont pas besoin d'être conscients de l'existence d'EmptyList et n'ont pas besoin de comprendre comment utiliser (API). Il vous suffit de savoir comment utiliser l'interface List.
** ④ Vous pouvez changer le sous-type à renvoyer en fonction de la situation. ** **
En ③, l'explication est basée sur l'hypothèse que le même sous-type sera toujours retourné.
Ce que je veux dire en ④, c'est que vous pouvez sélectionner et renvoyer celui qui convient à votre situation à partir de plusieurs sous-types.
** ⑤ Le sous-type à renvoyer peut être décidé lors de l'exécution. (C'est OK même si ce n'est pas décidé au moment de la mise en œuvre de la méthode de l'usine statique.)
Par exemple, JDBC DriverManager.getConnection () correspond à ceci.
En conséquence, il devient plus flexible en tant qu'API.
◆ Inconvénients
** ① L'utilisateur ne peut pas créer une sous-classe du type de retour. ** **
Par exemple, ʻemptyList () `dans java.util.Collections renvoie EmptyList, mais comme EmptyList est une classe privée, les utilisateurs ne peuvent pas créer de sous-classes d'EmptyList.
Non limité à cet exemple, je ne pouvais pas penser à un cas qui me donnerait envie de créer une sous-classe.
En pratique, je ne pense pas qu'il y ait de faiblesses ou de restrictions.
** ② Il est difficile pour les utilisateurs de trouver des méthodes d'usine statiques. ** **
C'est certainement le cas. Dans Javadoc, le constructeur est une section séparée, ce qui le distingue, mais les méthodes de fabrique statiques sont enterrées dans la liste des méthodes.
Essayez de rendre l'API facile à comprendre pour les utilisateurs en suivant les modèles de dénomination généraux ci-dessous.
Vue d'ensemble du cadre des collections
https://docs.oracle.com/javase/jp/9/docs/api/java/util/doc-files/coll-overview.html
Point 2 Considérez un constructeur face à de nombreux paramètres de constructeur
[NG] Modèle télescopique
public class NutritionFacts {
//Définition de champ omise
public NutritionFacts(int servingSize, int servings) {
this(servingSize, servings, 0);
}
public NutritionFacts(int servingSize, int servings, int calories) {
this(servingSize, servings, calories, 0);
}
//Le flux ci-dessus se poursuit sans fin ...
}
Point NG
Même si le paramètre n'a pas d'importance pour l'utilisateur, il forcera le réglage.
L'utilisateur doit se soucier du paramètre numérique, ce qui est très difficile. (En pratique, l'IDE le réduira un peu)
Il est difficile pour le lecteur du code de comprendre la signification de chaque paramètre. (En pratique, l'IDE le réduira un peu)
Tant que les types correspondent, aucune erreur de compilation ne se produira, donc même si des paramètres du même type sont spécifiés dans le mauvais ordre, l'utilisateur ne remarquera pas l'erreur.
[NG] Modèle Java Beans
//Ce sont simplement des Java Beans.
public class NutritionFacts {
//Définition de champ omise
public NutritionFats() {}
public void setServingSize(int val) { //Abréviation}
public void setServings(int val) { //Abréviation}
public void setCalories(int val) { //Abréviation}
//Le passeur continue ...
}
Point NG
Des incohérences peuvent survenir entre les paramètres après l'instanciation. À l'origine, de telles incohérences doivent être détectées avant l'instanciation.
Vous pouvez changer l'état interne de setter, vous ne pouvez donc pas rendre la classe immuable.
[Modèle de constructeur]
//Code d'utilisateur
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).
calories(100).sodium(35).carbohydrate(27).build();
Avantages
Facile à mettre en œuvre.
Facile à lire.
Vous pouvez détecter les incohérences entre les paramètres avant d'instancier avec build (). Cet avantage est très important. En effet, si l'incohérence n'est pas détectée ici et qu'une erreur se produit dans un endroit éloigné en raison de l'incohérence, il sera difficile d'en identifier la cause.
Ce modèle peut être appliqué même si les classes sont hiérarchiques.
Rend l'API flexible. Par exemple, vous pouvez créer de plus en plus d'objets uniques en réutilisant un Builder (instance) et en numérotant le numéro de série dans build ().
Inconvénients (triviaux)
Builder doit être instancié et doit être pris en compte dans les situations où les exigences de performances sont sévères. Cependant, je pense qu'une telle situation est rare dans la pratique, c'est donc un inconvénient mineur.
Le code de l'utilisateur sera plus long que le modèle télescopique. Par conséquent, il peut être préférable de l'utiliser lorsqu'il existe de nombreux paramètres. Cependant, le nombre de paramètres peut augmenter régulièrement à mesure que vous le maintenez, et il n'est pas réaliste de passer au modèle Builder au milieu, c'est donc un inconvénient mineur.
Élément 3 Appliquer les caractéristiques de singleton avec un constructeur privé ou un type enum
Il existe trois façons de réaliser un singleton. Choisissez le meilleur pour votre situation.
Implémenter singleton avec le type enum. ← Dans de nombreux cas, c'est le meilleur
Créez une instance avec le constructeur privé ...
Définir le champ public et publier.
Retour avec méthode d'usine statique.
Si vous implémentez une seule tonne avec le type enum, cela ressemblera à ceci.
enum type tonne unique
public enum Elvis {
INSTANCE;
public void someMethod();
}
Le meilleur moyen est d'utiliser enum, car d'autres méthodes risquent de ne pas être une seule tonne et prendront la peine de l'éviter. Plus précisément, c'est comme suit.
Dans le cas d'un constructeur privé, l'utilisateur peut appeler le constructeur privé interne par réflexion. Il est difficile d'écrire un processus de vérification pour éviter cela.
Si vous voulez pouvoir sérialiser une classe singleton, vous risquez de créer une instance à chaque désérialisation. Il est difficile d'écrire un processus pour éviter cela. (Je pense que c'est rare que vous vouliez sérialiser ...)
Je pense que ces risques peuvent être ignorés dans la pratique, mais il faut y réfléchir correctement.
La méthode d'utilisation du type enum n'a pas été vue dans la pratique, mais elle est rationnelle dans les points ci-dessus et devrait être activement utilisée.
Les deux méthodes d'utilisation du constructeur privé présentent les avantages suivants. Cependant, je pense qu'il n'y a qu'un nombre limité de cas où vous voulez obtenir ces avantages, donc en fin de compte, enum est le meilleur choix dans de nombreux cas.
Méthode
Avantages
Définir sur le champ public et publier
・ L'API peut clairement comprendre qu'il s'agit d'une seule tonne. · Facile
Retour avec la méthode d'usine statique
・ Vous pouvez changer plus tard s'il doit être singleton ・ Peut être une usine singleton générique ・ Vous pouvez utiliser des références de méthode
Item 4 Forcer l'impossibilité d'instanciation avec un constructeur privé
Une classe constituée uniquement de méthodes statiques et de champs statiques est communément appelée classe utilitaire.
De telles classes ne doivent pas être instanciées et utilisées, mais si vous pouvez utiliser le constructeur, vous risquez de les instancier accidentellement. Je ne pense pas qu'il y ait vraiment de mal, mais cela me rend très déçu quand il est utilisé de cette manière.
Alors implémentons un constructeur privé comme celui-ci:
public class UtilityClass {
//Supprimez le constructeur par défaut afin qu'il ne puisse pas être instancié.
//En laissant un commentaire comme celui-ci, transmettons l'intention d'implémenter ce constructeur à la postérité.
private UtilityClass() {
throw new AssertionError();
}
}
Cela vous évitera d'être instancié ou hérité accidentellement pour créer des sous-classes.
Élément 5 Sélectionnez l'injection de dépendances plutôt que de lier directement les ressources
Prenons un correcteur orthographique comme exemple.
[NG] Implémenté en tant que classe utilitaire
public class SpellChecker{
//Dictionnaire utilisé pour Speccheck
private static final Lexicon dictionary = ...;
//Supprimer l'instanciation selon la méthode de l'élément 4
private SpellChecker() {}
public static boolean isValid(String word) { ... }
}
[NG] Mis en œuvre pour être une seule tonne
// SpellChecker.INSTANCE.isValid("some-word");Utilisez comme.
public class SpellChecker{
//Dictionnaire utilisé pour Speccheck
private final Lexicon dictionary = ...;
//Supprimer l'instanciation selon la méthode de l'élément 4
private SpellChecker() {}
public static SpellChecker INSTANCE = new SpellChecker(...);
public static boolean isValid(String word) { ... }
}
Étant donné qu'un seul dictionnaire peut être utilisé dans ces exemples NG, il n'est pas possible de changer de dictionnaire en fonction de la situation. Cette difficulté s'applique non seulement au code de production, mais également aux tests.
Il existe un moyen de fournir une méthode comme setDictionary (lexicon) afin que vous puissiez la changer plus tard, mais c'est difficile à comprendre pour l'utilisateur. Ce n'est pas non plus sûr pour les threads.
En premier lieu, le fait que le dictionnaire change en fonction de la situation signifie que le dictionnaire est un «état». Par conséquent, vous devez implémenter le correcteur orthographique comme une classe qui peut être instanciée et utilisée.
Plus précisément, c'est comme suit.
[OK] Implémenté dans le style de l'injection de dépendances
public class SpellChecker{
//Dictionnaire utilisé pour Speccheck
private final Lexicon dictionary;
//Puisqu'il a un "état" appelé dictionnaire, il est instancié et utilisé.
//A ce moment, une dépendance appelée dictionnaire est injectée.
public SpellChecker(Lexicon dictionary) {
this.dictionary = Objects.requireNonNull(dictionary);
}
public static boolean isValid(String word) { ... }
}
De cette façon, vous pouvez changer de dictionnaire en fonction de la situation et ce sera thread-safe.
Élément 6 Évitez de créer des objets inutiles
La réutilisation d'objets peut minimiser le coût de création d'objets et augmenter leur vitesse.
En revanche, si vous créez beaucoup d'objets inutiles, ce sera extrêmement lent.
La nature immuable est très importante car les objets immuables (objets immuables) peuvent toujours être réutilisés en toute sécurité.
[NG Partie 1]
// new String()Donc, nous créons un objet inutile.
// "bikini"Donc, nous créons un objet inutile.
String s = new String("bikini");
【OK】
//L'objet généré est"bikini"Instance de chaîne uniquement.
//Littéraux de chaîne dans la même JVM"bikini"Les instances de sont toujours réutilisées.
String s = "bikini";
[NG Partie 2]
//Puisqu'il s'agit d'un constructeur, de nouveaux objets sont toujours créés.
new Boolean(String);
【OK】
//La méthode de fabrique statique n'a pas besoin de créer un nouvel objet.
//Un objet booléen vrai ou faux est réutilisé.
Boolean.valueOf(String);
[NG Partie 3]
//Un objet Pattern est créé à l'intérieur des correspondances.
// isRomanNumeral()À chaque appel, un objet Pattern est créé.
static boolean isRomanNumeral(String s){
return s.matches("Expressions régulières. Le contenu est omis.");
}
【OK】
public class RomanNumerals {
//Vous réutilisez un objet Pattern.
private static final Pattern ROMAN = Pattern.compile("Expressions régulières. Le contenu est omis.");
static boolean isRomanNumeral(String s) {
return ROMAN.matcher(s).matches();
}
}
[NG Partie 4]
//Exemple de NG en boxe automatique
private static long sum() {
Long sum = 0L;
for (long i = 0; i <= Integer.MAX_VALUE; i++)
//Comme Long est immuable, une nouvelle instance sera créée à chaque fois qu'elle est ajoutée.
sum += i;
return sum;
}
【OK】
private static long sum() {
//Passer au type primitif (Long)-> long)
long sum = 0L;
for (long i = 0; i <= Integer.MAX_VALUE; i++)
sum += i;
return sum;
}
À propos, par exemple, Map.keySet () renvoie une vue Set (adaptateur), mais quel que soit le nombre de fois que vous appelez keySet (), la même instance Set sera renvoyée. Il est facile de penser qu'une nouvelle instance est créée chaque fois que vous l'appelez, mais en fait, l'instance est également réutilisée à ces endroits et l'efficacité est améliorée. Même lorsque nous implémentons l'API, nous devons l'implémenter de manière optimale du point de vue «Est-il nécessaire de créer une nouvelle instance?
Élément 7 Supprimer les références d'objet obsolètes
Si votre classe gère sa propre mémoire qui échappe au contrôle du garbage collector, vous devez effacer les références aux objets dont vous n'avez plus besoin (définissez la variable sur null).
Non géré signifie que le garbage collector ne peut pas reconnaître même un objet pratiquement inutilisé.
Normalement, les variables hors de portée sont soumises au garbage collection. En revanche, si vous gérez des objets dans une classe, ils ne seront pas hors de portée et ne seront pas soumis au garbage collection. Du point de vue du garbage collector, l'objet est considéré comme étant en cours d'utilisation.
Dans le livre, ce qui précède est expliqué en utilisant une implémentation de pile simple comme exemple. Veuillez consulter le livre pour plus de détails.
Cas d'implémentation de votre propre cache
Même si vous implémentez votre propre cache, cela correspond au "cas de gestion de votre propre mémoire dans la classe" mentionné ci-dessus.
Par exemple, lors de la mise en cache de données d'image avec HashMap, HashMap doit être géré comme un champ d'un objet A, de sorte que les références sont connectées comme suit.
Source-> Objet A-> HashMap-> clé et valeur
Même si les données (clé et valeur) gérées par HashMap ne sont plus nécessaires, ces données ne seront pas collectées tant que l'objet A et HashMap continueront à être référencés. En conséquence, la mémoire augmente.
Le remède dans ce cas est le suivant. (Je ne pense pas qu'il existe de nombreuses possibilités d'implémenter le cache vous-même ...)
** Méthode: implémentez le cache avec WeakHashMap. ** **
WeakHashMap sera soumis au prochain GC pour son entrée (paire clé / valeur) lorsque la clé n'est plus référencée par quelqu'un d'autre que son objet WeakHashMap. Nous utilisons un mécanisme appelé référence faible.
Pensez à l'adopter si vous souhaitez la supprimer du cache avec l'état que "la clé n'est référencée par personne d'autre que cet objet WeakHashMap".
Comme expliqué dans ce livre, s'il est supprimé du cache si facilement, il ne peut plus être appelé cache. Pour plus de détails, voir [Voir ci-dessous. ](# Qu'est-ce qu'une référence faible référence)
** Méthode (2) Supprimez régulièrement les anciennes données du cache avec ScheduledThreadPoolExecutor. ** **
Cet article est recommandé pour une compréhension rapide de l'utilisation de ScheduledThreadPoolExecutor.
https://codechacha.com/ja/java-scheduled-thread-pool-executor/
Pensez à l'utiliser si vous souhaitez supprimer quelque chose qui a été enregistré dans le cache depuis un certain temps.
** Méthode ③ Lors de l'ajout d'une nouvelle entrée dans le cache, supprimez l'ancienne. ** **
C'est simple. Similaire à (2), si vous souhaitez supprimer quelque chose qui a été enregistré dans le cache depuis un certain temps, envisagez de l'adopter.
Cas de l'enregistrement des écouteurs et des rappels en mémoire
Lors de la création d'une API qui permet aux écouteurs et aux rappels d'être enregistrés à partir du client, les écouteurs enregistrés et les rappels ne seront pas soumis à GC à moins qu'ils ne soient créés avec une considération appropriée.
C'est une bonne idée d'utiliser un mécanisme de référence faible, comme l'enregistrer en tant que clé pour WeakHashMap. S'il n'est plus utilisé par quelqu'un d'autre que WeakHashMap, il sera soumis à GC.
Référence: Qu'est-ce qu'une référence faible?
Les objets qui ne peuvent normalement pas atteindre le référent (objets qui ne sont utilisés par personne) sont soumis à GC et sont supprimés de la mémoire.
Cependant, cela peut poser problème s'il est supprimé immédiatement. Il existe un mécanisme pour empêcher les objets qui ne peuvent pas être atteints depuis la source de référence d'être immédiatement soumis à GC. C'est le package java.lang.ref.
Dans le monde de ce package, les références ordinaires sont appelées «références fortes» et leurs propres «références» sont définies comme suit.
java.lang.Types de références dans ref
Difficulté à devenir une cible GC (valeur relative)
La description
Utilisation
Référence faible
★ Effacé facilement
Si seulement vous (objet WeakReference) faites référence à un objet A, vous serez soumis au prochain GC.
Utilisez cette option lorsque vous souhaitez effacer immédiatement l'objet A de la mémoire lorsqu'il n'y a pas de références fortes à l'objet A. WeakHashMap utilise WeakReference dans ce but précis. Dans ce livre, il est écrit qu'il peut être utilisé comme cache, mais si la référence forte disparaît, elle disparaîtra, je pense que ce n'est plus un cache, donc je pense qu'il n'y a presque aucune possibilité de l'utiliser.
Référence souple
★★ Assez têtu
Si vous seul (objet SoftReference) faites référence à un objet A, l'objet référencé a été créé récemment./S'il est référencé, il ne sera pas soumis au prochain CG. Sinon, il sera soumis au prochain CG.
Je pense que c'est le cas si vous l'utilisez à des fins de mise en cache, mais Java SE n'a pas de carte qui le prend en charge. Il semble qu'il n'y ait presque aucune chance de l'utiliser réellement.
Référence fantôme
★★★ En gros ça ne disparaît pas
Même si vous seul (objet PhantomReference) faites référence à un certain objet A, il ne sera pas la cible de GC.
(inconnue)
Point 8 Évitez les finisseurs et les nettoyants
Le finaliseur ici est java.lang.Object # finalize () ou une méthode qui le remplace dans une sous-classe.
Un nettoyeur est un java.lang.ref.Cleaner.
Il y a plusieurs raisons pour lesquelles ce n'est pas bon, mais il n'est presque pas nécessaire de comprendre le contenu.
Ne l'utilisez jamais car il est dangereux de toute façon. C'est très bien.
Élément 9 Sélectionnez try-with-resources plutôt que try-finally
try-finally a les problèmes suivants:
Une fuite fermée est susceptible de se produire.
Si vous essayez de fermer sans omission, l'imbrication augmentera. Par conséquent, le code est difficile à lire et à modifier.
Facile à écraser les exceptions qui se produisent.
Pour éviter d'écraser les exceptions, vous devez écrire une clause catch. Par conséquent, le code est difficile à lire et à modifier.
Try-with-resources résout ces problèmes.
Si une exception se produit pendant l'essai et puis la fermeture lève également une exception, la première sera prioritaire et levée. Vous pouvez attraper cela dans la clause catfh.
Pour accéder à ce dernier, utilisez la méthode getSuppressed dans le premier objet d'exception. Cependant, dans de nombreux cas, vous voudrez connaître le premier, il semble donc que vous n'aurez pas beaucoup d'occasions de l'utiliser.
Chapitre 3 Méthodes communes à tous les objets
Point 10 Lorsque vous remplacez les égaux, suivez le contrat général
Je pense que la possibilité de passer outre la méthode des égaux est limitée en premier lieu.
Si vous souhaitez remplacer, certaines conditions doivent être remplies.
Si vous devez remplacer la méthode d'égalité, respectez cette exigence. Plus précisément, les exigences sont les suivantes.
Réflectivité: x.equals (x) renvoie vrai.
Symétrie: x.equals (y) renvoie vrai uniquement si y.equals (x) est vrai.
Transition: Si x.equals (y) et y.equals (z) sont vrais, x.equals (z) renvoie vrai.
Cohérence: Si x.equals (y) est appelé plusieurs fois, le résultat sera le même.
Non nul: x.equals (null) renvoie faux.
X, y, z ne sont pas nuls.
Le livre dit qu'il y a des choses à garder à l'esprit pour chaque exigence, mais il n'y a pas beaucoup d'occasions de remplacer les égaux en premier lieu, il est donc probablement moins coûteux d'en apprendre davantage lorsque vous n'en avez pas besoin.
Pour cette raison, je ne fais référence aux livres que lorsque j'en ai besoin, et je n'entrerai pas non plus dans les détails dans cet article.
Élément 11 Lorsque vous remplacez égal à, remplacez toujours hashCode
Comme vous pouvez le voir dans l'élément 10, il n'y a pas beaucoup de chances de remplacer l'égalité, donc cet élément ne semble pas non plus très important. Je vais juste vous donner un aperçu.
Les conditions requises pour remplacer la méthode hashCode sont les suivantes:
Si la méthode hashCode est appelée plusieurs fois, elle renvoie la même valeur.
Si les deux objets sont égaux dans la méthode equals, la méthode hashCode des deux objets renvoie la même valeur.
Juste parce que les deux objets sont différents dans la méthode equals, la méthode hashCode des deux objets n'a pas non plus besoin de renvoyer des valeurs différentes **. Cependant, il est plus facile d'améliorer les performances de la table de hachage en renvoyant des valeurs différentes.
Élément 12 Toujours remplacer toString
L'avantage de la substitution de la méthode toString est qu'elle facilite le débogage pour les utilisateurs de cette classe.
Cependant, le système créé dans la pratique n'est pas une œuvre d'art et les ressources humaines et temporelles sont limitées. Par conséquent, je pense que vous devez décider de passer ou non si nécessaire.
Si vous remplacez la méthode toString, tenez compte des éléments suivants:
Sortie de toutes les informations que l'utilisateur doit savoir. Sachez que toString existe pour vous.
Décidez si vous souhaitez décider du format de la valeur de retour (par exemple, 090-1234-5678 pour les numéros de téléphone) en fonction de la situation. Il peut être intéressant pour les utilisateurs d'avoir un format fixe, mais une fois qu'il est décidé, il est difficile de le changer plus tard. Équilibrons.
Point 13 Remplacer soigneusement le clone
Overriding Object.clone () est fondamentalement NG. La raison en est la suivante.
Les exigences qui doivent être remplies lors de la substitution ne sont pas appliquées par le compilateur, ni même spécifiées dans le Javadoc.
Les exigences sont assez complexes. Consultez le livre pour plus de détails.
La méthode remplacée vous oblige à appeler Object.clone () sous la forme super.clone (). À ce moment, la complexité suivante est créée.
Vous devez gérer une CloneNotSupportedException que Object.clone () peut lancer.
Le type de retour d'Object.clone () est Object, mais en pratique vous devez le convertir en un type spécifique.
Les champs définitifs ne peuvent pas être copiés car la modification est interdite. Vous devez supprimer final pour une raison non essentielle pour remplacer clone ().
Il existe d'autres raisons détaillées en plus de ce qui précède. Consultez le livre pour plus de détails.
Puisqu'il est NG, vous ne devez pas remplacer Object.clone () à moins qu'il y ait une situation particulière où vous avez déjà une classe qui remplace Object.clone () et que vous devez le réparer pour la maintenance.
Utilisez plutôt les méthodes suivantes. Ceux-ci ne présentent pas les inconvénients ci-dessus.
//Copier le constructeur
public Yum(Yum yum) { ... }
//Copier l'usine
public static Yum newInstance(Yum yum) { ... }
Que vous utilisiez un constructeur de copie ou une méthode de fabrique de copie ou Object.clone (), tenez compte des points communs suivants.
En règle générale, lors de la copie d'un champ, effectuez une copie complète.
En d'autres termes, lors de la copie d'un champ, il ne suffit pas de définir la référence de l'objet source de la copie sur le champ de destination de la copie. En effet, la même référence d'objet est partagée entre la source de la copie et la destination de la copie. C'est la soi-disant copie superficielle. L'idée est évidente, mais elle est assez lourde à mettre en œuvre. Il existe une exception à cette règle, et s'il s'agit d'un objet immuable, vous pouvez copier la référence.
Bien sûr, c'est NG que l'état de la source de copie change pendant la copie, donc implémentons-le dans thread safe.
Pour comprendre cet élément, nous vous recommandons de connaître à l'avance les éléments suivants.
Object.clone () a un modificateur natif, donc il est implémenté dans des langages autres que Java. En d'autres termes, il y a le contenu du processus.
Type de retour covariant
https://www.slideshare.net/ryotamurohoshi/ss-38240061
Point 14 Envisager de mettre en œuvre Comparable
En implémentant Comparable.compareTo () dans la classe que vous développez, vous pouvez stocker les objets de cette classe dans une collection et utiliser l'API pratique de la collection. Par exemple, vous pourrez bien trier.
Si vous souhaitez bénéficier de cet avantage, implémentez une interface comparable.
Dans ce cas, des exigences similaires au remplacement de la méthode égale doivent être satisfaites.
sgn (x.compareTo (y)) == -sgn (y.compareTo (x)). Autrement dit, lorsque la comparaison est effectuée dans l'ordre inverse, les signes des résultats sont également inversés.
Si (x.compareTo (y)> 0 && y.compareTo (z)> 0), alors x.compareTo (z)> 0. En d'autres termes, cela devient une relation de transition.
Si x.compareTo (y) == 0, alors sgn (x.compareTo (z)) == sgn (y.compareTo (y)). Autrement dit, si l'ordre de x et y est égal, le résultat sera égal lors de la comparaison d'autres objets z avec chacun de x et y.
Si x.compareTo (y) == 0, alors x.equals (y) est vrai, et vice versa ** n'est pas obligatoire, mais préférable **.
Sgn (expression) est une fonction qui renvoie -1, 0, 1 lorsque l'expression est respectivement négative, zéro et positive.
Il y a des notes sur les trois premières exigences. Étant donné une classe existante qui implémente Comparable, il est pratiquement impossible de répondre à ces exigences si vous l'étendez et ajoutez de nouveaux champs. Si vous le forcez, ce n'est plus du code orienté objet. Dans un tel cas, réalisons-le avec la composition au lieu d'étendre (point 18).
Et si nous violons la quatrième exigence? Un exemple de violation est Big Decimal. new BigDecimal ("1.0") et new BigDecimal ("1.00") ne sont pas égaux dans la méthode equals et égaux dans la méthode compareTo.
Si vous les mettez dans un nouveau HashSet, ils seront comparés par la méthode equiqls, donc le nombre d'éléments sera de 2. Par contre, si vous le mettez dans un nouveau TreeSet, le nombre d'éléments sera de 1 car il sera comparé par la méthode compareTo. Si vous ne gardez pas ce comportement à l'esprit, vous n'aurez aucune idée de la cause dans l'éventualité peu probable d'un problème.
En plus des quatre exigences, notez les points suivants:
L'interface Comparable est paramétrée, il n'est donc pas nécessaire de vérifier ou de convertir les types d'arguments.
Si l'argument est nul, une NullPointerException doit être lancée. Cela devrait normalement être le cas sans rien faire.
Si les champs que vous souhaitez comparer sont des objets, comparez-les par compareTo au lieu de les comparer. Si vous le souhaitez, vous pouvez implémenter compareTo vous-même ou utiliser un composeur existant (par exemple, String.CASE_INSENSITIVE_ORDER).
N'utilisez pas d'opérateurs relationnels (>, <, ==) lors de la comparaison des grandeurs. Cela devient déroutant. Utilisez plutôt une méthode de comparaison telle que Integer.compare ().
En Java 8, il peut être implémenté de manière fluide en utilisant la méthode de construction du comparateur.
private static final Comparator<PhoneNumber> COMPARATOR =
comparingInt((PhoneNumber pn) -> pn.areaCode)
.thenComparingInt(pn -> pn.prefix)
.thenComparingInt(pn -> pn.lineNum);
public int compareTo(PhoneNumber pn) {
return COMPARATOR.compare(this, pn);
}
Si un comparateur basé sur la différence est utilisé, non seulement l'exigence transitoire ne peut pas être satisfaite, mais il existe également un risque qu'un débordement d'entier se produise. À la place, utilisez des méthodes statiques telles que Integer.compare () mentionnées ci-dessus ou des méthodes de construction de comparateur.
//Exemple NG
static Comparator<Object> hashCodeOrder = new Comparator<>() {
public int compare(Object o1, Object o2) {
return o1.hashCode() - o2.hashCode();
}
}
Chapitre 4 Classes et interfaces
Point 15 Minimiser l'accessibilité aux classes et aux membres
Qu'est-ce que le masquage et l'encapsulation d'informations?
Minimisez la partie du composant accessible de l'extérieur (API publique). Rendre les autres parties (données internes, détails d'implémentation) inaccessibles de l'extérieur.
Pour faire simple, minimisez ce que vous déclarez comme public ou protégé. Concevez soigneusement votre classe pour ce faire.
Raisons de cacher et d'encapsuler des informations (pourquoi)
Le masquage et l'encapsulation des informations permettent aux composants d'être développés individuellement et optimisés individuellement. En d'autres termes, vous pouvez réduire considérablement la crainte que cela n'endommage les autres composants.
Le but du masquage et de l'encapsulation d'informations est de viser ce qui suit.
Permet à chaque composant d'être développé en parallèle pour accélérer le développement.
Réduit l'effort d'enquête d'impact et réduit la charge de maintenance.
Permet l'élimination locale des goulots d'étranglement pour faciliter le réglage des performances.
Permet aux composants d'être réutilisés.
Si vous ne pouvez pas atteindre ces objectifs, il est inutile de viser la dissimulation ou l'encapsulation d'informations. Les programmes pratiques ne sont pas des œuvres d'art, mais un moyen pour une entreprise prospère. Donc, vous ne devriez pas être satisfait d'avoir fait quelque chose de beau (je fais très attention car il est facile d'être satisfait de cette façon).
Comment faire (comment)
Plus précisément, considérez les points suivants.
Au lieu d'ajouter du public sans réfléchir, choisissez celui qui minimise l'accessibilité.
La mise en œuvre de Serializable en fait une API publique.
Les API déclarées protégées seront des API publiques car elles sont accessibles aux utilisateurs (sous-classes).
Rendre le package privé pour les tests est acceptable dans la mesure où il n'est pas exposé au monde extérieur.
Si vous publiez une constante dans public static final, vous devez vous assurer que la constante est conforme à la politique d'abstraction du composant.
Les objets variables peuvent être modifiés par l'utilisateur. Par conséquent, il est NG de publier en tant que constante. Au lieu de cela, vous devez soit le convertir en un objet immuable et le publier (tel que Collections.unmodifiableList), soit en renvoyer une copie.
Le système de modules ajouté dans Java 9 n'a pas encore été largement utilisé et son utilité n'a pas été confirmée, il n'est donc pas nécessaire de l'utiliser activement pour le moment.
Élément 16 Dans la classe publique, utilisez la méthode d'accesseur au lieu du champ public.
Il va de soi d'ajouter setter / getter.
En pratique, je pense que c'est du bon sens de faire cela, et je pense qu'il est facile d'ajouter des setters / getters sans penser à rien. Cependant, si vous ne comprenez pas la raison, ce ne sera pas une bonne conception de classe. Comprenons la raison pour laquelle nous devrions ajouter à nouveau setter / getter.
La raison de l'ajout de setter / getter est de bénéficier du masquage et de l'encapsulation des informations. Plus précisément, c'est comme suit.
Ceci afin que le format de représentation des données internes (de quel type il doit s'agir, etc.) puisse être modifié ultérieurement.
Ceci afin que vous puissiez vérifier la cohérence entre les champs.
Ceci afin que le traitement auxiliaire puisse être ajouté plus tard.
Puisque le point essentiel est de réduire le nombre d'API publiques autant que possible, il est peu nécessaire de préparer les setters / getters dans les champs pour les classes privées de package et les classes internes privées. Si vous essayez de fournir un setter / getter inutilement, la mise en œuvre prendra plus de temps et le code sera difficile à lire. Ne vous sentez pas comme "ajoutez juste un setter / getter sans réfléchir".
Point 17 Minimiser la variabilité
Les objets immuables (objets immuables) présentent plusieurs avantages, notamment le fait qu'ils peuvent être utilisés en toute sécurité avec les threads. Des exemples typiques dans JDK sont des classes de données de base encadrées telles que String, Integer, BigDecimal, BigInteger.
Si vous créez votre propre classe qui a des valeurs, rendez-la immuable à moins que vous n'ayez une bonne raison de la rendre variable. Même s'il n'est pas pratique de le rendre parfaitement immuable, c'est une bonne idée de le rendre aussi immuable que possible, par exemple en rendant le champ aussi définitif que possible. En effet, si le nombre d'états possibles est réduit, il y a des avantages tels que moins de problèmes.
Avantages des objets immuables
Sans fil. Peu importe s'il est accédé par plusieurs threads. En pratique, je pense que c'est le plus grand mérite.
L'utilisateur n'a pas besoin d'être conscient de la transition d'état à l'intérieur de l'objet.
Vous pouvez partager l'instance. En conséquence, le coût de création d'objets et la consommation de mémoire peuvent être réduits.
L'état interne peut être partagé entre les instances. Par exemple, une classe qui contient un tableau peut transmettre une référence à un tableau détenu par une instance à une nouvelle instance pour réutilisation.
D'autres objets qui détiennent cette instance dans un champ peuvent avoir besoin de conserver un état en interne (comme la relation d'amplitude entre les champs). Si l'instance contenue dans le champ est variable, le processus de conservation de l'état souhaité devient très compliqué car vous devez être conscient de la transition d'état de cette instance. Ou, il est impossible de réaliser en premier lieu. Inversement, si l'instance détenue sur le terrain est immuable, ce problème n'existe pas.
Il y a une atomicité d'erreur. L'atomicité d'erreur est la propriété d'un objet de revenir à son état d'avant la défaillance. Les objets immuables ne changent pas en premier lieu, il est donc naturel d'avoir cette propriété. Afin de donner l'atomicité d'erreur d'objet variable, cela prend du temps et des efforts. Les objets immuables ne nécessitent pas ce genre d'effort, mais c'est bien.
Inconvénients des objets immuables
Chaque instance avec une valeur différente consomme des ressources système. En conséquence, il peut consommer beaucoup de mémoire et entraîner de mauvaises performances. Par exemple, dans chacune des étapes de calcul multiples, une instance ad hoc peut être créée et détruite. La solution de contournement est la suivante.
Combinez plusieurs étapes de traitement en une seule API publique.
Fournit une classe compagnon de variable publique, telle que StringBuilder pour String.
Comment rendre un objet immuable (conditions à remplir)
Ne fournissez pas de méthodes qui peuvent changer l'état d'un objet, comme setter.
Empêchez-le d'être étendu. Rendez la classe finale ou rendez le constructeur privé et fournissez des méthodes de fabrique statiques.
Rendre tous les champs définitifs. Cela rend le champ immuable et vous permet de passer en toute sécurité des références d'instance entre les threads. Il y a des exceptions à cela. Dans certains cas, vous souhaiterez peut-être mettre en cache les résultats de calcul qui ne sont utilisés qu'en interne dans le champ. Dans ce cas, la possibilité de ne pas la rendre définitive est également une fourmi.
Rendre tous les champs privés. En faisant cela, même si vous avez un objet variable dans le champ, vous pouvez empêcher l'utilisateur de modifier le contenu de l'objet. Vous pouvez également modifier la représentation interne du champ ultérieurement sans affecter l'utilisateur.
Si le champ a un objet mutable, seule la classe immuable (self) peut accéder à l'objet mutable. Lors de la création d'un objet de classe immuable basé sur l'objet passé par l'utilisateur, n'utilisez pas la référence telle quelle, mais faites une copie défensive (élément 50).
Point 18 Choisissez la composition plutôt que l'héritage
L'héritage rompt l'encapsulation. En d'autres termes, les sous-classes dépendent des détails d'implémentation des superclasses. Par conséquent, des modifications dans les détails d'implémentation des superclasses peuvent conduire à des sous-classes ne fonctionnant pas comme prévu ou à des failles de sécurité.
Vous pourriez penser que ce n'est pas grave si la sous-classe ne remplace pas les méthodes de la superclasse, mais ce n'est pas le cas. La signature de la méthode que la superclasse a ajoutée ultérieurement peut entrer en conflit avec la méthode implémentée dans la sous-classe.
En raison de ces inconvénients, ne sautez pas dans l'héritage soudainement. Les compositions n'ont pas le même inconvénient.
Cependant, l'héritage est mauvais à 100% et la composition n'est pas positive à 100%. Pouvons choisir entre composition et héritage en fonction de la situation.
Quelle est la composition
Au lieu d'hériter d'une classe existante, conservez la classe existante dans un champ privé, comme illustré ci-dessous.
Étendez une classe existante en appelant les méthodes de cette classe existante.
//Classe de transfert. La composition a été appliquée dans cette classe.
//Une classe est fournie séparément de InstrumentedSet afin qu'elle puisse être réutilisée.
public class ForwardingSet<E> implements Set<E> {
//Tenez l'objet de la classe existante que vous souhaitez étendre dans le champ.
private final Set<E> s;
public ForwardingSet(Set<E> s) {
this.s = s;
}
//Lancez le processus sur l'objet de la classe existante que vous souhaitez étendre.
public void clear() {
this.s.clear();
}
//Omis par la suite.
}
/*
Créez votre propre classe en héritant de la classe de transfert.
Vous pourriez penser: "Quoi? L'héritage n'est pas bon, non?", Mais l'héritage ici est
C'est une bonne décision de rendre le jeu de transfert réutilisable à d'autres fins.
*/
public class InstrumentedSet<E> extends FowardingSet<E> {
//Cette classe est responsable de la gestion du nombre de fois qu'un ensemble a été ajouté au total.
private int addCount = 0;
public InstrumentedSet(Set<E> s) {
super(s);
}
@Override
public boolean add(E e) {
addCount++;
return super.add(e);
}
//Omis par la suite.
}
En passant, la technique de l'exemple de code ci-dessus n'est pas exactement la "délégation". S'il vous plaît soyez prudente.
Quand il est normal d'hériter
Vous pouvez hériter dans les cas suivants.
Lorsque la classe héritée (la classe que vous voulez être une super classe) est créée en supposant qu'elle sera héritée. Le Javadoc de votre classe devrait le dire.
Lorsque la classe héritée (classe que vous voulez être une super classe) est publiée uniquement dans le package (package private). Dans ce cas, si vous modifiez la superclasse, même s'il y a un effet négatif sur la sous-classe, l'effet indésirable restera dans le package. Vous pouvez faire avancer les choses en modifiant les sous-classes.
En tant que prémisse majeure lors de l'adoption de l'héritage, il est nécessaire qu'une relation «est-un» soit établie entre les classes. Autrement dit, si la classe B hérite de la classe A, la réponse «Sont tous des Bs A?» Doit être oui. L'héritage est une technique pour réaliser de telles relations dans le code.
Point 19 Conception et document pour l'héritage, sinon interdire l'héritage
Créer une classe qui est censée être héritée est en fait extrêmement difficile et difficile. Plus précisément, vous devez tenir compte des éléments suivants:
Il est nécessaire de montrer à l'utilisateur quand la méthode qui peut être remplacée dans la sous-classe est appelée dans la superclasse. Ces informations sont exactement les "détails d'implémentation" de la superclasse, donc cela rompt le masquage / l'encapsulation des informations ...
Vous devrez fournir une méthode protégée (ou un champ dans certains cas) dans votre superclasse pour fournir aux sous-classes des crochets pour le comportement interne de la superclasse. Trop vous éloignera des avantages de la dissimulation / encapsulation d'informations. D'un autre côté, trop peu rendra les superclasses difficiles à utiliser pour les sous-classes. Il est extrêmement difficile de concevoir avec cet équilibre.
Les constructeurs de superclasses ne doivent pas appeler de méthodes remplaçables. Cela est dû au fait que le constructeur de la superclasse est appelé au début du constructeur de la sous-classe, vous appellerez donc la méthode remplaçable lorsque la sous-classe n'est pas prête.
Il y a certaines choses à savoir lorsque les superclasses implémentent Cloneable, Serializable. Cependant, je pense qu'une telle situation est rare, je vais donc omettre l'explication. Consultez le livre pour plus de détails.
Comme vous pouvez le voir, c'est extrêmement difficile. Si vous créez une classe qui est censée être héritée par tous les moyens, soyez prêt à accepter les points ci-dessus. C'est un professionnel.
Je pense qu'il y a plus de cas où ce n'est pas le cas que lors de la création d'une classe qui est censée être héritée. Dans ce cas, rendez la classe finale ou rendez le constructeur privé et préparez une méthode de fabrique statique afin que la classe créée ne soit pas héritée par erreur.
Élément 20 Sélectionnez une interface sur une classe abstraite
Cet article est extrêmement difficile à lire. J'expliquerai en priorité sur la compréhensibilité.
Il existe plusieurs implémentations d'un «type». Par exemple, il existe plusieurs implémentations d'un «type» appelé Comparable. Java fournit les deux mécanismes suivants pour réaliser un tel type «autoriser plusieurs implémentations».
Interface (Cliquez ici pour l'exemple de Comparable)
Classe abstraite
Si vous voulez créer un nouveau type qui "autorise plusieurs implémentations", faites-le essentiellement avec une interface. L'interface est supérieure à la classe abstraite des manières suivantes: Il est facile à utiliser pour les utilisateurs.
Les utilisateurs peuvent implémenter plusieurs interfaces. Java ne peut hériter que d'une classe, vous n'obtiendrez donc pas ces avantages lors de l'utilisation de classes abstraites.
Les utilisateurs peuvent introduire des interfaces dans une classe existante, même si elle hérite déjà de quelque chose. Java ne peut hériter que d'une classe, vous n'obtiendrez donc pas ces avantages lors de l'utilisation de classes abstraites.
Les utilisateurs peuvent combiner des interfaces existantes pour en créer de nouvelles. Vous pouvez faire la même chose avec des classes abstraites, mais c'est ridiculement compliqué. Plus précisément, cet article est très utile.
Supposons qu'un utilisateur souhaite créer une nouvelle fonction en utilisant le type existant "autoriser plusieurs implémentations". Si le type est une classe abstraite, l'utilisateur n'a d'autre choix que d'hériter de la classe abstraite. S'il s'agit d'une interface, vous pouvez utiliser la composition (élément 18).
Techniques de création d'interfaces compliquées "aide au montage" "montage squelette"
Si vous souhaitez créer vous-même une interface simple, c'est une technique dont vous n'avez pas à vous soucier. Veuillez ne voir que ceux qui en ont besoin.
Lorsque vous créez votre propre interface, supposons que vous définissiez plusieurs méthodes pour cette interface. Par exemple, supposons que vous définissiez la méthode A et la méthode B. Normalement, si vous savez que la méthode A appelle la méthode B, il vous est plus facile d'implémenter la logique typique de la méthode A dans votre interface. Pour les utilisateurs, c'est plus facile s'il y a peu de pièces qu'ils implémentent.
Il existe les deux modèles suivants pour y parvenir.
Assistance: Déclarez la méthode d'interface par défaut et implémentez la logique dans l'interface. Dans l'exemple précédent, ajoutez default à la partie déclaration de la méthode A pour implémenter la logique. la valeur par défaut est disponible à partir de Java 8. Si c'est tout ce dont vous avez besoin, vous bénéficierez de tous les avantages de l'interface ci-dessus, alors considérez d'abord cette option au lieu de l'implémentation squelette ci-dessous.
Implémentation squelettique: une interface avec assistance d'implémentation est implémentée dans une classe abstraite. Il existe certaines restrictions, telles que l'impossibilité d'implémenter des méthodes de la classe Object telles que equals dans l'interface par défaut. Vous pouvez couvrir (implémenter) ces parties avec une classe abstraite. Un exemple concret de ce modèle est AbstractList. De plus, puisqu'il est supposé qu'il sera hérité, écrivons fermement Javadoc conformément au point 19.
Point 21 Concevoir l'interface du futur
Une fois l'interface publiée, c'est la dernière. Ce n'est pas si facile de changer. Vérifions-le soigneusement avant de publier.
Si vous ajoutez une méthode à une interface ultérieurement, la classe qui implémente cette interface obtiendra une erreur de compilation. Vous pouvez utiliser default comme "astuces" pour éviter ce problème, mais c'est NG dans les points suivants. Il est important de ne pas compter sur la valeur par défaut, mais de concevoir fermement.
la valeur par défaut n'existe pas à cette fin.
Si la méthode par défaut appelle une méthode existante de l'interface, elle peut casser l'état interne de la classe qui a implémenté l'interface. On a l'impression que le site est confus parce que des règles déraisonnables sont imposées unilatéralement sans comprendre la situation sur le site.
Rubrique 22 Utiliser l'interface uniquement pour définir le type
Il existe une méthode pour définir une constante dans une interface et implémenter cette interface afin que la classe puisse utiliser la constante. C'est NG. Le JDK a en fait une telle interface, mais vous ne devriez pas la copier.
En effet, la classe utilise les constantes d'autres composants, ce qui est un détail d'implémentation. Implémenter une interface signifie faire de cette partie une API publique. Les détails de mise en œuvre ne doivent pas être des API publiques. En premier lieu, exposer des constantes est hors de question car loin de l'essence de l'interface.
Si vous souhaitez fournir la constante à l'extérieur, effectuez l'une des opérations suivantes.
Si une constante est fortement liée à une classe particulière, ajoutez la constante à l'intérieur de cette classe. Par exemple, Integer.MAX_VALUE.
Si une constante est considérée comme membre d'un type d'énumération, définissez-la comme un type d'énumération.
Dans les autres cas, définissez-le dans une classe utilitaire qui ne peut pas être instanciée.
//Classe d'utilité non immuable
public class PhysicalConstatns(){
private PhysicalConstants() {} //Empêcher l'instanciation
public static final double AVOGADROS_NUMBER = 6.022_140_857e23;
}
En important statiquement la classe constante, l'utilisateur n'a pas à écrire le nom de la classe à chaque fois que la constante est utilisée. Cela est expliqué dans le livre, mais quand vous y reviendrez plus tard, on vous demandera "Où est définie cette constante?", Ce qui peut être difficile à lire. Tenez compte de l'équilibre lorsque vous décidez si une importation statique est appropriée.
Élément 23 Sélectionnez une hiérarchie de classes plutôt qu'une classe balisée
Supposons qu'il existe une classe dans une classe qui exprime s'il s'agit d'un cercle ou d'un rectangle. Certains d'entre eux ont des constructeurs, des champs et des méthodes qui sont utilisés pour les cercles, et certains sont également utilisés pour les rectangles.
Les classes qui tentent d'exprimer plusieurs concepts dans une classe de cette manière sont appelées «classes étiquetées» dans les livres.
Si vous souhaitez exprimer plusieurs concepts, divisez les classes avec obéissance. Plus précisément, utilisons l'héritage / sous-typage pour les organiser dans une structure hiérarchique. C'est naturel.
Élément 24 Sélectionnez une classe de membre statique sur une classe de membre non statique
Parfois, vous souhaitez déclarer une autre classe dans une classe. Dans ce contexte, la première classe est appelée "classe englobante" et la seconde classe est appelée "classe imbriquée".
Il existe plusieurs façons d'implémenter des classes imbriquées. Plus précisément, il y a les quatre suivants.
classe de membre statique
Classe de membre non statique
Classe anonyme
Classe locale (une classe déclarée avec un nom dans une méthode)
Utilisons-les correctement. Compte tenu du flux suivant, il ne devrait y avoir aucune erreur.
Examen étape 0
La "classe imbriquée" que j'essaie de créer est-elle susceptible d'être utilisée par d'autres classes, indépendamment de la classe englobante?
Si OUI, il ne doit pas être créé en tant que classe imbriquée en premier lieu. Il doit être créé comme une classe ordinaire, indépendante de la classe englobante.
Examen étape 1
Si la "classe imbriquée" que vous essayez de créer est la suivante, utilisez ** classe anonyme **.
Utilisé uniquement dans certaines méthodes de la classe englobante.
La partie déclaration de la classe imbriquée est courte. S'il est trop long, les méthodes de la classe englobante seront difficiles à lire.
** Uniquement en un seul endroit ** Instancier.
Si une classe anonyme est déclarée dans une méthode non statique de la classe englobante, l'instance de la classe anonyme fera automatiquement référence à l'instance de la classe englobante. Dans certains cas, cela provoque une fuite de mémoire. Soyez conscient du danger avant de l'utiliser.
Cela ne se produit pas s'il est déclaré dans une méthode non statique.
Examen étape 2
Si la "classe imbriquée" que vous essayez de créer est la suivante, utilisez ** classe locale **.
Utilisé uniquement dans certaines méthodes de la classe englobante.
La partie déclaration de la classe imbriquée est courte.
Instanciez ** à plusieurs endroits dans cette méthode. S'il est utilisé à plusieurs endroits, il doit être déclaré avec un nom.
Examen étape 3
Si la "classe imbriquée" que vous essayez de créer est la suivante, utilisez une ** classe membre non statique **.
L'une des conditions suivantes s'applique.
La partie déclaration de la classe imbriquée est longue.
Utilisé par plusieurs méthodes de la classe englobante.
Besoin d'accéder à une instance de la classe englobante ** est **. Lorsqu'une classe membre non statique est instanciée, elle fait automatiquement référence à une instance de la classe englobante à partir de cette instance. Dans certains cas, cela provoque une fuite de mémoire. Soyez conscient du danger avant de l'utiliser.
Examen étape 4
Si la "classe imbriquée" que vous essayez de créer est la suivante, utilisez des ** classes membres statiques **.
L'une des conditions suivantes s'applique.
La partie déclaration de la classe imbriquée est longue.
Utilisé par plusieurs méthodes de la classe englobante.
Pas besoin d'accéder à une instance de la classe englobante **. Il n'y a pas de danger comme les classes membres non statiques, alors choisissez ceci autant que possible.
Élément 25 Limiter les fichiers source à une seule classe de premier niveau
Normalement, vous n'implémentez pas plusieurs classes de niveau supérieur dans un fichier source. Cet élément explique pourquoi c'est NG, mais vous n'avez pas besoin d'en connaître la raison car vous ne le faites pas dans la pratique en premier lieu. (fin)
Chapitre 5 Génériques
Point 26: ne pas utiliser le prototype
Un prototype est une expression qui n'implique pas de paramètre de type, tel que «List» au lieu de «List ».
Il n'est plus logique de ne pas utiliser le prototype. La raison en est que vous pouvez obtenir une ClassCastException au moment de l'exécution. Si vous l'implémentez avec des paramètres de type, vous pouvez remarquer de tels risques sous la forme d'erreurs de compilation et d'avertissements au moment de la compilation. N'utilisons pas le prototype.
Cependant, il existe une situation dans laquelle vous pouvez utiliser le prototype par inadvertance. Plus précisément, c'est comme suit.
【NG】
//Ne faites pas cela simplement parce que vous ne connaissez pas les paramètres de type de Set.
static int numElementsInCommon(Set s1, Set s2) {
int result = 0;
for (Object o1 : s1)
if(s2.contains(o1))
result++;
return result;
}
Implémentons-le comme suit.
【OK】
//Cela entraînera une erreur de compilation lors de la tentative d'ajout d'éléments à s1 ou s2.
//C'est certainement mieux que de le remarquer au moment de l'exécution. (Cependant, nul peut être ajouté)
static int numElementsInCommon(Set<?> s1, Set<?> s2) {
//Abréviation
}
Élément 27 Supprimer l'avertissement non inspecté
Lorsqu'ils sont implémentés à l'aide de génériques, les risques menant à une exception ClassCastException au moment de l'exécution peuvent être remarqués par des erreurs de compilation et des avertissements lors de la compilation.
Vous pouvez compiler les avertissements si vous les laissez seuls, mais en règle générale, répondez à tous les avertissements et corrigez-les pour qu'ils n'apparaissent pas. Ajoutez @SuppressWarning (" décoché ") pour supprimer l'avertissement uniquement s'il n'y a vraiment pas de problème. Au fait, «non coché» est un avertissement qui signifie «Je n'ai pas vérifié le type, mais est-ce que ça va?
Si vous ne supprimez pas l'avertissement lorsqu'il est vraiment correct, vous ne remarquerez pas l'avertissement qui pose vraiment problème. Ne coupons pas les coins là non plus.
Élément 28 Sélectionnez une liste dans un tableau
En raison du contexte historique, les séquences et les génériques ont des propriétés différentes. Pour cette raison, les utiliser en combinaison pose des problèmes. À moins qu'il y ait des problèmes importants avec la simplicité du code ou les performances, implémentez-les uniformément dans Generics.
Quels types de problèmes surviendront lorsqu'ils sont utilisés en combinaison?
Si vous implémentez les deux en combinaison, vous ne comprendrez pas la signification des erreurs et des avertissements émis par le compilateur, et vous serez inutilement confus. Dans certains cas, l'avertissement est supprimé sans examen attentif, ce qui entraîne une exception ClassCastException ou demande aux membres de maintenance "Pourquoi faites-vous @ SuppressWarning ici ...?" Sera embrassé et confus.
Par exemple ...
Un tableau avec des génériques comme élément, tel que List <String> [], entraînera une erreur de compilation. Si vous l'autorisez, vous pouvez obtenir une ClassCastException. Vous pourriez penser que cela n'a pas d'importance parce que vous ne le faites pas, mais ce n'est pas le cas. Par exemple, une méthode d'argument de longueur variable crée un tableau pour contenir des arguments de longueur variable. Si vous faites de l'argument un type générique, vous obtiendrez un avertissement difficile à comprendre. Si vous ne comprenez pas ce qui se passe dans les coulisses, vous ne pourrez pas le résoudre correctement. new E [] est également inutile.
Suite à son implémentation ad hoc sans comprendre la signification des erreurs de compilation et des avertissements, les classes NG suivantes sont créées.
public class Chooserr<T> {
private final T[] choiceArray;
public Chooser(Collection<T> choices) {
//les choix peuvent être du type qui contient T,
//Souvent toArray()Est-ce que T[]Il ne devrait y avoir aucune garantie qu'il reviendra.
//Vous pouvez obtenir une ClassCastException au moment de l'exécution.
//Je ne comprends pas le sens@Il est mal de supprimer les avertissements.
@SuppressWarning("unchecked")
choiceArray = (T[]) choices.toArray();
}
}
Pourquoi cela arrive-t-il?
C'est parce qu'il existe les différences suivantes entre les deux. (Bien que cela soit expliqué dans le livre, cela n'explique pas pourquoi ces différences conduisent au problème ci-dessus ... J'ajouterai un commentaire plus tard si j'ai le temps.)
Différence ①
Différence ②
Tableau
Par exempleObject[] objectArray = new Long[1];PuisLong[]EstObject[]En tant que sous-type deSera traité.. Donc, ces affectations sont autorisées. Ces propriétés sont appelées covariantes. Il est bon de se souvenir avec des nuances telles que "changer de manière flexible en fonction de l'autre partie".
TableauEst、À l'exécution自身がどんな型を格納できるのか、ということを知っています。なので、不適切な型のオブジェクトを代入しようとすると、À l'exécutionJe reçois une exception. Ces propriétés sont appelées «béton».
Génériques
Par exempleList<Object> objectList = new ArrayList<Long>();PuisArrayList<Long>EstList<Object>En tant que sous-type deNon traité.. Par conséquent, une erreur de compilation se produira. Ces propriétés sont appelées invariantes. Ce n'est pas aussi flexible que covariant.
GénériquesEstAu moment de la compilationのみ型制約を強制し、実行時にEst要素の型情報を廃棄(erase)します。こういったことを「イレイジャで実装されている」と表現します。これEst、Génériquesが導入された時に、Génériquesを利用していない既存のコードと、利用する新しいコードが共存できるようにするための措置です。これが冒頭で触れた「歴史的経緯」です。
Covariant est traduit par covariant et invariant est traduit par immuable, mais une telle traduction japonaise ne mène qu'à la confusion. Vous feriez mieux de ne pas vous en souvenir.
Point 29 Utiliser le type générique
Lorsque vous créez votre propre classe, vous devez la rendre aussi générique que possible. Ce faisant, les utilisateurs bénéficieront des avantages suivants:
Vous pouvez éliminer le risque d'une ClassCastException au moment de l'exécution.
Les utilisateurs n'ont pas à convertir la valeur de retour un par un.
Dans certains cas, il peut être préférable d'utiliser des tableaux dans votre propre classe. Par exemple, si vous souhaitez créer un type générique de base comme ArrayList, ou si vous avez une raison de performances.
Dans ces cas, vous devrez faire `@SuppressWanings (" non coché ") ʻà l'intérieur de la classe pour supprimer les avertissements lors de la compilation. Bien entendu, il va sans dire que nous devons examiner attentivement s’il est vraiment approprié de le supprimer.
Point 30 Utiliser la méthode générique
Lorsque vous créez votre propre méthode, vous devez la rendre aussi générique que possible. Ce faisant, l'utilisateur bénéficiera des mêmes avantages que l'article 29.
Tout d'abord, je vais expliquer le cas de la définition d'une méthode générique normale.
[NG] Méthode non générique
//Si les types d'objets à conserver sont différents entre s1 et s2, une ClassCastException se produira lors de l'exécution.
public static Set union(Set s1, Set s2) {
Set reslut = new HashSet(s1);
result.addAll(s2);
return result;
}
[OK] Méthode générique
//Les paramètres de type (liste des) utilisés dans la méthode doivent être déclarés entre le modificateur et le type de retour.
//Dans cet exemple, c'est immédiatement après static<E>C'est.
public static <E> Set<E> union(Set<E> s1, Set<E> s2) {
Set<E> reslut = new HashSet<>(s1);
result.addAll(s2);
return result;
}
Vient ensuite le cas de l'implémentation d'une méthode générique avancée. Je présenterai les deux techniques suivantes.
Usine Singleton générique
Limite récursive
Technique n ° 1: Usine Singleton générique
Disons que vous avez besoin d'une API qui a pour rôle de renvoyer un objet qui fonctionne avec le type que vous spécifiez. Si l'objet à créer n'a pas d'état, il est inutile de créer l'objet à chaque fois car le type spécifié par l'utilisateur est différent.
La fabrique de singleton générique est une technique qui permet à l'objet d'être singletonné (c'est-à-dire de réduire le coût de création d'une instance et la quantité de mémoire utilisée) tout en fonctionnant sur le type spécifié par l'utilisateur.
Le livre en donne un exemple avec la fonction constante. À propos, une fonction égale est une fonction qui renvoie les paramètres tels quels. À quoi cela sert-il? Vous pouvez penser qu'il apparaît comme l'une des fonctions d'activation dans le domaine de l'apprentissage automatique. Je dois spécifier quelque chose dans l'API en tant que fonction d'activation, mais je ne veux rien faire, il y a donc une utilisation telle que la spécification d'une fonction qui ne fait pratiquement rien.
//Point de cette technique ①
private static UnaryOperator<Object> IDENTITY_FN = (t) -> t;
//Point de cette technique ②
@SuppressWarnings("unchecked")
public static <T> UnaryOperator<T> identityFunction() {
return (UnaryOperator<T>) IDENTITY_FN;
}
** Points de cette technique ① **
Étant donné que les génériques sont implémentés dans la gomme, il est possible de ne renvoyer qu'une seule instance appelée IDENTITY_FN, quel que soit le type spécifié par l'utilisateur.
D'autre part, si les génériques sont incorporés, c'est-à-dire si IDENTITY_FN se souvient du paramètre de type spécifié par l'utilisateur même au moment de l'exécution, une instance appelée IDENTITY_FN ne peut pas le gérer.
Par exemple, si un utilisateur appelle identityFunction () avec une chaîne comme paramètre de type, IDENTITY_FN doit être ʻUnaryOperator `(un monde où les génériques sont incorporés). Dans ce cas, pour correspondre à la personne qui souhaite spécifier Long comme paramètre de type, la version Long de IDENTITY_FN doit être préparée. Vous aurez également besoin d'une version longue de la méthode identityFunction () qui renvoie cet objet.
** Point de cette technique ② **
Lorsque j'essaye d'implémenter une fabrique de singleton générique, je la lance sans inspection, est-ce correct? Sera averti. Mais dans de nombreux cas, ça va. Reconnaissez la raison et supprimez l'avertissement.
Tout d'abord, que signifie l'avertissement de diffusion non inspecté dans cet exemple?