JavaRush /Blog Java /Random-FR /Analyse des questions et réponses des entretiens pour dév...

Analyse des questions et réponses des entretiens pour développeur Java. Partie 15

Publié dans le groupe Random-FR
Salut Salut ! Que doit savoir un développeur Java ? Vous pouvez discuter longtemps sur cette question, mais la vérité est que lors de l'entretien, vous serez pleinement guidé par la théorie. Même dans les domaines de connaissances que vous n'aurez pas l'occasion d'utiliser dans votre travail. Analyse des questions et réponses des entretiens pour développeur Java.  Partie 15 - 1Eh bien, si vous êtes débutant, vos connaissances théoriques seront prises très au sérieux. Puisqu'il n'y a pas encore d'expérience ni de grandes réalisations, il ne reste plus qu'à vérifier la solidité de la base de connaissances. Aujourd'hui, nous allons continuer à renforcer cette base en examinant les questions d'entretien les plus populaires auprès des développeurs Java. Volons!

Noyau Java

9. Quelle est la différence entre les liaisons statiques et dynamiques en Java ?

J'ai déjà répondu à cette question dans cet article à la question 18 sur le polymorphisme statique et dynamique, je vous conseille de le lire.

10. Est-il possible d'utiliser des variables privées ou protégées dans une interface ?

Non tu ne peux pas. Parce que lorsque vous déclarez une interface, le compilateur Java ajoute automatiquement les mots-clés public et abstract avant les méthodes d'interface et les mots-clés public , static et final avant les données membres. En fait, si vous ajoutez private ou protected , un conflit surviendra et le compilateur se plaindra du modificateur d'accès avec le message : "Modificateur '<selected modifier>' non autorisé ici." Pourquoi le compilateur ajoute-t-il public , static et final variables dans l'interface ? Voyons cela :
  • public - l'interface permet au client d'interagir avec l'objet. Si les variables n’étaient pas publiques, les clients n’y auraient pas accès.
  • statique - les interfaces ne peuvent pas être créées (ou plutôt leurs objets), donc la variable est statique.
  • final - puisque l'interface est utilisée pour atteindre 100 % d'abstraction, la variable a sa forme finale (et ne sera pas modifiée).

11. Qu'est-ce que Classloader et à quoi sert-il ?

Classloader - ou Class Loader - permet le chargement des classes Java. Plus précisément, le chargement est assuré par ses descendants - des chargeurs de classes spécifiques, car ClassLoader lui-même est abstrait. Chaque fois qu'un fichier .class est chargé, par exemple après avoir appelé un constructeur ou une méthode statique de la classe correspondante, cette action est effectuée par l'un des descendants de la classe ClassLoader . Il existe trois types d'héritiers :
  1. Bootstrap ClassLoader est un chargeur de base, implémenté au niveau JVM et n'a aucun retour de l'environnement d'exécution, puisqu'il fait partie du noyau JVM et écrit en code natif. Ce chargeur sert de parent à toutes les autres instances de ClassLoader.

    Principalement responsable du chargement des classes internes du JDK, généralement rt.jar et d'autres bibliothèques principales situées dans le répertoire $JAVA_HOME/jre/lib . Différentes plates-formes peuvent avoir différentes implémentations de ce chargeur de classe.

  2. Extension Classloader est un chargeur d'extension, un descendant de la classe de chargeur de base. S'occupe du chargement de l'extension des classes de base Java standard. Chargé depuis le répertoire des extensions du JDK, généralement $JAVA_HOME/lib/ext ou tout autre répertoire mentionné dans la propriété système java.ext.dirs (cette option peut être utilisée pour contrôler le chargement des extensions).

  3. System ClassLoader est un chargeur système implémenté au niveau JRE qui se charge de charger toutes les classes au niveau de l'application dans la JVM. Il charge les fichiers trouvés dans la variable d'environnement de classe -classpath ou l'option de ligne de commande -cp .

