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 12

Publié dans le groupe Random-FR
membres
Salut! La connaissance est le pouvoir. Plus vous aurez de connaissances lors de votre premier entretien, plus vous vous sentirez en confiance. Explorer les questions et réponses d'un entretien d'embauche pour un poste de développeur Java.  Partie 12 - 1Si vous faites appel à un gros cerveau plein de connaissances, votre interlocuteur aura du mal à vous confondre et sera probablement agréablement surpris. Alors, sans plus tarder, nous allons continuer aujourd'hui à renforcer votre base théorique en révisant les questions destinées à un développeur Java.

103. Quelles règles s'appliquent à la vérification des exceptions lors de l'héritage ?

Si je comprends bien la question, ils posent des questions sur les règles permettant de travailler avec des exceptions lors de l'héritage. Les règles pertinentes sont les suivantes :
  • Une méthode remplacée ou implémentée dans un descendant/une implémentation ne peut pas lever d'exceptions vérifiées qui sont plus élevées dans la hiérarchie que les exceptions dans une méthode de superclasse/d'interface.
Par exemple, supposons que nous ayons une interface Animal avec une méthode qui lève une IOException :
public interface Animal {
   void speak() throws IOException;
}
Lors de l'implémentation de cette interface, nous ne pouvons pas exposer une exception jetable plus générale (par exemple Exception , Throwable ), mais nous pouvons remplacer l'exception existante par une sous-classe, telle que FileNotFoundException :
public class Cat implements Animal {
   @Override
   public void speak() throws FileNotFoundException {
// Some implementation
   }
}
  • La clause throws du constructeur de sous-classe doit inclure toutes les classes d'exception levées par le constructeur de superclasse appelé pour créer l'objet.
Supposons que le constructeur de la classe Animal lève de nombreuses exceptions :
public class Animal {
  public Animal() throws ArithmeticException, NullPointerException, IOException {
  }
Ensuite, un constructeur de sous-classe doit également les lancer :
public class Cat extends Animal {
   public Cat() throws ArithmeticException, NullPointerException, IOException {
       super();
   }
Ou, comme pour les méthodes, vous pouvez spécifier des exceptions différentes, plus générales. Dans notre cas, nous pouvons indiquer Exception , car elle est plus générale et est un ancêtre commun aux trois exceptions indiquées dans la superclasse :
public class Cat extends Animal {
   public Cat() throws Exception {
       super();
   }

104. Pouvez-vous écrire du code dans lequel le bloc final n'est pas exécuté ?

Tout d’abord, rappelons-nous ce qui est finalement . Plus tôt, nous avons examiné le mécanisme de capture des exceptions : un bloc try désigne l'endroit où les exceptions seront interceptées, et le ou les blocs catch sont le code qui sera invoqué lorsqu'une exception correspondante est interceptée. Un troisième bloc de code marqué par le mot-clé enfin peut remplacer ou venir après les blocs catch. L'idée derrière ce bloc est que son code est toujours exécuté indépendamment de ce qui se passe dans un bloc try ou catch (qu'il y ait ou non une exception). Les cas où ce bloc n’est pas exécuté sont rares et anormaux. L'exemple le plus simple est lorsque System.exit(0) est appelé avant le bloc final, mettant ainsi fin au programme :
try {
   throw new IOException();
} catch (IOException e) {
   System.exit(0);
} finally {
   System.out.println("This message will not be printed on the console");
}
Il existe également d'autres situations dans lesquelles le bloc final ne s'exécutera pas :
  • Par exemple, une interruption anormale d'un programme provoquée par des erreurs système critiques ou une erreur provoquant le blocage de l'application (par exemple, StackOverflowError , qui se produit lorsque la pile de l'application déborde).

  • Une autre situation est lorsqu'un thread démon entre dans un bloc try-finally , mais que le thread principal du programme se termine ensuite. Après tout, les threads démons sont destinés au travail en arrière-plan qui n'est ni hautement prioritaire ni obligatoire, de sorte que l'application n'attendra pas qu'ils se terminent.

  • L’exemple le plus insensé est une boucle sans fin à l’intérieur d’un bloc try ou catch — une fois à l’intérieur, un thread y restera bloqué pour toujours :

    try {
       while (true) {
       }
    } finally {
       System.out.println("This message will not be printed on the console");
    }
Cette question est très populaire lors des entretiens avec les développeurs juniors, c'est donc une bonne idée de se souvenir de quelques-unes de ces situations exceptionnelles. Explorer les questions et réponses d'un entretien d'embauche pour un poste de développeur Java.  Partie 12 - 2

105. Écrivez un exemple dans lequel vous gérez plusieurs exceptions dans un seul bloc catch.

1) Je ne suis pas sûr que cette question ait été posée correctement. Autant que je sache, cette question fait référence à plusieurs blocs catch et à un seul try :
try {
  throw new FileNotFoundException();
} catch (FileNotFoundException e) {
   System.out.print("Oops! There was an exception: " + e);
} catch (IOException e) {
   System.out.print("Oops! There was an exception: " + e);
} catch (Exception e) {
   System.out.print("Oops! There was an exception: " + e);
}
Si une exception est levée dans un bloc try , alors les blocs catch associés tentent de l'attraper, séquentiellement de haut en bas. Une fois que l'exception correspond à l'un des blocs catch , les blocs restants ne pourront plus l'attraper et le gérer. Tout cela signifie que les exceptions plus étroites sont disposées au-dessus des exceptions plus générales dans l'ensemble des blocs catch . Par exemple, si notre premier bloc catch intercepte la classe Exception , alors les blocs suivants n'attraperont pas les exceptions vérifiées (c'est-à-dire que les blocs restants avec des sous-classes d' Exception seront complètement inutiles). 2) Ou peut-être que la question a été posée correctement. Dans ce cas, nous pourrions gérer les exceptions comme suit :
try {
  throw new NullPointerException();
} catch (Exception e) {
   if (e instanceof FileNotFoundException) {
       // Some handling that involves a narrowing type conversion: (FileNotFoundException)e
   } else if (e instanceof ArithmeticException) {
       // Some handling that involves a narrowing type conversion: (ArithmeticException)e
   } else if(e instanceof NullPointerException) {
       // Some handling that involves a narrowing type conversion: (NullPointerException)e
   }
Après avoir utilisé catch pour intercepter une exception, nous essayons ensuite de découvrir son type spécifique en utilisant l' opérateur instanceof , qui vérifie si un objet appartient à un certain type. Cela nous permet d'effectuer en toute confiance une conversion de type restrictif sans craindre de conséquences négatives. Nous pourrions appliquer l’une ou l’autre approche dans la même situation. J'ai exprimé des doutes sur la question uniquement parce que je ne considérerais pas la deuxième option comme une bonne approche. D'après mon expérience, je ne l'ai jamais rencontré et la première approche impliquant plusieurs blocs catch est très répandue.

106. Quel opérateur vous permet de forcer la levée d'une exception ? Écrivez un exemple

Je l'ai déjà utilisé plusieurs fois dans les exemples ci-dessus, mais je vais le répéter encore une fois : le mot-clé throw . Exemple de levée manuelle d'une exception :
throw new NullPointerException();

107. La méthode main peut-elle lever une exception ? Si oui, où va-t-il ?

Tout d’abord, je tiens à souligner que la méthode principale n’est rien de plus qu’une méthode ordinaire. Oui, il est appelé par la machine virtuelle pour lancer l’exécution d’un programme, mais au-delà, il peut être appelé depuis n’importe quel autre code. Cela signifie qu'il est également soumis aux règles habituelles concernant l'indication des exceptions vérifiées après le mot-clé throws :
public static void main(String[] args) throws IOException {
En conséquence, il peut lever des exceptions. Lorsque main est appelé comme point de départ du programme (plutôt que par une autre méthode), toute exception levée sera gérée par UncaughtExceptionHandler . Chaque thread possède un de ces gestionnaires (c'est-à-dire qu'il existe un tel gestionnaire dans chaque thread). Si nécessaire, vous pouvez créer votre propre gestionnaire et le définir en appelant la méthode public static void main(String[] args) throws IOException {setDefaultUncaughtExceptionHandler sur un public static void main(String[] args) throws IOException {Thread object.

Multithreading

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

108. Quels mécanismes pour travailler dans un environnement multithread connaissez-vous ?

Les mécanismes de base du multithreading en Java sont :
  • Le mot-clé synchronisé , qui est un moyen pour un thread de verrouiller une méthode/un bloc lors de son entrée, empêchant ainsi les autres threads d'entrer.

  • Le mot-clé volatile garantit un accès cohérent à une variable accessible par différents threads. Autrement dit, lorsque ce modificateur est appliqué à une variable, toutes les opérations d'affectation et de lecture de cette variable deviennent atomiques. En d’autres termes, les threads ne copieront pas la variable dans leur mémoire locale et ne la modifieront pas. Ils changeront sa valeur d'origine.

  • Runnable — Nous pouvons implémenter cette interface (qui consiste en une seule méthode run() ) dans certaines classes :

    public class CustomRunnable implements Runnable {
       @Override
       public void run() {
           // Some logic
       }
    }

    Et une fois que nous avons créé un objet de cette classe, nous pouvons démarrer un nouveau thread en passant notre objet au constructeur Thread puis en appelant la méthode start() :

    Runnable runnable = new CustomRunnable();
    new Thread(runnable).start();

    La méthode start exécute la méthode run() implémentée sur un thread séparé.

  • Thread — Nous pouvons hériter de cette classe et remplacer sa méthode run :

    public class CustomThread extends Thread {
       @Override
       public void run() {
           // Some logic
       }
    }

    Nous pouvons démarrer un nouveau thread en créant un objet de cette classe puis en appelant la méthode start() :

    new CustomThread().start();

  • Concurrence — Il s'agit d'un ensemble d'outils permettant de travailler dans un environnement multithread.

    Cela consiste en:

    • Collections simultanées — Il s'agit d'une collection de collections explicitement créées pour travailler dans un environnement multithread.

    • Files d'attente — Files d'attente spécialisées pour un environnement multithread (bloquantes et non bloquantes).

    • Synchroniseurs — Il s'agit d'utilitaires spécialisés permettant de travailler dans un environnement multithread.

    • Exécuteurs — Mécanismes de création de pools de threads.

    • Verrous — Mécanismes de synchronisation de threads plus flexibles que les mécanismes standards (synchronisés, attendre, notifier, notifierAll).

    • Atomics — Classes optimisées pour le multithreading. Chacune de leurs opérations est atomique.

109. Parlez-nous de la synchronisation entre les threads. A quoi servent les méthodes wait(), notify(), notifyAll() et join() ?

La synchronisation entre les threads concerne le mot-clé synchronisé . Ce modificateur peut être placé soit directement sur le bloc :
synchronized (Main.class) {
   // Some logic
}
Ou directement dans la signature de la méthode :
public synchronized void move() {
   // Some logic }
Comme je l'ai dit plus tôt, synchronisé est un mécanisme permettant de verrouiller un bloc/une méthode sur d'autres threads une fois qu'un thread entre. Pensons à un bloc/méthode de code comme à une pièce. Un fil s'approche de la pièce, y entre et verrouille la porte avec sa clé. Lorsque d'autres threads s'approchent de la pièce, ils voient que la porte est verrouillée et attendent à proximité jusqu'à ce que la pièce soit disponible. Une fois que le premier fil a terminé ses activités dans la pièce, il déverrouille la porte, quitte la pièce et libère la clé. J'ai mentionné une clé à plusieurs reprises pour une raison : parce que quelque chose d'analogue existe réellement. Il s'agit d'un objet spécial qui a un état occupé/libre. Chaque objet en Java a un tel objet, donc lorsque nous utilisons le bloc synchronisé , nous devons utiliser des parenthèses pour indiquer l'objet dont le mutex sera verrouillé :
Cat cat = new Cat();
synchronized (cat) {
   // Some logic
}
On peut aussi utiliser un mutex associé à une classe, comme je l'ai fait dans le premier exemple ( Main.class ). Après tout, lorsque nous utilisons synchronisé sur une méthode, nous ne spécifions pas l'objet que nous voulons verrouiller, n'est-ce pas ? Dans ce cas, pour les méthodes non statiques, le mutex qui sera verrouillé est l' objet this , c'est à dire l'objet courant de la classe. Pour les méthodes statiques, le mutex associé à la classe actuelle ( this.getClass(); ) est verrouillé. wait() est une méthode qui libère le mutex et met le thread actuel dans l'état d'attente, comme s'il s'attachait au moniteur actuel (quelque chose comme une ancre). Pour cette raison, cette méthode ne peut être appelée qu’à partir d’un bloc ou d’une méthode synchronisée . Sinon, qu’attendrait-il et qu’est-ce qui serait publié ?). Notez également qu'il s'agit d'une méthode de la classe Object . Enfin, pas un, mais trois :
  • wait() met le thread actuel en état d'attente jusqu'à ce qu'un autre thread appelle la méthode notify() ou notifyAll() sur cet objet (nous parlerons de ces méthodes plus tard).

  • wait(long timeout) met le thread actuel dans l'état d'attente jusqu'à ce qu'un autre thread appelle la méthode notify() ou notifyAll() sur cet objet ou que l'intervalle de temps spécifié par timeout expire.

  • wait(long timeout, int nanos) est comme la méthode précédente, mais ici nanos vous permet de spécifier des nanosecondes (un délai d'attente plus précis).

  • notify() vous permet de réveiller un thread aléatoire en attente sur le bloc de synchronisation actuel. Encore une fois, cette méthode ne peut être appelée que dans un bloc ou une méthode synchronisé (après tout, dans d’autres endroits, il n’y aurait personne pour se réveiller).

  • notifyAll() réveille tous les threads en attente sur le moniteur actuel (également utilisé uniquement dans un bloc ou une méthode synchronisée ).

110. Comment arrêter un fil de discussion ?

La première chose à dire ici est que lorsque run() s'exécute jusqu'à son terme, le thread se termine automatiquement. Mais parfois, nous souhaitons tuer un thread plus tôt que prévu, avant que la méthode ne soit terminée. Alors que faisons-nous? Peut-être pouvons-nous utiliser la méthode stop() sur l' objet Thread ? Non! Cette méthode est obsolète et peut provoquer des pannes du système. Explorer les questions et réponses d'un entretien d'embauche pour un poste de développeur Java.  Partie 12 - 4Eh bien, et alors ? Il y a deux façons de procéder : Tout d'abord , utilisez son indicateur booléen interne. Regardons un exemple. Nous avons notre implémentation d'un fil de discussion qui devrait afficher une certaine phrase à l'écran jusqu'à ce que le fil s'arrête complètement :
public class CustomThread extends Thread {
private boolean isActive;

   public CustomThread() {
       this.isActive = true;
   }

   @Override
   public void run() {
       {
           while (isActive) {
               System.out.println("The thread is executing some logic...");
           }
           System.out.println("The thread stopped!");
       }
   }

   public void stopRunningThread() {
       isActive = false;
   }
}
L’appel de la méthode stopRunningThread() définit l’indicateur interne sur false, provoquant l’arrêt de la méthode run() . Appelons-le en principal :
System.out.println("Program starting...");
CustomThread thread = new CustomThread();
thread.start();
Thread.sleep(3);
// As long as our main thread is asleep, our CustomThread runs and prints its message on the console
thread.stopRunningThread();
System.out.println("Program stopping...");
En conséquence, nous verrons quelque chose comme ceci dans la console :
Démarrage du programme... Le thread exécute de la logique... Le thread exécute de la logique... Le thread exécute de la logique... Le thread exécute de la logique... Le thread exécute de la logique... Le thread exécute une logique... Arrêt du programme... Le thread s'est arrêté !
Cela signifie que notre fil de discussion a démarré, imprimé plusieurs messages sur la console, puis arrêté avec succès. Notez que le nombre de messages affichés varie d'un lancement à l'autre. Et parfois, le thread auxiliaire peut ne rien afficher du tout. Le comportement spécifique dépend de la durée pendant laquelle le thread principal dort. Plus il dort longtemps, moins il est probable que le thread auxiliaire ne parvienne à rien afficher. Avec un temps de veille de 1 ms, vous ne verrez presque jamais les messages. Mais si vous le réglez sur 20 ms, les messages s'affichent presque toujours. Lorsque le temps de sommeil est court, le thread n'a tout simplement pas le temps de démarrer et de faire son travail. Au lieu de cela, il est immédiatement arrêté. Une deuxième façon consiste à utiliser la méthode interrompue() sur l’ objet Thread . Il renvoie la valeur de l'indicateur d'interruption interne, qui est faux par défaut. Ou sa méthode interruption() , qui définit cet indicateur sur true (lorsque l'indicateur est true , le thread doit cesser de s'exécuter). Regardons un exemple :
public class CustomThread extends Thread {

   @Override
   public void run() {
       {
           while (!Thread.interrupted()) {
               System.out.println("The thread is executing some logic...");
           }
           System.out.println("The thread stopped!");
       }
   }
}
Fonctionnement principal :
System.out.println("Program starting...");
Thread thread = new CustomThread();
thread.start();
Thread.sleep(3);
thread.interrupt();
System.out.println("Program stopping...");
Le résultat de cette exécution est le même que dans le premier cas, mais j'aime mieux cette approche : nous avons écrit moins de code et utilisé davantage de fonctionnalités standard prêtes à l'emploi. Eh bien, c'est tout pour aujourd'hui !
Commentaires
  • Populaires
  • Nouveau
  • Anciennes
Tu dois être connecté(e) pour laisser un commentaire
Cette page ne comporte pas encore de commentaires