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 14

Publié dans le groupe Random-FR
Feu d'artifice! Le monde bouge constamment et nous bougeons constamment. Auparavant, pour devenir développeur Java, il suffisait de connaître un peu la syntaxe Java, et le reste viendrait. Au fil du temps, le niveau de connaissances requis pour devenir développeur Java a considérablement augmenté, tout comme la concurrence, qui continue de pousser la barre inférieure des connaissances requises vers le haut. Analyse des questions et réponses des entretiens pour développeur Java.  Partie 14 - 1Si vous voulez vraiment devenir développeur, vous devez le prendre pour acquis et vous préparer minutieusement pour vous démarquer parmi les débutants comme vous. Ce que nous allons faire aujourd'hui, c'est-à-dire continuer à analyser plus de 250 questions . Dans les articles précédents, nous avons examiné toutes les questions du niveau junior et aujourd'hui, nous aborderons les questions du niveau intermédiaire. Bien que je note qu'il ne s'agit pas à 100 % de questions de niveau intermédiaire, vous pouvez en rencontrer la plupart lors d'un entretien de niveau junior, car c'est lors de tels entretiens qu'une analyse détaillée de votre base théorique a lieu, tandis que pour un étudiant du niveau intermédiaire, le les questions sont davantage axées sur l'exploration de son expérience. Analyse des questions et réponses des entretiens pour développeur Java.  Partie 14 - 2Mais sans plus tarder, commençons.

Milieu

Sont communs

1. Quels sont les avantages et les inconvénients de la POO par rapport à la programmation procédurale/fonctionnelle ?

Il y avait cette question dans l'analyse des questions pour Juinior, et donc j'y ai déjà répondu. Recherchez cette question et sa réponse dans cette partie de l'article, questions 16 et 17.

2. En quoi l’agrégation diffère-t-elle de la composition ?