Analyse des questions et réponses des entretiens pour développeur Java.  Partie 15 - 2Les chargeurs de classes font partie du runtime Java. Au moment où la JVM demande une classe, le chargeur de classe essaie de trouver la classe et de charger la définition de classe dans le runtime en utilisant le nom complet de la classe. La méthode java.lang.ClassLoader.loadClass() est responsable du chargement de la définition de classe au moment de l'exécution. Il essaie de charger une classe en fonction de son nom complet. Si la classe n'a pas encore été chargée, elle délègue la requête au chargeur de classe parent. Ce processus se produit de manière récursive et ressemble à ceci :
  1. System Classloader essaie de trouver la classe dans son cache.

    • 1.1. Si la classe est trouvée, le chargement est terminé avec succès.

    • 1.2. Si la classe n’est pas trouvée, le chargement est délégué au Extension Classloader.

  2. Extension Classloader essaie de trouver la classe dans son propre cache.

    • 2.1. Si la classe est trouvée, elle se termine avec succès.

    • 2.2. Si la classe n'est pas trouvée, le chargement est délégué au chargeur de classe Bootstrap.

  3. Bootstrap Classloader essaie de trouver la classe dans son propre cache.

    • 3.1. Si la classe est trouvée, le chargement est terminé avec succès.

    • 3.2. Si la classe n'est pas trouvée, le chargeur de classe Bootstrap sous-jacent tentera de la charger.

  4. En cas de chargement :

    • 4.1. Réussi : le chargement de la classe est terminé.

    • 4.2. En cas d'échec, le contrôle est transféré au Extension Classloader.

  5. 5. Extension Classloader essaie de charger la classe, et en cas de chargement :

    • 5.1. Réussi : le chargement de la classe est terminé.

    • 5.2. En cas d'échec, le contrôle est transféré à System Classloader.

  6. 6. System Classloader essaie de charger la classe, et en cas de chargement :

    • 6.1. Réussi : le chargement de la classe est terminé.

    • 6.2. Échec : une exception est générée : ClassNotFoundException.

Le sujet des chargeurs de classes est vaste et ne doit pas être négligé. Pour en connaître plus en détail, je vous conseille de lire cet article , et nous ne nous attarderons pas et passerons à autre chose.

12. Que sont les zones de données d'exécution ?

Données d'exécution Ares - Zones de données d'exécution JVM. La JVM définit certaines zones de données d'exécution nécessaires lors de l'exécution du programme. Certains d'entre eux sont créés au démarrage de la JVM. D'autres sont locaux au thread et sont créés uniquement lorsque le thread est créé (et détruits lorsque le thread est détruit). Les zones de données d'exécution JVM ressemblent à ceci : Analyse des questions et réponses des entretiens pour développeur Java.  Partie 15 - 3
  • PC Register est local à chaque thread et contient l'adresse de l'instruction JVM que le thread est en cours d'exécution.

  • JVM Stack est une zone de mémoire utilisée comme stockage pour les variables locales et les résultats temporaires. Chaque thread possède sa propre pile distincte : dès que le thread se termine, cette pile est également détruite. Il convient de noter que l'avantage de la pile par rapport au tas réside dans les performances, tandis que le tas a certainement un avantage en termes d'évolutivité du stockage.

  • Pile de méthodes natives : une zone de données par thread qui stocke des éléments de données, similaires à la pile JVM, pour exécuter des méthodes natives (non Java).

  • Heap - utilisé par tous les threads comme stockage contenant des objets, des métadonnées de classe, des tableaux, etc., créés au moment de l'exécution. Cette zone est créée au démarrage de la JVM et est détruite à son arrêt.

  • Zone de méthode - Cette zone d'exécution est commune à tous les threads et est créée au démarrage de la JVM. Il stocke les structures pour chaque classe, telles que le pool de constantes d'exécution, le code des constructeurs et des méthodes, les données de méthode, etc.

13. Qu'est-ce qu'un objet immuable ?

Dans cette partie de l'article, dans les questions 14 et 15, il y a déjà une réponse à cette question, alors jetez-y un œil sans perdre votre temps.

14. Quelle est la particularité de la classe String ?

