CodeGym/Java-Blog/Random-DE/Erkundung der Fragen und Antworten aus einem Vorstellungs...
John Squirrels
Level 41
San Francisco

Erkundung der Fragen und Antworten aus einem Vorstellungsgespräch für eine Stelle als Java-Entwickler. Teil 12

Veröffentlicht in der Gruppe Random-DE
Hallo! Wissen ist Macht. Je mehr Wissen Sie in Ihrem ersten Vorstellungsgespräch haben, desto sicherer werden Sie sich fühlen. Erkundung der Fragen und Antworten aus einem Vorstellungsgespräch für eine Stelle als Java-Entwickler.  Teil 12 - 1Wenn Sie ein großes Gehirn voller Wissen mitbringen, wird es Ihrem Gesprächspartner schwer fallen, Sie zu verwirren, und er wird wahrscheinlich angenehm überrascht sein. Deshalb werden wir heute ohne weitere Umschweife Ihre theoretischen Grundlagen weiter festigen, indem wir Fragen für einen Java-Entwickler durchgehen.

103. Welche Regeln gelten für die Ausnahmeprüfung bei der Vererbung?

Wenn ich die Frage richtig verstehe, geht es um Regeln für den Umgang mit Ausnahmen bei der Vererbung. Die relevanten Regeln lauten wie folgt:
  • Eine überschriebene oder implementierte Methode in einem Nachkommen/einer Implementierung kann keine geprüften Ausnahmen auslösen, die in der Hierarchie höher sind als Ausnahmen in einer Superklasse/Schnittstellenmethode.
Angenommen, wir haben eine Animal- Schnittstelle mit einer Methode, die eine IOException auslöst :

public interface Animal {
   void speak() throws IOException;
}
Bei der Implementierung dieser Schnittstelle können wir keine allgemeinere auslösbare Ausnahme (z. B. Exception , Throwable ) verfügbar machen, aber wir können die vorhandene Ausnahme durch eine Unterklasse ersetzen, z. B. FileNotFoundException :

public class Cat implements Animal {
   @Override
   public void speak() throws FileNotFoundException {
// Some implementation
   }
}
  • Die throws- Klausel des Unterklassenkonstruktors muss alle Ausnahmeklassen enthalten, die vom Superklassenkonstruktor ausgelöst werden, der zum Erstellen des Objekts aufgerufen wird.
Angenommen, der Konstruktor der Animal- Klasse wirft viele Ausnahmen aus:

 public class Animal {
   public Animal() throws ArithmeticException, NullPointerException, IOException {
   }
Dann muss ein Unterklassenkonstruktor sie auch auslösen:

public class Cat extends Animal {
   public Cat() throws ArithmeticException, NullPointerException, IOException {
       super();
   }
Oder Sie können, wie bei Methoden, andere, allgemeinere Ausnahmen angeben. In unserem Fall können wir Exception angeben , da es allgemeiner ist und ein gemeinsamer Vorfahre aller drei in der Oberklasse angegebenen Ausnahmen ist:

public class Cat extends Animal {
   public Cat() throws Exception {
       super();
   }

104. Können Sie Code schreiben, bei dem der „finally“-Block nicht ausgeführt wird?

Erinnern wir uns zunächst daran, was letztendlich ist. Zuvor haben wir den Mechanismus zum Abfangen von Ausnahmen untersucht: Ein Try- Block gibt an, wo Ausnahmen abgefangen werden, und Catch -Blöcke sind der Code, der aufgerufen wird, wenn eine entsprechende Ausnahme abgefangen wird. Ein dritter Codeblock, der durch das Schlüsselwort „final“ gekennzeichnet ist , kann die Catch-Blöcke ersetzen oder darauf folgen. Die Idee hinter diesem Block ist, dass sein Code immer ausgeführt wird, unabhängig davon, was in einem Try- oder Catch- Block passiert (unabhängig davon, ob eine Ausnahme vorliegt oder nicht). Fälle, in denen dieser Block nicht ausgeführt wird, sind selten und ungewöhnlich. Das einfachste Beispiel ist, wenn System.exit(0) vor dem final-Block aufgerufen wird und dadurch das Programm beendet wird:

try {
   throw new IOException();
} catch (IOException e) {
   System.exit(0);
} finally {
   System.out.println("This message will not be printed on the console");
}
Es gibt auch einige andere Situationen, in denen der „finally“ -Block nicht ausgeführt wird:
  • Zum Beispiel ein abnormaler Programmabbruch, der durch kritische Systemfehler verursacht wird, oder ein Fehler, der zum Absturz der Anwendung führt (zum Beispiel der StackOverflowError , der auftritt, wenn der Anwendungsstapel überläuft).

  • Eine andere Situation ist, wenn ein Daemon- Thread in einen try-finally- Block eintritt, dann aber der Haupt-Thread des Programms beendet wird. Schließlich sind Daemon-Threads für Hintergrundarbeiten gedacht, die keine hohe Priorität haben oder obligatorisch sind, sodass die Anwendung nicht auf deren Abschluss wartet.

  • Das sinnloseste Beispiel ist eine Endlosschleife innerhalb eines Try- oder Catch -Blocks – sobald ein Thread darin steckt, bleibt er dort für immer hängen:

    
    try {
       while (true) {
       }
    } finally {
       System.out.println("This message will not be printed on the console");
    }
Diese Frage ist in Interviews mit Nachwuchsentwicklern sehr beliebt, daher ist es eine gute Idee, sich an einige dieser Ausnahmesituationen zu erinnern. Erkundung der Fragen und Antworten aus einem Vorstellungsgespräch für eine Stelle als Java-Entwickler.  Teil 12 - 2

105. Schreiben Sie ein Beispiel, in dem Sie mehrere Ausnahmen in einem einzigen Catch-Block behandeln.

1) Ich bin nicht sicher, ob diese Frage richtig gestellt wurde. Soweit ich weiß, bezieht sich diese Frage auf mehrere Catch- Blöcke und einen einzelnen Versuch :

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);
}
Wenn in einem Try- Block eine Ausnahme ausgelöst wird, versuchen die zugehörigen Catch- Blöcke, sie abzufangen, der Reihe nach von oben nach unten. Sobald die Ausnahme mit einem der Catch- Blöcke übereinstimmt, können alle verbleibenden Blöcke sie nicht mehr abfangen und verarbeiten. Dies alles bedeutet, dass engere Ausnahmen in der Menge der Catch- Blöcke über allgemeineren Ausnahmen angeordnet sind . Wenn unser erster Catch- Block beispielsweise die Exception- Klasse abfängt, fangen alle nachfolgenden Blöcke keine geprüften Ausnahmen ab (das heißt, die verbleibenden Blöcke mit Unterklassen von Exception sind völlig nutzlos). 2) Oder vielleicht wurde die Frage richtig gestellt. In diesem Fall könnten wir Ausnahmen wie folgt behandeln:

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
   }
Nachdem wir mit „catch“ eine Ausnahme abgefangen haben, versuchen wir dann, ihren spezifischen Typ zu ermitteln, indem wir den Operator „ instanceof “ verwenden, der prüft, ob ein Objekt zu einem bestimmten Typ gehört. Dadurch können wir eine Konvertierung des einschränkenden Typs sicher durchführen, ohne negative Konsequenzen befürchten zu müssen. Wir könnten beide Ansätze in derselben Situation anwenden. Ich habe nur deshalb Zweifel an der Frage geäußert, weil ich die zweite Option nicht als guten Ansatz bezeichnen würde. Meiner Erfahrung nach bin ich noch nie darauf gestoßen, und der erste Ansatz mit mehreren Catch-Blöcken ist weit verbreitet.

106. Mit welchem ​​Operator können Sie das Auslösen einer Ausnahme erzwingen? Schreiben Sie ein Beispiel

Ich habe es in den obigen Beispielen bereits mehrfach verwendet, aber ich wiederhole es noch einmal: das Schlüsselwort throw . Beispiel für das manuelle Auslösen einer Ausnahme:

throw new NullPointerException();

107. Kann die Hauptmethode eine Ausnahme auslösen? Wenn ja, wohin geht es dann?

Zunächst möchte ich darauf hinweisen, dass die Hauptmethode nichts anderes als eine gewöhnliche Methode ist. Ja, es wird von der virtuellen Maschine aufgerufen, um die Ausführung eines Programms zu starten, aber darüber hinaus kann es von jedem anderen Code aus aufgerufen werden. Das bedeutet, dass auch die üblichen Regeln für die Angabe geprüfter Ausnahmen nach dem Schlüsselwort throws gelten :

public static void main(String[] args) throws IOException {
Dementsprechend kann es Ausnahmen auslösen. Wenn main als Startpunkt des Programms aufgerufen wird (und nicht durch eine andere Methode), wird jede von ihm ausgelöste Ausnahme von UncaughtExceptionHandler behandelt . Jeder Thread hat einen solchen Handler (das heißt, es gibt einen solchen Handler in jedem Thread). Bei Bedarf können Sie Ihren eigenen Handler erstellen und ihn festlegen, indem Sie die Methode public static void main(String[] args) throws IOException {setDefaultUncaughtExceptionHandler für ein öffentliches static void main(String[] args) throws IOException {Thread-Objekt aufrufen.

Multithreading

Erkundung der Fragen und Antworten aus einem Vorstellungsgespräch für eine Stelle als Java-Entwickler.  Teil 12 - 3

108. Welche Mechanismen zum Arbeiten in einer Multithread-Umgebung kennen Sie?

Die grundlegenden Mechanismen für Multithreading in Java sind:
  • Das synchronisierte Schlüsselwort, mit dem ein Thread eine Methode/einen Block beim Betreten sperren kann, um andere Threads am Betreten zu hindern.

  • Das Schlüsselwort volatile stellt einen konsistenten Zugriff auf eine Variable sicher, auf die verschiedene Threads zugreifen. Das heißt, wenn dieser Modifikator auf eine Variable angewendet wird, werden alle Vorgänge zum Zuweisen und Lesen dieser Variablen atomar. Mit anderen Worten: Die Threads kopieren die Variable nicht in ihren lokalen Speicher und ändern sie. Sie werden seinen ursprünglichen Wert ändern.

  • Runnable – Wir können diese Schnittstelle (die aus einer einzelnen run()- Methode besteht) in einer Klasse implementieren:

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

    Und sobald wir ein Objekt dieser Klasse erstellt haben, können wir einen neuen Thread starten, indem wir unser Objekt an den Thread- Konstruktor übergeben und dann die start()- Methode aufrufen :

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

    Die Startmethode führt die implementierte run()- Methode in einem separaten Thread aus.

  • Thread – Wir können diese Klasse erben und ihre Ausführungsmethode überschreiben :

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

    Wir können einen neuen Thread starten, indem wir ein Objekt dieser Klasse erstellen und dann die start()- Methode aufrufen:

    
    new CustomThread().start();

  • Parallelität – Dies ist ein Paket von Tools für die Arbeit in einer Multithread-Umgebung.

    Es besteht aus:

    • Gleichzeitige Sammlungen – Dies ist eine Sammlung von Sammlungen, die explizit für die Arbeit in einer Multithread-Umgebung erstellt wurden.

    • Warteschlangen – Spezialisierte Warteschlangen für eine Multithread-Umgebung (blockierend und nicht blockierend).

    • Synchronizer – Hierbei handelt es sich um spezielle Dienstprogramme für die Arbeit in einer Multithread-Umgebung.

    • Executors – Mechanismen zum Erstellen von Thread-Pools.

    • Sperren – Thread-Synchronisierungsmechanismen, die flexibler als die Standardmechanismen sind (synchronisiert, warten, benachrichtigen, notifyAll).

    • Atomics – Für Multithreading optimierte Klassen. Jede ihrer Operationen ist atomar.

109. Erzählen Sie uns etwas über die Synchronisierung zwischen Threads. Wozu dienen die Methoden wait(), notify(), notifyAll() und join()?

Bei der Synchronisierung zwischen Threads geht es um das synchronisierte Schlüsselwort. Dieser Modifikator kann entweder direkt auf dem Block platziert werden:

synchronized (Main.class) {
   // Some logic
}
Oder direkt in der Methodensignatur:

public synchronized void move() {
   // Some logic }
Wie ich bereits sagte, ist synchronisiert ein Mechanismus zum Sperren eines Blocks/einer Methode für andere Threads, sobald ein Thread eintritt. Stellen wir uns einen Codeblock/eine Codemethode als einen Raum vor. Irgendein Thread nähert sich dem Zimmer, betritt es und verschließt die Tür mit seinem Schlüssel. Wenn sich andere Threads dem Raum nähern, sehen sie, dass die Tür verschlossen ist und warten in der Nähe, bis der Raum frei wird. Sobald der erste Thread seine Arbeit im Raum erledigt hat, schließt er die Tür auf, verlässt den Raum und gibt den Schlüssel frei. Ich habe einen Schlüssel ein paar Mal aus gutem Grund erwähnt – weil es tatsächlich etwas Analoges gibt. Dies ist ein spezielles Objekt, das einen Besetzt/Frei-Zustand hat. Jedes Objekt in Java hat ein solches Objekt. Wenn wir also den synchronisierten Block verwenden, müssen wir Klammern verwenden, um das Objekt anzugeben, dessen Mutex gesperrt wird:

Cat cat = new Cat();
synchronized (cat) {
   // Some logic
}
Wir können auch einen Mutex verwenden, der einer Klasse zugeordnet ist, wie ich es im ersten Beispiel ( Main.class ) getan habe. Wenn wir „synchonized“ für eine Methode verwenden, geben wir doch nicht das Objekt an, das wir sperren möchten, oder? In diesem Fall ist bei nicht statischen Methoden der Mutex, der gesperrt wird, das Objekt this , also das aktuelle Objekt der Klasse. Bei statischen Methoden ist der der aktuellen Klasse zugeordnete Mutex ( this.getClass(); ) gesperrt. wait() ist eine Methode, die den Mutex freigibt und den aktuellen Thread in den Wartezustand versetzt, als würde er an den aktuellen Monitor angeschlossen (so etwas wie ein Anker). Aus diesem Grund kann diese Methode nur von einem synchronisierten Block oder einer synchronisierten Methode aufgerufen werden. Worauf würde es sonst warten und was würde veröffentlicht werden?). Beachten Sie außerdem, dass es sich hierbei um eine Methode der Object- Klasse handelt. Nun, nicht einer, sondern drei:
  • wait() versetzt den aktuellen Thread in den Wartezustand, bis ein anderer Thread die Methode notify() oder notifyAll() für dieses Objekt aufruft (wir werden später auf diese Methoden eingehen).

  • wait(long timeout) versetzt den aktuellen Thread in den Wartezustand, bis ein anderer Thread die Methode notify() oder notifyAll() für dieses Objekt aufruft oder das durch timeout angegebene Zeitintervall abläuft.

  • wait(long timeout, int nanos) ähnelt der vorherigen Methode, aber hier können Sie mit nanos Nanosekunden angeben (ein genaueres Timeout).

  • Mit notify() können Sie einen zufälligen Thread aktivieren, der auf den aktuellen Synchronisierungsblock wartet. Auch diese Methode kann nur in einem synchronisierten Block oder einer synchronisierten Methode aufgerufen werden (an anderen Orten gäbe es schließlich niemanden, der aufwachen könnte).

  • notifyAll() weckt alle Threads, die auf dem aktuellen Monitor warten (wird auch nur in einem synchronisierten Block oder einer synchronisierten Methode verwendet).

110. Wie stoppen wir einen Thread?

Das erste, was hier gesagt werden muss, ist, dass der Thread automatisch beendet wird, wenn run() vollständig ausgeführt wird. Aber manchmal möchten wir einen Thread vorzeitig beenden, bevor die Methode abgeschlossen ist. Also, was machen wir? Vielleicht können wir die stop() -Methode für das Thread- Objekt verwenden ? Nein! Diese Methode ist veraltet und kann zu Systemabstürzen führen. Erkundung der Fragen und Antworten aus einem Vorstellungsgespräch für eine Stelle als Java-Entwickler.  Teil 12 - 4Na, was dann? Dafür gibt es zwei Möglichkeiten: Erstens verwenden Sie das interne boolesche Flag. Schauen wir uns ein Beispiel an. Wir haben unsere Implementierung eines Threads, der eine bestimmte Phrase auf dem Bildschirm anzeigen soll, bis der Thread vollständig stoppt:

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;
   }
}
Durch den Aufruf der Methode „stopRunningThread()“ wird das interne Flag auf „false“ gesetzt, wodurch die Methode „run()“ beendet wird. Nennen wir es main :

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...");
Als Ergebnis sehen wir in der Konsole etwa Folgendes:
Programm startet... Der Thread führt eine Logik aus... Der Thread führt eine Logik aus... Der Thread führt eine Logik aus... Der Thread führt eine Logik aus... Der Thread führt eine Logik aus... Der Thread führt eine Logik aus ... Programm stoppt ... Der Thread wurde gestoppt!
Das bedeutet, dass unser Thread gestartet, mehrere Meldungen auf der Konsole ausgegeben und dann erfolgreich beendet wurde. Beachten Sie, dass die Anzahl der angezeigten Nachrichten von Start zu Start variieren kann. Und manchmal zeigt der Hilfsthread überhaupt nichts an. Das spezifische Verhalten hängt davon ab, wie lange der Hauptthread schläft. Je länger er schläft, desto unwahrscheinlicher ist es, dass der Hilfsthread nichts anzeigen kann. Bei einer Ruhezeit von 1 ms werden Sie die Nachrichten fast nie sehen. Wenn Sie es jedoch auf 20 ms einstellen, werden die Nachrichten fast immer angezeigt. Wenn die Ruhezeit kurz ist, hat der Thread einfach keine Zeit, zu starten und seine Arbeit zu erledigen. Stattdessen wird es sofort gestoppt. Eine zweite Möglichkeit besteht darin, die Methode interrupted() für das Thread- Objekt zu verwenden . Es gibt den Wert des internen Interrupt-Flags zurück, der standardmäßig falsch ist. Oder seine interrupt()- Methode, die dieses Flag auf true setzt (wenn das Flag true ist , sollte der Thread nicht mehr ausgeführt werden). Schauen wir uns ein Beispiel an:

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!");
       }
   }
}
Im Hauptlauf :

System.out.println("Program starting...");
Thread thread = new CustomThread();
thread.start();
Thread.sleep(3);
thread.interrupt();
System.out.println("Program stopping...");
Das Ergebnis dieser Ausführung ist dasselbe wie im ersten Fall, aber dieser Ansatz gefällt mir besser: Wir haben weniger Code geschrieben und mehr vorgefertigte Standardfunktionen verwendet. So, das war's für heute!
Kommentare
  • Beliebt
  • Neu
  • Alt
Du musst angemeldet sein, um einen Kommentar schreiben zu können
Auf dieser Seite gibt es noch keine Kommentare