En POO, il existe plusieurs types d'interactions entre objets, réunis sous le concept général de « Has-A Relationship ». Cette relation indique qu'un objet est un composant d'un autre objet. En même temps, il existe deux sous-types de cette relation : Composition - un objet crée un autre objet et la durée de vie d'un autre objet dépend de la durée de vie du créateur. Agrégation - un objet reçoit un lien (pointeur) vers un autre objet pendant le processus de construction (dans ce cas, la durée de vie de l'autre objet ne dépend pas de la durée de vie du créateur). Pour une meilleure compréhension, regardons un exemple spécifique. Nous avons une certaine classe de voiture - Car , qui à son tour a des champs internes du type - Engine et une liste de passagers - List<Passenger> , elle a également une méthode pour démarrer le mouvement - startMoving() :
public class Car {

 private Engine engine;
 private List<Passenger> passengers;

 public Car(final List<Passenger> passengers) {
   this.engine = new Engine();
   this.passengers = passengers;
 }

 public void addPassenger(Passenger passenger) {
   passengers.add(passenger);
 }

 public void removePassengerByIndex(Long index) {
   passengers.remove(index);
 }

 public void startMoving() {
   engine.start();
   System.out.println("Машина начала своё движение");
   for (Passenger passenger : passengers) {
     System.out.println("В машине есть пассажир - " + passenger.getName());
   }
 }
}
Dans ce cas, la Composition est la connexion entre Car et Engine , puisque les performances de la voiture dépendent directement de la présence de l'objet moteur, car si engine = null , alors nous recevrons une NullPointerException . À son tour, un moteur ne peut exister sans machine (pourquoi avons-nous besoin d’un moteur sans machine ?) et ne peut pas appartenir à plusieurs machines à la fois. Cela signifie que si nous supprimons l' objet Car , il n'y aura plus de références à l' objet Engine et il sera bientôt supprimé par le Garbage Collector . Comme vous pouvez le constater, cette relation est très stricte (forte). L'agrégation est la connexion entre Car et Passenger , puisque les performances de Car ne dépendent en aucun cas des objets de type Passager et de leur nombre. Ils peuvent soit quitter la voiture - supprimerPassengerByIndex(Long index) ou en entrer de nouveaux - addPassenger(Passenger passager) , malgré cela, la voiture continuera à fonctionner correctement. À leur tour, les objets Passenger peuvent exister sans objet Car . Comme vous le comprenez, il s’agit d’une connexion beaucoup plus faible que ce que l’on voit dans la composition. Analyse des questions et réponses des entretiens pour développeur Java.  Partie 14 - 3Mais ce n'est pas tout, un objet connecté par agrégation à un autre peut aussi avoir une connexion donnée avec d'autres objets au même instant. Par exemple, en tant qu'étudiant Java, vous êtes inscrit à des cours d'anglais, de POO et de logarithmes au même moment, mais en même temps, vous n'en êtes pas une partie essentielle, sans laquelle un fonctionnement normal est impossible (comme un enseignant).

3. Quels modèles GoF avez-vous utilisés dans la pratique ? Donne des exemples.

J'ai déjà répondu à cette question auparavant, je vais donc juste laisser un lien vers l'analyse , voir la première question. J'ai également trouvé un merveilleux article d'aide-mémoire sur les modèles de conception, que je recommande fortement de garder à portée de main.

4. Qu'est-ce qu'un objet proxy ? Donne des exemples

Un proxy est un modèle de conception structurelle qui vous permet de remplacer des objets de substitution spéciaux, ou en d'autres termes, des objets proxy, au lieu d'objets réels. Ces objets proxy interceptent les appels vers l'objet d'origine, permettant d'insérer une certaine logique avant ou après que l'appel soit transmis à l'objet d'origine. Analyse des questions et réponses des entretiens pour développeur Java.  Partie 14 - 4Exemples d'utilisation d'un objet proxy :
  • En tant que proxy distant - utilisé lorsque nous avons besoin d'un objet distant (un objet dans un espace d'adressage différent) qui doit être représenté localement. Dans ce cas, le proxy gérera la création de connexion, l'encodage, le décodage, etc., tandis que le client l'utilisera comme s'il s'agissait de l'objet d'origine situé dans l'espace local.

  • En tant que proxy virtuel - utilisé lorsqu'un objet gourmand en ressources est nécessaire. Dans ce cas, l'objet proxy sert comme une image d'un objet réel qui n'existe pas encore. Lorsqu'une requête réelle (appel de méthode) est envoyée à cet objet, alors seulement l'objet d'origine se charge et la méthode s'exécute. Cette approche est également appelée initialisation paresseuse ; elle peut être très pratique, car dans certaines situations, l'objet d'origine peut ne pas être utile et il n'y aura alors aucun coût pour le créer.

  • En tant que proxy de sécurité - utilisé lorsque vous devez contrôler l'accès à un objet en fonction des droits du client. Autrement dit, si un client disposant de droits d'accès manquants tente d'accéder à l'objet d'origine, le proxy l'interceptera et ne l'autorisera pas.

Regardons un exemple de proxy virtuel : Nous avons une interface de gestion :
public interface Processor {
 void process();
}
Dont la mise en œuvre consomme trop de ressources, mais en même temps elle ne peut pas être utilisée à chaque lancement de l'application :
public class HiperDifficultProcessor implements Processor {
 @Override
 public void process() {
   // некоторый сверхсложная обработка данных
 }
}
Classe proxy :
public class HiperDifficultProcessorProxy implements Processor {
private HiperDifficultProcessor processor;

 @Override
 public void process() {
   if (processor == null) {
     processor = new HiperDifficultProcessor();
   }
   processor.process();
 }
}
Exécutons-le dans main :
Processor processor = new HiperDifficultProcessorProxy();
// тут тяжеловсеного оригинального an object, ещё не сущетсвует
// но при этом есть an object, который его представляет и у которого можно вызывать его методы
processor.process(); // лишь теперь, an object оригинал был создан
Je note que de nombreux frameworks utilisent le proxy, et pour Spring , il s'agit d'un modèle clé (Spring est cousu avec lui à l'intérieur et à l'extérieur). En savoir plus sur ce modèle ici . Analyse des questions et réponses des entretiens pour développeur Java.  Partie 14 à 5

5. Quelles innovations ont été annoncées dans Java 8 ?