Plus tôt dans l'analyse, nous avons parlé à plusieurs reprises de certaines fonctionnalités de String (il y avait une section distincte à cet effet). Résumons maintenant les fonctionnalités de String :
  1. C'est l'objet le plus populaire en Java et il est utilisé à diverses fins. En termes de fréquence d'utilisation, il n'est même pas inférieur aux types primitifs.

  2. Un objet de cette classe peut être créé sans utiliser le mot-clé new - directement via des guillemets String str = « string » ; .

  3. String est une classe immuable : lors de la création d'un objet de cette classe, ses données ne peuvent pas être modifiées (lorsque vous ajoutez + « une autre chaîne » à une certaine chaîne, vous obtiendrez ainsi une nouvelle et troisième chaîne). L’immuabilité de la classe String la rend thread-safe.

  4. La classe String est finalisée (a le final modificateur ), elle ne peut donc pas être héritée.

  5. String possède son propre pool de chaînes, une zone de mémoire dans le tas qui met en cache les valeurs de chaîne qu'elle crée. Dans cette partie de la série , à la question 62, j'ai décrit le pool de chaînes.

  6. Java a des analogues de String , également conçus pour fonctionner avec des chaînes - StringBuilder et StringBuffer , mais à la différence qu'ils sont mutables. Vous pouvez en savoir plus à leur sujet dans cet article .

Analyse des questions et réponses des entretiens pour développeur Java.  Partie 15 - 4

15. Qu'est-ce que la covariance de type ?

Pour comprendre la covariance, nous examinerons un exemple. Disons que nous avons une classe d'animaux :

public class Animal {
 void voice() {
   System.out.println("*тишина*");
 }
}
Et une classe Dog qui l'étend :

public class Dog extends Animal {
 
 @Override
 public void voice() {
   System.out.println("Гав, гав, гав!!!");
 }
}
On s'en souvient, on peut facilement attribuer des objets de type héritier au type parent :

Animal animal = new Dog();
Ce ne sera rien d'autre que du polymorphisme. Pratique et flexible, n'est-ce pas ? Et la liste des animaux ? Peut-on donner à une liste avec un Animal générique une liste avec des objets Dog ?

List<Dog> dogs = new ArrayList<>();
List<Animal> animals = dogs;
Dans ce cas, la ligne d'affectation de la liste des chiens à la liste des animaux sera soulignée en rouge, soit le compilateur ne transmettra pas ce code. Malgré le fait que cette affectation semble assez logique (après tout, on peut affecter un objet Dog à une variable de type Animal ), cela ne peut pas être fait. En effet, si cela était autorisé, nous pourrions mettre un objet Animal dans une liste qui était initialement prévue pour être un Dog , tout en pensant que nous n'avions que des Dogs dans la liste . Et puis, par exemple, nous utiliserons la méthode get() pour prendre un objet de cette liste de chiens , en pensant que c'est un chien, et appelerons une méthode de l' objet Dog dessus, qu'Animal n'a pas . Et comme vous le comprenez, c'est impossible - une erreur se produira. Mais heureusement, le compilateur ne manque pas cette erreur logique en attribuant une liste de descendants à une liste de parents (et vice versa). En Java, vous ne pouvez attribuer des objets de liste qu'à des variables de liste avec des génériques correspondants. C'est ce qu'on appelle l'invariation. S’ils pouvaient faire cela, cela s’appellerait et s’appelle covariance. Autrement dit, la covariance est si nous pouvions définir un objet de type ArrayList<Dog> sur une variable de type List<Animal> . Il s'avère que la covariance n'est pas prise en charge en Java ? Peu importe comment c'est ! Mais cela se fait d’une manière particulière. A quoi sert le design ? étend Animal . Il est placé avec un générique de la variable à laquelle on veut définir l'objet liste, avec un générique du descendant. Cette construction générique signifie que tout type descendant du type Animal fera l'affaire (et le type Animal relève également de cette généralisation). À son tour, Animal peut être non seulement une classe, mais aussi une interface (ne vous laissez pas tromper par le mot-clé extends ). Nous pouvons faire notre mission précédente comme ceci : Analyse des questions et réponses des entretiens pour développeur Java.  Partie 15 - 5

List<Dog> dogs = new ArrayList<>();
List<? extends Animal> animals = dogs;
En conséquence, vous verrez dans l’EDI que le compilateur ne se plaindra pas de cette construction. Vérifions la fonctionnalité de cette conception. Disons que nous avons une méthode qui fait que tous les animaux qui lui sont transmis émettent des sons :

public static void animalsVoice(List<? extends Animal> animals) {
 for (Animal animal : animals) {
   animal.voice();
 }
}
Donnons-lui une liste de chiens :

List<Dog> dogs = new ArrayList<>();
dogs.add(new Dog());
dogs.add(new Dog());
dogs.add(new Dog());
animalsVoice(dogs);
Dans la console, nous verrons le résultat suivant :
Ouaf ouaf ouaf !!! Ouaf ouaf ouaf !!! Ouaf ouaf ouaf !!!
Cela signifie que cette approche de la covariance fonctionne avec succès. Permettez-moi de noter que ce générique est inclus dans la liste ? extends Animal nous ne pouvons pas insérer de nouvelles données d'aucun type : ni le type Dog , ni même le type Animal :

List<Dog> dogs = new ArrayList<>();
List<? extends Animal> animals = dogs;
animals.add(new Dog());
dogs.add(new Animal());
En fait, dans les deux dernières lignes, le compilateur mettra en évidence l’insertion d’objets en rouge. Cela est dû au fait que nous ne pouvons pas être sûrs à cent pour cent de la liste d'objets de quel type qui sera attribuée à la liste avec les données par le <? étend Animal> . Analyse des questions et réponses des entretiens pour développeur Java.  Partie 15 - 6Je voudrais également parler de contravariance , car généralement ce concept va toujours de pair avec la covariance, et en règle générale, on les interroge ensemble. Ce concept est quelque peu à l’opposé de la covariance, puisque cette construction utilise le type héritier. Disons que nous voulons une liste à laquelle peut être attribuée une liste d'objets de type qui ne sont pas des ancêtres de l' objet Dog . Cependant, nous ne savons pas à l’avance de quels types spécifiques il s’agira. Dans ce cas, une construction de la forme ? super Chien , pour lequel tous les types conviennent - les géniteurs de la classe Chien :

List<Animal> animals = new ArrayList<>();
List<? super Dog> dogs = animals;
dogs.add(new Dog());
dogs.add(new Dog());
Nous pouvons ajouter en toute sécurité des objets de type Dog à la liste avec un tel générique , car dans tous les cas, il possède toutes les méthodes implémentées de n'importe lequel de ses ancêtres. Mais nous ne pourrons pas ajouter d'objet de type Animal , puisqu'il n'y a aucune certitude qu'il y aura des objets de ce type à l'intérieur, et non, par exemple, Dog . Après tout, on peut demander à un élément de cette liste une méthode de la classe Dog , qu'Animal n'aura pas . Dans ce cas, une erreur de compilation se produira. Aussi, si nous voulions implémenter la méthode précédente, mais avec ce générique :

public static void animalsVoice(List<? super Dog> dogs) {
 for (Dog dog : dogs) {
   dog.voice();
 }
}
nous obtiendrions une erreur de compilation dans la boucle for , car nous ne pouvons pas être sûrs que la liste renvoyée contient des objets de type Dog et sommes libres d'utiliser ses méthodes. Si nous appelons la méthode dogs.get(0) sur cette liste . - nous obtiendrons un objet de type Object . Autrement dit, pour que la méthode animalsVoice() fonctionne , nous devons au moins ajouter de petites manipulations pour affiner les données de type :

public static void animalsVoice(List<? super Dog> dogs) {
 for (Object obj : dogs) {
   if (obj instanceof Dog) {
     Dog dog = (Dog) obj;
     dog.voice();
   }
 }
}
Analyse des questions et réponses des entretiens pour développeur Java.  Partie 15 à 7

16. Comment y a-t-il des méthodes dans la classe Object ?

Dans cette partie de la série, au paragraphe 11, j'ai déjà répondu à cette question, je vous conseille donc fortement de la lire si vous ne l'avez pas encore fait. C'est là que nous terminerons pour aujourd'hui. Rendez-vous dans la prochaine partie ! Analyse des questions et réponses des entretiens pour développeur Java.  Partie 15 à 8
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION