CodeGym /Blog Java /Random-FR /Explorer les questions et réponses d'un entretien d'embau...
John Squirrels
Niveau 41
San Francisco

Explorer les questions et réponses d'un entretien d'embauche pour un poste de développeur Java. Partie 10

Publié dans le groupe Random-FR
Salut! Combien d’heures faut-il pour devenir maître dans quelque chose ? J'ai souvent entendu quelque chose comme : « Pour devenir maître dans quelque chose, il faut y consacrer 10 000 heures ». C'est un chiffre intimidant, n'est-ce pas ? Explorer les questions et réponses d'un entretien d'embauche pour un poste de développeur Java.  Partie 10 - 1Pourtant, je me demande si c'est vrai. Et j'essaie constamment de comprendre combien d'heures j'ai déjà investies dans la maîtrise de l'art de la programmation. Et lorsque je franchirai cette ligne spéciale des 10 000 heures et que je deviendrai un maître, ressentirai-je la différence ? Ou ai-je déjà franchi cette ligne il y a longtemps sans m’en rendre compte ? Quoi qu’il en soit, vous n’avez pas besoin d’investir autant de temps pour devenir programmeur. L’important est d’utiliser votre temps à bon escient. Votre objectif principal est d’obtenir un entretien. Et lors des entretiens, les futurs développeurs de logiciels sont d'abord interrogés sur la théorie, ce qui doit donc être un point fort. En fait, lorsque vous vous préparez à un entretien, votre travail consiste à découvrir toutes les lacunes dans les connaissances de la théorie de base de Java, puis à les combler. Aujourd'hui, je suis là pour vous aider à le faire, puisque nous poursuivrons aujourd'hui notre examen des questions d'entretien les plus populaires. Eh bien, continuons !

89. En quoi une ArrayList est-elle différente d'une LinkedList ?

C'est l'une des questions les plus populaires, avec celle sur la structure interne d'un HashMap . Aucun entretien n'est complet sans cela, votre réponse devrait donc facilement sortir de votre langue. En plus de l'évidence (ils portent des noms différents), ils diffèrent par leur structure interne. Plus tôt, nous avons discuté de la structure interne d' ArrayList et de LinkedList , je ne vais donc pas entrer dans les détails de leur implémentation. Je rappelle juste qu'ArrayList est implémenté à l'aide d'un tableau interne dont la taille augmente dynamiquement selon cette formule :

<size of the current array> * 3 / 2 + 1
De plus, l'implémentation d'une LinkedList utilise une liste interne doublement chaînée, c'est-à-dire que chaque élément a une référence aux éléments précédent et suivant, à l'exception des éléments situés au début et à la fin de la liste. Les enquêteurs adorent poser cette question comme celle-ci : « Quel est le meilleur, ArrayList ou LinkedList ? » en espérant t'attraper. Après tout, si vous dites que l’un ou l’autre est meilleur, alors vous avez donné la mauvaise réponse. Explorer les questions et réponses d'un entretien d'embauche pour un poste de développeur Java.  Partie 10 - 2Au lieu de cela, vous devez clarifier la situation spécifique dont vous parlez : accéder aux éléments par index ou insérer au milieu de la liste. Ensuite, en fonction de leur réponse, vous pourrez expliquer laquelle est la meilleure. J'ai déjà décrit comment ArrayList et LinkedList fonctionnent dans chaque situation. Résumons cela en les mettant dans une rangée pour comparaison : Ajouter un élément (ajouter)
  1. Si aucun index n'est spécifié, un nouvel élément sera automatiquement ajouté à la fin pour les deux types de listes. Dans une LinkedList , le nouvel élément deviendra la nouvelle queue (seule une paire de références sera réécrite, donc la complexité algorithmique est O(1) ).

    La méthode add ajoute un élément à la dernière cellule vide du tableau ( O(1) ).

  2. Ajouter un élément par index signifie généralement l'insérer quelque part au milieu de la liste. Dans une LinkedList , la méthode recherchera d'abord l'emplacement souhaité en itérant sur les éléments de la queue et de la tête ( O(n/2) ) et insérera ensuite la valeur en écrasant les références des éléments des deux côtés de l'endroit où se trouve le un nouvel élément est inséré ( O(1) ). La complexité algorithmique globale de cette opération sera O(n/2) .

    Dans la même situation (ajout par index), une ArrayList trouve l'emplacement souhaité ( O(1) ) puis décale tous les éléments situés à droite (y compris l'élément déjà stocké à l'index spécifié) vers la droite d'un (ce qui peut nécessiter la création d'un nouveau tableau interne et la copie d'éléments dessus) ( O(n/2) ). La complexité globale est O(n/2) .

  3. Ajouter un élément au début d'une LinkedList est similaire à ajouter un élément à la fin : le nouvel élément devient la nouvelle tête ( O(1) ). Mais pour un ArrayList, cette opération nécessite de déplacer tous les éléments vers la droite ( O(n) ).

L'essentiel est que pour une LinkedList, la complexité algorithmique variera de O(1) à O(n/2) . Une autre observation est que plus l’insertion est proche de la fin ou du début de la liste, plus elle est rapide. Pour ArrayList , la complexité algorithmique va de O(1) à O(n) , et plus l'insertion est proche de la fin de la liste, plus elle est rapide. Définition d'un élément (set) Cette opération écrit un élément à la position spécifiée dans la liste, écrasant tout élément existant. Dans une LinkedList , cette opération est similaire à l'ajout, puisque le plus grand défi ici est de trouver l'emplacement de l'élément. L'élément existant est écrasé en mettant à jour une paire de références, nous avons donc encore une fois une complexité algorithmique qui varie de O(1) à O(n/2) , en fonction de la distance de la position souhaitée par rapport à la fin ou au début de la liste. Mais pour un ArrayList , cette opération trouve la cellule souhaitée par index et y écrit le nouvel élément. Comme l'opération set, la recherche par index a une complexité algorithmique de O(1) . Obtenir un élément par index (get) Obtenir un élément à partir d'une LinkedList suit le même principe de recherche que celui utilisé dans d'autres opérations. La complexité dépend de la distance à la fin ou au début, c'est-à-dire qu'elle varie de O(1) à O(n/2) . Comme indiqué précédemment, pour un ArrayList , trouver un élément par index dans le tableau interne a une complexité de O(1) . Supprimer un élément par index (remove) Pour LinkedList , le même principe s'applique encore une fois. Tout d'abord, l'élément est localisé, puis les références sont réécrites, les voisins de l'élément supprimé se réfèrent désormais les uns aux autres, éliminant les références à l'élément supprimé, qui seront ensuite nettoyées par le garbage collector. En d’autres termes, la complexité algorithmique est toujours la même : elle varie de O(1) à O(n/2) . Pour ArrayList , cette opération s'apparente davantage à l'ajout d'un nouvel élément (add). Tout d'abord, la méthode trouve l'élément souhaité ( O(1) ), le supprime, puis tous les éléments situés à droite sont décalés d'un pas vers la gauche afin de combler l'espace créé par la suppression. La suppression d'un élément a la même complexité algorithmique que l'opération d'ajout - de O(1) à O(n). Plus l’élément supprimé est proche de la fin de la liste, plus la complexité algorithmique de cette opération est faible. Et maintenant, nous avons couvert toutes les opérations principales. Permettez-moi de vous rappeler que lorsque vous comparez ces deux types de listes, vous devez clarifier la situation spécifique dans laquelle elles sont utilisées. Ce n'est qu'alors que vous pourrez répondre sans équivoque à la question de l'intervieweur.

90. En quoi une ArrayList est-elle différente d'un HashSet ?

Si nous pouvions comparer ArrayList et LinkedList opération par opération pour déterminer laquelle est la meilleure, nous ne trouverions pas si facile de faire une telle comparaison entre ArrayList et HashSet , car ce sont des collections complètement différentes. Vous pouvez comparer un dessert avec un autre, mais comparer un dessert et un plat salé est un défi : ils sont terriblement différents. Je vais néanmoins essayer de souligner certaines des différences entre eux :
  • ArrayList implémente l' interface List tandis que HashSet implémente l' interface Set .

  • ArrayList vous permet d'accéder à un élément par index : l' opération get a une complexité algorithmique O(1) , mais HashSet vous permet uniquement d'accéder à un élément souhaité par itération, ce qui donne une complexité algorithmique allant de O(1) à O(n) .

  • ArrayList autorise les éléments en double. Dans un HashSet , tous les éléments sont uniques : toute tentative d'ajout d'un élément déjà présent dans un HashSet échouera (les doublons sont vérifiés par hashcode, d'où le nom de cette collection).

  • ArrayList est implémenté à l'aide d'un tableau interne, mais HashSet est implémenté à l'aide d'un HashMap interne .

  • ArrayList conserve l'ordre d'insertion des éléments, mais HashSet est un ensemble non ordonné et ne conserve pas l'ordre des éléments.

  • ArrayList autorise n'importe quel nombre de valeurs nulles, mais vous ne pouvez ajouter qu'une seule valeur nulle à un HashSet (après tout, les éléments doivent être uniques).

91. Pourquoi Java a-t-il autant d'implémentations de tableaux dynamiques différentes ?

Il s'agit plutôt d'une question philosophique. On pourrait également se demander pourquoi inventent-ils autant de technologies nouvelles et variées ? Pour plus de commodité. Et la même chose est vraie pour un grand nombre d’implémentations de tableaux dynamiques. Aucune d’entre elles ne peut être qualifiée de mise en œuvre la meilleure ou idéale. Chacune a ses avantages dans des situations spécifiques. Notre métier est de connaître leurs différences et leurs forces/faiblesses afin de pouvoir utiliser la collection la plus adaptée à chaque situation donnée.

92. Pourquoi Java a-t-il autant d'implémentations différentes de stockage clé-valeur ?

Ici, la situation est la même que pour les implémentations de tableaux dynamiques. Il n’y en a certainement aucun qui soit universellement meilleur que les autres : chacun a des forces et des faiblesses. Et nous devons bien entendu tirer le meilleur parti de leurs atouts. Exemple : le package concurrent, qui possède de nombreuses classes multithread, possède ses propres collections concurrentes . La classe ConcurrentHashMap présente un avantage par rapport à la classe HashMap standard en termes de sécurité lorsque vous travaillez avec des données dans un environnement multithread, mais cela se fait au prix de performances plus lentes. Et les implémentations qui ne constituent pas le meilleur choix dans n’importe quelle situation cessent progressivement d’être utilisées. Par exemple : Hashtable , qui était initialement destiné à être un HashMap thread-safe , a été oublié et n'est plus utilisé, car ConcurrentHashMap est encore meilleur que Hashtable lorsque vous travaillez dans un environnement multithread.

93. Comment trier une collection d'éléments ?

La première chose à dire est que la classe représentant les éléments de la collection doit implémenter l' interface Comparable , qui consiste en la méthode compareTo . Ou vous avez besoin d'une classe qui implémente l' interface Comparator , y compris sa méthode de comparaison . Les deux méthodes indiquent comment comparer des objets d'un type donné. Ceci est essentiel lors du tri, car l’algorithme de tri doit comprendre quel principe utiliser pour comparer les éléments. Cela se fait principalement en implémentant Comparable directement dans la classe que vous souhaitez trier. L’utilisation de Comparator est moins courante. Supposons que vous utilisiez une classe d’une bibliothèque et qu’elle n’implémente pas Comparable , mais que vous deviez trier une collection de ses objets. Puisque vous ne pouvez pas modifier le code de cette classe (sauf en l'étendant), vous pouvez écrire une implémentation de Comparator qui indique comment comparer les objets de la classe. Et encore un exemple. Si vous devez trier des objets du même type de différentes manières, vous pouvez écrire plusieurs implémentations de Comparator à utiliser dans différentes situations. En règle générale, de nombreuses classes prêtes à l'emploi, par exemple String , implémentent déjà l' interface Comparable . Cela signifie que vous n'avez pas à vous soucier de la manière de comparer ces classes. Vous pouvez simplement continuer et les utiliser. La première et la plus évidente consiste à utiliser la classe TreeSet ou TreeMap . Ces classes stockent les éléments dans un ordre trié en fonction du comparateur implémenté par les éléments de classe. N'oubliez pas que TreeMap trie les clés, pas les valeurs. Si vous utilisez Comparator au lieu de Comparable , vous devez alors transmettre un objet Comparator au constructeur de la collection lorsque vous la créez :

TreeSet treeSet = new TreeSet(customComparator);
Mais que se passe-t-il si vous avez un autre type de collection ? Comment le trier ? Dans ce cas, la deuxième méthode de la classe utilitaire Collections — la méthode sort() — convient. La méthode est statique, il vous suffit donc d'ajouter le nom de la classe puis de transmettre la liste à trier. Par exemple:

Collections.sort(someList);
Si vous utilisez une implémentation de Comparator plutôt que de Comparable , vous devez la transmettre comme deuxième argument :

Collections.sort(someList, customComparator);
Cette opération va changer l'ordre interne des éléments dans la liste passée : la liste sera triée à l'aide du comparateur. Notez que la liste transmise doit être mutable, sinon la méthode échouera et lancera une UnsupportedOperationException . Une troisième option consiste à utiliser la méthode sorted de la classe Stream , qui trie les éléments de la collection. Si nous utilisons Comparable :

someList = someList.stream().sorted().collect(Collectors.toList());
Si nous utilisons Comparator :

someList = someList.stream().sorted(customComparator).collect(Collectors.toList());
La quatrième méthode consiste à implémenter manuellement un algorithme de tri, par exemple un tri à bulles ou un tri par fusion .

Classe d'objet. est égal à() et hashCode()

94. Donnez une brève description de la classe Object en Java.

Dans la deuxième partie de la revue, nous avons déjà évoqué les méthodes de la classe Object . Ici, je vous rappelle que la classe Object est un ancêtre de chaque classe en Java. Il dispose de 11 méthodes, qui sont à leur tour héritées par toutes les classes. Explorer les questions et réponses d'un entretien d'embauche pour un poste de développeur Java.  Partie 10 - 3

95. À quoi servent equals() et hashCode() en Java ?

hashCode() est une méthode de la classe Object héritée par toutes les classes. Son travail consiste à générer un nombre qui représente un objet spécifique. Un exemple de cette méthode en action peut être trouvé dans HashMap , où elle est appelée sur des objets clés pour obtenir le hashcode local, qui déterminera dans quel compartiment (cellule du tableau interne) la paire clé-valeur sera stockée. cette méthode est généralement utilisée dans la méthode equals() comme l’un de ses principaux moyens d’identifier des objets. equals() est une méthode de la classe Object dont le travail consiste à comparer des objets et à déterminer s'ils sont égaux. Cette méthode est utilisée partout où nous devons comparer des objets, car l'opérateur de comparaison standard == ne convient pas aux objets, car il compare uniquement les références d'objets.

96. Parlez-nous du contrat entre equals() et hashCode() en Java ?

Tout d’abord, permettez-moi de dire que pour que les méthodes equals() et hashCode() fonctionnent correctement, elles doivent être correctement remplacées. Leurs nouvelles implémentations doivent suivre ces règles :
  • Les objets identiques pour lesquels égal renvoie vrai doivent avoir les mêmes codes de hachage.
  • Les objets avec les mêmes codes de hachage ne sont pas nécessairement égaux.
Cela semble maintenant être un bon endroit pour faire une pause jusqu'à la prochaine partie de l'examen !
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION