
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.
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.
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"); }

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

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.
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:
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!
GO TO FULL VERSION