Les innovations apportées par Java 8 sont les suivantes :
  • Des interfaces fonctionnelles ont été ajoutées, découvrez de quel type de bête il s'agit ici .

  • Les expressions Lambda, qui sont étroitement liées aux interfaces fonctionnelles, en savoir plus sur leur utilisation ici .

  • Ajout de l'API Stream pour un traitement pratique des collections de données, en savoir plus ici .

  • Ajout de liens vers les méthodes .

  • La méthode forEach() a été ajoutée à l' interface Iterable .

  • Ajout d'une nouvelle API de date et d'heure dans le package java.time , analyse détaillée ici .

  • API simultanée améliorée .

  • En ajoutant une classe wrapper facultative , utilisée pour gérer correctement les valeurs nulles, vous pouvez trouver un excellent article sur ce sujet ici .

  • Ajout de la possibilité pour les interfaces d'utiliser des méthodes statiques et par défaut (ce qui, par essence, rapproche Java de l'héritage multiple), plus de détails ici .

  • Ajout de nouvelles méthodes à la classe Collection(removeIf(), spliterator()) .

  • Améliorations mineures de Java Core.

Analyse des questions et réponses des entretiens pour développeur Java.  Partie 14 à 6

6. Que sont une cohésion élevée et un faible couplage ? Donne des exemples.

La haute cohésion ou haute cohésion est le concept lorsqu'une certaine classe contient des éléments étroitement liés les uns aux autres et combinés dans leur but. Par exemple, toutes les méthodes de la classe User doivent représenter le comportement de l'utilisateur. Une classe a une faible cohésion si elle contient des éléments non liés. Par exemple, la classe User contenant une méthode de validation d'adresse email :
public class User {
private String name;
private String email;

 public String getName() {
   return this.name;
 }

 public void setName(final String name) {
   this.name = name;
 }

 public String getEmail() {
   return this.email;
 }

 public void setEmail(final String email) {
   this.email = email;
 }

 public boolean isValidEmail() {
   // некоторая логика валидации емейла
 }
}
La classe d'utilisateurs peut être responsable du stockage de l'adresse e-mail de l'utilisateur, mais pas de sa validation ou de l'envoi de l'e-mail. Par conséquent, afin d’obtenir une cohérence élevée, nous déplaçons la méthode de validation dans une classe utilitaire distincte :
public class EmailUtil {
 public static boolean isValidEmail(String email) {
   // некоторая логика валидации емейла
 }
}
Et nous l'utilisons selon nos besoins (par exemple, avant de sauvegarder l'utilisateur). Le faible couplage ou faible couplage est un concept qui décrit une faible interdépendance entre les modules logiciels. Essentiellement, l’interdépendance signifie que changer l’un nécessite de changer l’autre. Deux classes ont un couplage fort (ou couplage étroit) si elles sont étroitement liées. Par exemple, deux classes concrètes qui stockent des références l'une à l'autre et s'appellent mutuellement. Les classes faiblement couplées sont plus faciles à développer et à maintenir. Puisqu’ils sont indépendants les uns des autres, ils peuvent être développés et testés en parallèle. De plus, ils peuvent être modifiés et mis à jour sans s’affecter mutuellement. Regardons un exemple de classes fortement couplées. Nous avons des cours d'étudiants :
public class Student {
 private Long id;
 private String name;
 private List<Lesson> lesson;
}
Qui contient une liste de leçons :
public class Lesson {
 private Long id;
 private String name;
 private List<Student> students;
}
Chaque leçon contient un lien vers les étudiants présents. Une adhérence incroyablement forte, vous ne trouvez pas ? Comment pouvez-vous le réduire ? Tout d’abord, veillons à ce que les étudiants disposent non pas d’une liste de matières, mais d’une liste de leurs identifiants :
public class Student {
 private Long id;
 private String name;
 private List<Long> lessonIds;
}
Deuxièmement, la classe n’a pas besoin de connaître tous les élèves, supprimons donc complètement leur liste :
public class Lesson {
 private Long id;
 private String name;
}
C’est donc devenu beaucoup plus facile et la connexion est devenue beaucoup plus faible, vous ne trouvez pas ? Analyse des questions et réponses des entretiens pour développeur Java.  Partie 14 à 7

POO

7. Comment implémenter l’héritage multiple en Java ?

