JavaRush /Java-Blog /Random-DE /Java Core. Interviewfragen, Teil 2
Andrey
Level 26

Java Core. Interviewfragen, Teil 2

Veröffentlicht in der Gruppe Random-DE
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.
Java Core.  Fragen für ein Interview, Teil 2 - 1
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 Methode finalize()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 finalizewurde 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:
  1. 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 Methode finalize()wird dies jedoch nicht passieren. Die Superklassenmethode finalize()muss explizit aufgerufen werden.
  2. Jede von der Methode ausgelöste Ausnahme finalizewird 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?
  3. 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 es HashMapsich 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 HashTableaus 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.nextes aus irgendeinem Grund auf sich selbst verweisen kann. Dies führt zu einer Endlosschleife, aber wie e.nextzeigt 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.
Was Sie sich merken müssen, ist Folgendes: „Wenn Sie ein Konzept vollständig in Bezug auf „was es tut“ beschreiben können, ohne angeben zu müssen, „wie es es tut“, dann sollten Sie eine Schnittstelle verwenden. Wenn Sie einige Implementierungsdetails angeben müssen, müssen Sie Ihr Konzept in einer abstrakten Klasse darstellen.“ Anders ausgedrückt: Gibt es viele Klassen, die „zusammen gruppiert“ und durch ein einziges Substantiv beschrieben werden können? Wenn ja, erstellen Sie eine abstrakte Klasse mit dem Namen dieses Substantivs und erben Sie Klassen davon. Beispielsweise kann Cates Dogvon 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 IFeedableund Animaldiese 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. HorseILikeable

Wie spart StringBuffer Speicher?

Die Klasse Stringist 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. StringBufferAndererseits 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, StringBufferda 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 Methoden wait, notify, notifyAllwerden 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, Objectsodass 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, Objecteine 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).
Im Java-Design kann ein Thread nicht genau definiert werden; es ist immer der aktuelle Thread, der den Code ausführt. Wir können jedoch einen Monitor definieren (das ist ein Objekt, für das wir eine Methode aufrufen können 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 Java deadlockist 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. Java Core.  Fragen für ein Interview, Teil 2 - 2 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 es NotSerializableExceptionwä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.
  • writeObjectRufen Sie zunächst defaultWriteObjectden Thread auf, um alle Nicht- transientFelder zu speichern, und rufen Sie dann die restlichen Methoden auf, um die einzelnen Eigenschaften Ihres nicht serialisierbaren Objekts zu serialisieren.
  • Rufen Sie in readObjectzuerst defaultReadObjectden Stream auf, um alle Nicht- transientFelder zu lesen, und rufen Sie dann andere Methoden auf (entsprechend denen, die Sie in hinzugefügt haben writeObject), um Ihr Nicht- transientObjekt zu deserialisieren.

Erklären Sie transiente und flüchtige Schlüsselwörter in Java

„Das Schlüsselwort transientwird 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.javaFelder 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. Volatileam 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 ImmutableObjectstellt sicher, dass alle Threads immer den aktuell verfügbaren Verweis auf die Instanz sehen.

Unterschied zwischen Iterator und ListIterator?

Wir können oder verwenden , Iteratorum über Elemente zu iterieren . Es kann jedoch nur zum Durchlaufen von Elementen verwendet werden . Weitere Unterschiede werden im Folgenden beschrieben. Sie können: SetListMapListIteratorList
  1. in umgekehrter Reihenfolge iterieren.
  2. Holen Sie sich den Index irgendwo.
  3. Fügen Sie überall einen Mehrwert hinzu.
  4. Setzen Sie einen beliebigen Wert an der aktuellen Position.
Viel Erfolg beim Studium!! Autor des Artikels Lokesh Gupta Originalartikel Java Core. Interviewfragen, Teil 1 Java Core. Fragen für ein Interview, Teil 3
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION