Für diejenigen, die das Wort Java Core zum ersten Mal hören, sind dies die grundlegenden Grundlagen der Sprache. Mit diesem Wissen können Sie bedenkenlos ein Praktikum/Praktikum absolvieren.
Diese Fragen helfen Ihnen, Ihr Wissen vor dem Vorstellungsgespräch aufzufrischen oder etwas Neues für sich zu lernen. Um praktische Fähigkeiten zu erwerben, studieren Sie bei JavaRush . Originalartikel Links zu anderen Teilen: Java Core. Interviewfragen, Teil 1 Java Core. Fragen für ein Interview, Teil 3
Warum sollte die Methode finalize() vermieden werden?
Wir alle kennen die Aussage, dass eine Methodefinalize()
vom Garbage Collector aufgerufen wird, bevor der von einem Objekt belegte Speicher freigegeben wird. Hier ist ein Beispielprogramm, das beweist, dass ein Methodenaufruf finalize()
nicht garantiert ist:
public class TryCatchFinallyTest implements Runnable {
private void testMethod() throws InterruptedException
{
try
{
System.out.println("In try block");
throw new NullPointerException();
}
catch(NullPointerException npe)
{
System.out.println("In catch block");
}
finally
{
System.out.println("In finally block");
}
}
@Override
protected void finalize() throws Throwable {
System.out.println("In finalize block");
super.finalize();
}
@Override
public void run() {
try {
testMethod();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class TestMain
{
@SuppressWarnings("deprecation")
public static void main(String[] args) {
for(int i=1;i< =3;i++)
{
new Thread(new TryCatchFinallyTest()).start();
}
}
}
Ausgabe: Im Try-Block Im Catch-Block Im Final-Block Im Try-Block Im Catch-Block Im Final-Block Im Try-Block Im Catch-Block Im Final-Block Überraschenderweise finalize
wurde die Methode für keinen Thread ausgeführt. Das bestätigt meine Worte. Ich denke, der Grund dafür ist, dass Finalizer von einem separaten Garbage-Collector-Thread ausgeführt werden. Wenn die Java Virtual Machine zu früh beendet wird, hat der Garbage Collector nicht genügend Zeit, Finalizer zu erstellen und auszuführen. Weitere Gründe, die Methode nicht zu nutzen, finalize()
können sein:
- Die Methode
finalize()
funktioniert nicht mit Ketten wie Konstruktoren. Das heißt, wenn Sie einen Klassenkonstruktor aufrufen, werden die Konstruktoren der Oberklasse bedingungslos aufgerufen. Bei der Methodefinalize()
wird dies jedoch nicht passieren. Die Superklassenmethodefinalize()
muss explizit aufgerufen werden. - Jede von der Methode ausgelöste Ausnahme
finalize
wird vom Garbage-Collector-Thread ignoriert und nicht weitergegeben, was bedeutet, dass das Ereignis nicht in Ihren Protokollen aufgezeichnet wird. Das ist sehr schlimm, nicht wahr? -
Außerdem kommt es zu erheblichen Leistungseinbußen, wenn die Methode
finalize()
in Ihrer Klasse vorhanden ist. In „Effective Programming“ (2. Aufl.) sagte Joshua Bloch:
„Ja, und noch etwas: Die Verwendung von Finalizern führt zu großen Leistungseinbußen. Auf meiner Maschine beträgt die Zeit zum Erstellen und Zerstören einfacher Objekte etwa 5,6 Nanosekunden.
Durch das Hinzufügen eines Finalizers erhöht sich die Zeit auf 2400 Nanosekunden. Mit anderen Worten: Das Erstellen und Löschen eines Objekts mit einem Finalizer ist etwa 430-mal langsamer.“
Warum sollte HashMap nicht in einer Multithread-Umgebung verwendet werden? Könnte dies eine Endlosschleife verursachen?
Wir wissen, dass esHashMap
sich hierbei um eine nicht synchronisierte Sammlung handelt, deren synchronisiertes Gegenstück HashTable
. Wenn Sie also auf eine Sammlung zugreifen und sich in einer Umgebung mit mehreren Threads befinden, in der alle Threads Zugriff auf eine einzelne Instanz der Sammlung haben, ist die Verwendung HashTable
aus offensichtlichen Gründen sicherer, z. B. um schmutzige Lesevorgänge zu vermeiden und die Datenkonsistenz sicherzustellen. Im schlimmsten Fall führt diese Multithread-Umgebung zu einer Endlosschleife. Ja es ist wahr. HashMap.get()
kann eine Endlosschleife verursachen. Mal sehen, wie? Schaut man sich den Quellcode der Methode an HashMap.get(Object key)
, sieht dieser so aus:
public Object get(Object key) {
Object k = maskNull(key);
int hash = hash(k);
int i = indexFor(hash, table.length);
Entry e = table[i];
while (true) {
if (e == null)
return e;
if (e.hash == hash && eq(k, e.key))
return e.value;
e = e.next;
}
}
while(true)
kann in einer Multithread-Laufzeitumgebung immer einer Endlosschleife zum Opfer fallen, wenn e.next
es aus irgendeinem Grund auf sich selbst verweisen kann. Dies führt zu einer Endlosschleife, aber wie e.next
zeigt es auf sich selbst (also auf e
)? Dies kann bei einer Methode passieren , die während der Größenänderung void transfer(Entry[] newTable)
aufgerufen wird .HashMap
do {
Entry next = e.next;
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
} while (e != null);
Dieser Codeteil neigt dazu, eine Endlosschleife zu erzeugen, wenn die Größenänderung gleichzeitig mit dem Versuch eines anderen Threads erfolgt, die Karteninstanz zu ändern ( HashMap
). Die einzige Möglichkeit, dieses Szenario zu vermeiden, besteht darin, die Synchronisierung in Ihrem Code zu verwenden, oder noch besser, eine synchronisierte Sammlung zu verwenden.
Erklären Sie Abstraktion und Kapselung. Wie hängen sie zusammen?
Mit einfachen Worten : „ Abstraktion zeigt nur die Eigenschaften eines Objekts an, die für die aktuelle Ansicht von Bedeutung sind . “ In der objektorientierten Programmiertheorie umfasst Abstraktion die Fähigkeit, Objekte zu definieren, die abstrakte „Akteure“ darstellen, die Arbeit ausführen, Änderungen in ihrem Zustand ändern und melden und mit anderen Objekten im System „interagieren“ können. Abstraktion funktioniert in jeder Programmiersprache auf viele Arten. Dies lässt sich an der Erstellung von Routinen zur Definition von Schnittstellen für Befehle in niedriger Sprache erkennen. Einige Abstraktionen versuchen, die Breite der Gesamtdarstellung der Bedürfnisse eines Programmierers einzuschränken, indem sie die Abstraktionen, auf denen sie basieren, wie z. B. Entwurfsmuster, vollständig verbergen. Typischerweise kann Abstraktion auf zwei Arten gesehen werden: Datenabstraktion ist eine Möglichkeit, komplexe Datentypen zu erstellen und nur sinnvolle Vorgänge für die Interaktion mit dem Datenmodell verfügbar zu machen, während gleichzeitig alle Implementierungsdetails vor der Außenwelt verborgen bleiben. Bei der Ausführungsabstraktion handelt es sich um den Prozess, bei dem alle wichtigen Anweisungen identifiziert und als Arbeitseinheit verfügbar gemacht werden. Normalerweise verwenden wir diese Funktion, wenn wir eine Methode erstellen, um einige Arbeiten auszuführen. Das Einschränken von Daten und Methoden innerhalb von Klassen in Kombination mit dem Ausblenden (mithilfe der Zugriffskontrolle) wird oft als Kapselung bezeichnet. Das Ergebnis ist ein Datentyp mit Eigenschaften und Verhalten. Bei der Kapselung geht es im Wesentlichen auch um das Ausblenden von Daten und das Ausblenden von Implementierungen. „Alles zusammenfassen, was sich ändern kann“ . Dieses Zitat ist ein bekanntes Gestaltungsprinzip. In jeder Klasse können Datenänderungen zur Laufzeit und Implementierungsänderungen in zukünftigen Versionen auftreten. Daher gilt die Kapselung sowohl für die Daten als auch für die Implementierung. Sie können also wie folgt verbunden werden:- Abstraktion ist hauptsächlich das, was eine Klasse tun kann [Idee]
- Kapselung ist mehr So erreichen Sie diese Funktionalität [Implementierung]
Unterschiede zwischen Schnittstelle und abstrakter Klasse?
Die wesentlichen Unterschiede lassen sich wie folgt auflisten:- Eine Schnittstelle kann keine Methoden implementieren, eine abstrakte Klasse jedoch schon.
- Eine Klasse kann viele Schnittstellen implementieren, aber nur eine Oberklasse haben (abstrakt oder nicht abstrakt).
- Eine Schnittstelle ist nicht Teil einer Klassenhierarchie. Nicht verwandte Klassen können dieselbe Schnittstelle implementieren.
Cat
es Dog
von der abstrakten Klasse erben Animal
, und diese abstrakte Basisklasse implementiert die Methode void Breathe()
„Breathe“, die somit alle Tiere auf die gleiche Weise ausführen. Welche Verben können in meiner Klasse und auf andere angewendet werden? Erstellen Sie für jedes dieser Verben eine Schnittstelle. Zum Beispiel können alle Tiere fressen, also werde ich eine Schnittstelle erstellen IFeedable
und Animal
diese Schnittstelle implementieren lassen. Nur gut genug, um eine Schnittstelle zu implementieren Dog
( mag mir gefallen), aber nicht alle. Jemand sagte: Der Hauptunterschied besteht darin, wo Sie Ihre Implementierung wünschen. Wenn Sie eine Schnittstelle erstellen, können Sie die Implementierung in jede Klasse verschieben, die Ihre Schnittstelle implementiert. Durch das Erstellen einer abstrakten Klasse können Sie die Implementierung aller abgeleiteten Klassen an einem Ort teilen und viele schlechte Dinge wie das Duplizieren von Code vermeiden. Horse
ILikeable
Wie spart StringBuffer Speicher?
Die KlasseString
ist als unveränderliches Objekt implementiert. Wenn Sie sich also zum ersten Mal dazu entschließen, etwas in das Objekt einzufügen String
, weist die virtuelle Maschine ein Array fester Länge zu, das genau der Größe Ihres ursprünglichen Werts entspricht. Dieser wird dann innerhalb der virtuellen Maschine als Konstante behandelt, was zu einer erheblichen Leistungsverbesserung führt, wenn sich der Wert der Zeichenfolge nicht ändert. Wenn Sie sich jedoch dazu entschließen, den Inhalt einer Zeichenfolge auf irgendeine Weise zu ändern, kopiert die virtuelle Maschine tatsächlich den Inhalt der ursprünglichen Zeichenfolge in den temporären Bereich, nimmt Ihre Änderungen vor und speichert diese Änderungen dann in einem neuen Speicherarray. Daher ist das Vornehmen von Änderungen am Wert einer Zeichenfolge nach der Initialisierung ein kostspieliger Vorgang. StringBuffer
Andererseits wird es als dynamisch wachsendes Array innerhalb der virtuellen Maschine implementiert, was bedeutet, dass jeder Änderungsvorgang an einer vorhandenen Speicherzelle durchgeführt werden kann und bei Bedarf neuer Speicher zugewiesen wird. Allerdings gibt es für die virtuelle Maschine keine Möglichkeit, die Optimierung durchzuführen, StringBuffer
da ihre Inhalte in allen Instanzen als inkonsistent gelten.
Warum werden die Wait- und Notify-Methoden in der Object-Klasse und nicht in Thread deklariert?
Die Methodenwait
, notify
, notifyAll
werden nur benötigt, wenn Sie möchten, dass Ihre Threads Zugriff auf gemeinsam genutzte Ressourcen haben und die gemeinsam genutzte Ressource ein beliebiges Java-Objekt im Heap sein kann. Daher werden diese Methoden in der Basisklasse definiert, Object
sodass jedes Objekt über ein Steuerelement verfügt, das es Threads ermöglicht, auf ihrem Monitor zu warten. Java verfügt über kein spezielles Objekt, das zum Teilen einer gemeinsam genutzten Ressource verwendet wird. Es ist keine solche Datenstruktur definiert. Daher liegt es in der Verantwortung der Klasse, Object
eine gemeinsam genutzte Ressource zu werden und Hilfsmethoden wie wait()
, notify()
, bereitzustellen notifyAll()
. Java basiert auf Charles Hoares Idee von Monitoren. In Java verfügen alle Objekte über einen Monitor. Threads warten auf Monitoren. Um das Warten durchzuführen, benötigen wir zwei Parameter:
- ein Thread
- Monitor (jedes Objekt).
wait
). Dies ist ein gutes Design, denn wenn wir einen anderen Thread zwingen können, auf einem bestimmten Monitor zu warten, führt dies zu einer „Invasion“, was das Entwerfen/Programmieren paralleler Programme erschwert. Denken Sie daran, dass in Java alle Vorgänge, die andere Threads beeinträchtigen, veraltet sind (z. B. stop()
).
Schreiben Sie ein Programm, um einen Deadlock in Java zu erzeugen und ihn zu beheben
In Javadeadlock
ist dies eine Situation, in der mindestens zwei Threads einen Block auf verschiedenen Ressourcen halten und beide darauf warten, dass die andere Ressource verfügbar wird, um ihre Aufgabe abzuschließen. Und keiner von ihnen ist in der Lage, die gehaltene Ressource zu sperren. Beispielprogramm:
package thread;
public class ResolveDeadLockTest {
public static void main(String[] args) {
ResolveDeadLockTest test = new ResolveDeadLockTest();
final A a = test.new A();
final B b = test.new B();
// Thread-1
Runnable block1 = new Runnable() {
public void run() {
synchronized (a) {
try {
// Добавляем задержку, чтобы обе нити могли начать попытки
// блокирования ресурсов
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// Thread-1 заняла A но также нуждается в B
synchronized (b) {
System.out.println("In block 1");
}
}
}
};
// Thread-2
Runnable block2 = new Runnable() {
public void run() {
synchronized (b) {
// Thread-2 заняла B но также нуждается в A
synchronized (a) {
System.out.println("In block 2");
}
}
}
};
new Thread(block1).start();
new Thread(block2).start();
}
// Resource A
private class A {
private int i = 10;
public int getI() {
return i;
}
public void setI(int i) {
this.i = i;
}
}
// Resource B
private class B {
private int i = 20;
public int getI() {
return i;
}
public void setI(int i) {
this.i = i;
}
}
}
Das Ausführen des obigen Codes führt aus sehr offensichtlichen Gründen (oben erläutert) zu einem Deadlock. Jetzt müssen wir dieses Problem lösen. Ich glaube, dass die Lösung jedes Problems an der Wurzel des Problems selbst liegt. In unserem Fall ist das Zugriffsmodell auf A und B das Hauptproblem. Um das Problem zu lösen, ändern wir daher einfach die Reihenfolge der Zugriffsoperatoren auf gemeinsam genutzte Ressourcen. Nach der Änderung sieht es so aus:
// Thread-1
Runnable block1 = new Runnable() {
public void run() {
synchronized (b) {
try {
// Добавляем задержку, чтобы обе нити могли начать попытки
// блокирования ресурсов
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// Thread-1 заняла B но также нуждается в А
synchronized (a) {
System.out.println("In block 1");
}
}
}
};
// Thread-2
Runnable block2 = new Runnable() {
public void run() {
synchronized (b) {
// Thread-2 заняла B но также нуждается в А
synchronized (a) {
System.out.println("In block 2");
}
}
}
};
Führen Sie diese Klasse erneut aus und Sie werden den Deadlock jetzt nicht mehr sehen. Ich hoffe, dass dies Ihnen hilft, Deadlocks zu vermeiden und sie zu beseitigen, wenn Sie auf sie stoßen.
Was passiert, wenn Ihre Klasse, die die Serializable-Schnittstelle implementiert, eine nicht serialisierbare Komponente enthält? Wie kann man das beheben?
In diesem Fall wird esNotSerializableException
während der Ausführung ausgelöst. Um dieses Problem zu beheben, gibt es eine sehr einfache Lösung: Aktivieren Sie diese Kontrollkästchen transient
. Dies bedeutet, dass überprüfte Felder nicht serialisiert werden. Wenn Sie auch den Status dieser Felder speichern möchten, müssen Sie Referenzvariablen berücksichtigen, die das bereits implementieren Serializable
. readResolve()
Möglicherweise müssen Sie auch die Methoden und verwenden writeResolve()
. Fassen wir zusammen:
- Machen Sie zunächst Ihr Feld nicht serialisierbar
transient
. writeObject
Rufen Sie zunächstdefaultWriteObject
den Thread auf, um alle Nicht-transient
Felder zu speichern, und rufen Sie dann die restlichen Methoden auf, um die einzelnen Eigenschaften Ihres nicht serialisierbaren Objekts zu serialisieren.- Rufen Sie in
readObject
zuerstdefaultReadObject
den Stream auf, um alle Nicht-transient
Felder zu lesen, und rufen Sie dann andere Methoden auf (entsprechend denen, die Sie in hinzugefügt habenwriteObject
), um Ihr Nicht-transient
Objekt zu deserialisieren.
Erklären Sie transiente und flüchtige Schlüsselwörter in Java
„Das Schlüsselworttransient
wird verwendet, um Felder anzugeben, die nicht serialisiert werden.“ Gemäß der Java-Sprachspezifikation: Variablen können mit dem Transientenindikator markiert werden, um anzuzeigen, dass sie nicht Teil des persistenten Zustands des Objekts sind. Sie können beispielsweise Felder enthalten, die von anderen Feldern abgeleitet sind, und es ist vorzuziehen, sie programmgesteuert abzurufen, anstatt ihren Status durch Serialisierung wiederherzustellen. Beispielsweise können in einer Klasse BankPayment.java
Felder wie principal
(Direktor) und rate
(Rate) serialisiert werden und interest
(aufgelaufene Zinsen) jederzeit berechnet werden, auch nach der Deserialisierung. Wenn wir uns erinnern, hat jeder Thread in Java seinen eigenen lokalen Speicher und führt Lese-/Schreibvorgänge in diesem lokalen Speicher aus. Wenn alle Vorgänge abgeschlossen sind, wird der geänderte Status der Variablen in den gemeinsam genutzten Speicher geschrieben, von wo aus alle Threads auf die Variable zugreifen. Normalerweise ist dies ein normaler Thread innerhalb einer virtuellen Maschine. Der flüchtige Modifikator teilt der virtuellen Maschine jedoch mit, dass der Zugriff eines Threads auf diese Variable immer mit seiner eigenen Kopie dieser Variablen mit der Masterkopie der Variablen im Speicher übereinstimmen muss. Dies bedeutet, dass ein Thread jedes Mal, wenn er den Status einer Variablen lesen möchte, den internen Speicherstatus löschen und die Variable aus dem Hauptspeicher aktualisieren muss. Volatile
am nützlichsten bei sperrenfreien Algorithmen. Sie markieren eine Variable, die gemeinsam genutzte Daten speichert, als flüchtig, verwenden dann keine Sperren, um auf diese Variable zuzugreifen, und alle von einem Thread vorgenommenen Änderungen sind für andere sichtbar. Oder wenn Sie eine „Geschehen-Danach“-Beziehung erstellen möchten, um sicherzustellen, dass Berechnungen nicht wiederholt werden, wiederum um sicherzustellen, dass Änderungen in Echtzeit sichtbar sind. Volatile sollte verwendet werden, um unveränderliche Objekte sicher in einer Multithread-Umgebung zu veröffentlichen. Die Felddeklaration public volatile ImmutableObject
stellt sicher, dass alle Threads immer den aktuell verfügbaren Verweis auf die Instanz sehen.
Unterschied zwischen Iterator und ListIterator?
Wir können oder verwenden ,Iterator
um über Elemente zu iterieren . Es kann jedoch nur zum Durchlaufen von Elementen verwendet werden . Weitere Unterschiede werden im Folgenden beschrieben. Sie können: Set
List
Map
ListIterator
List
- in umgekehrter Reihenfolge iterieren.
- Holen Sie sich den Index irgendwo.
- Fügen Sie überall einen Mehrwert hinzu.
- Setzen Sie einen beliebigen Wert an der aktuellen Position.
GO TO FULL VERSION