L'héritage multiple est une fonctionnalité du concept orienté objet dans lequel une classe peut hériter des propriétés de plusieurs classes parentes. Le problème survient lorsqu’il existe des méthodes avec la même signature dans la superclasse et dans la sous-classe. Lors de l'appel d'une méthode, le compilateur ne peut pas déterminer quelle méthode de classe doit être appelée, et même lors de l'appel de la méthode de classe qui est prioritaire. Par conséquent, Java ne prend pas en charge l’héritage multiple ! Mais il existe une sorte de faille dont nous parlerons ensuite. Comme je l'ai mentionné plus tôt, avec la sortie de Java 8, la possibilité d'avoir des méthodes par défaut a été ajoutée aux interfaces . Si la classe implémentant l'interface ne substitue pas cette méthode, alors cette implémentation par défaut sera utilisée (il n'est pas nécessaire de substituer la méthode par défaut, comme l'implémentation d'une méthode abstraite). Dans ce cas, il est possible d'implémenter différentes interfaces dans une même classe et d'utiliser leurs méthodes par défaut. Regardons un exemple. Nous avons une interface flyer, avec une méthode fly() par défaut :
public interface Flyer {
 default void fly() {
   System.out.println("Я лечу!!!");
 }
}
L'interface du walker, avec la méthode walk() par défaut :
public interface Walker {
 default void walk() {
   System.out.println("Я хожу!!!");
 }
}
L'interface du nageur, avec la méthode swim() :
public interface Swimmer {
 default void swim() {
   System.out.println("Я плыву!!!");
 }
}
Eh bien, implémentons maintenant tout cela dans une seule classe de canard :
public class Duck implements Flyer, Swimmer, Walker {
}
Et exécutons toutes les méthodes de notre canard :
Duck donald = new Duck();
donald.walk();
donald.fly();
donald.swim();
Dans la console, nous recevrons :
J'y vais!!! Je vole!!! Je nage!!!
Cela signifie que nous avons correctement représenté l’héritage multiple, même si ce n’est pas ce dont il s’agit. Analyse des questions et réponses des entretiens pour développeur Java.  Partie 14 à 8Je noterai également que si une classe implémente des interfaces avec des méthodes par défaut qui ont les mêmes noms de méthodes et les mêmes arguments dans ces méthodes, alors le compilateur commencera à se plaindre d'une incompatibilité, car il ne comprend pas quelle méthode doit réellement être utilisée. Il existe plusieurs solutions :
  • Renommez les méthodes dans les interfaces afin qu’elles diffèrent les unes des autres.
  • Remplacez ces méthodes controversées dans la classe d’implémentation.
  • Héritez d'une classe qui implémente ces méthodes controversées (votre classe utilisera alors exactement son implémentation).

8. Quelle est la différence entre les méthodes final,final et finalize() ?

final est un mot-clé utilisé pour placer une contrainte sur une classe, une méthode ou une variable, une contrainte signifiant :
  • Pour une variable - après l'initialisation initiale, la variable ne peut pas être redéfinie.
  • Pour une méthode, la méthode ne peut pas être substituée dans une sous-classe (classe successeur).
  • Pour une classe, la classe ne peut pas être héritée.
enfin est un mot-clé avant un bloc de code, utilisé lors de la gestion des exceptions, en conjonction avec un bloc try et ensemble (ou de manière interchangeable) avec un bloc catch. Le code de ce bloc est exécuté dans tous les cas, qu'une exception soit levée ou non. Dans cette partie de l'article, à la question 104, sont évoquées les situations exceptionnelles dans lesquelles ce bloc ne sera pas exécuté. finalize() est une méthode de la classe Object , appelée avant que chaque objet ne soit supprimé par le garbage collector, cette méthode sera appelée (en dernier), et est utilisée pour nettoyer les ressources occupées. Pour plus d'informations sur les méthodes de la classe Object dont chaque objet hérite, consultez la question 11 dans cette partie de l'article. Eh bien, c’est là que nous terminerons aujourd’hui. Rendez-vous dans la prochaine partie ! Analyse des questions et réponses des entretiens pour développeur Java.  Partie 14 à 9
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION