JavaRush /Java-Blog /Random-DE /Analyse von Fragen und Antworten aus Interviews für Java-...

Analyse von Fragen und Antworten aus Interviews für Java-Entwickler. Teil 15

Veröffentlicht in der Gruppe Random-DE
Hallo Hallo! Wie viel muss ein Java-Entwickler wissen? Über dieses Thema kann man lange streiten, aber die Wahrheit ist, dass Sie beim Vorstellungsgespräch voll und ganz von der Theorie getrieben werden. Auch in den Wissensbereichen, die Sie in Ihrer Arbeit nicht nutzen können. Analyse von Fragen und Antworten aus Interviews für Java-Entwickler.  Teil 15 - 1Nun, wenn Sie Anfänger sind, werden Ihre theoretischen Kenntnisse sehr ernst genommen. Da noch keine Erfahrungen und großen Erfolge vorliegen, bleibt nur noch die Überprüfung der Stärke der Wissensbasis. Heute werden wir diese Basis weiter stärken, indem wir die beliebtesten Interviewfragen für Java-Entwickler untersuchen. Lass uns fliegen!

Java Core

9. Was ist der Unterschied zwischen statischer und dynamischer Bindung in Java?

Ich habe diese Frage bereits in diesem Artikel in Frage 18 zum statischen und dynamischen Polymorphismus beantwortet, ich empfehle Ihnen, sie zu lesen.

10. Ist es möglich, private oder geschützte Variablen in einer Schnittstelle zu verwenden?

Nein, geht nicht. Denn wenn Sie eine Schnittstelle deklarieren, fügt der Java-Compiler automatisch die Schlüsselwörter public und abstract vor den Schnittstellenmethoden und die Schlüsselwörter public , static und final vor den Datenmitgliedern hinzu. Wenn Sie private oder protected hinzufügen, entsteht tatsächlich ein Konflikt und der Compiler beschwert sich über den Zugriffsmodifikator mit der Meldung: „Modifikator '<ausgewählter Modifikator>' ist hier nicht zulässig.“ Warum fügt der Compiler public , static und final hinzu? Variablen in der Schnittstelle? Lass es uns herausfinden:
  • public – Die Schnittstelle ermöglicht dem Client die Interaktion mit dem Objekt. Wenn die Variablen nicht öffentlich wären, hätten Clients keinen Zugriff darauf.
  • statisch – Schnittstellen (bzw. deren Objekte) können nicht erstellt werden, daher ist die Variable statisch.
  • final – da die Schnittstelle verwendet wird, um eine 100-prozentige Abstraktion zu erreichen, hat die Variable ihre endgültige Form (und wird nicht geändert).

11. Was ist Classloader und wofür wird er verwendet?

Classloader – oder Class Loader – ermöglicht das Laden von Java-Klassen. Genauer gesagt wird das Laden durch seine Nachkommen sichergestellt – spezifische Klassenlader, weil ClassLoader selbst ist abstrakt. Jedes Mal, wenn eine .class-Datei geladen wird, beispielsweise nach dem Aufruf eines Konstruktors oder einer statischen Methode der entsprechenden Klasse, wird diese Aktion von einem der Nachkommen der ClassLoader- Klasse ausgeführt . Es gibt drei Arten von Erben:
  1. Bootstrap ClassLoader ist ein Basislader, der auf JVM-Ebene implementiert ist und keine Rückmeldung von der Laufzeitumgebung erhält, da er Teil des JVM-Kernels ist und in nativem Code geschrieben ist. Dieser Loader dient als übergeordnetes Element aller anderen ClassLoader-Instanzen.

    Hauptsächlich verantwortlich für das Laden interner JDK-Klassen, normalerweise rt.jar und anderer Kernbibliotheken, die sich im Verzeichnis $JAVA_HOME/jre/lib befinden . Verschiedene Plattformen können unterschiedliche Implementierungen dieses Klassenladers haben.

  2. Extension Classloader ist ein Erweiterungslader, ein Nachkomme der Basisladerklasse. Kümmert sich um das Laden der Erweiterung der Standard-Java-Basisklassen. Wird aus dem JDK-Erweiterungsverzeichnis geladen, normalerweise $JAVA_HOME/lib/ext oder einem anderen in der Systemeigenschaft java.ext.dirs genannten Verzeichnis (diese Option kann verwendet werden, um das Laden von Erweiterungen zu steuern).

  3. System ClassLoader ist ein auf JRE-Ebene implementierter Systemlader, der sich um das Laden aller Klassen auf Anwendungsebene in die JVM kümmert. Es lädt Dateien, die in der Klassenumgebungsvariablen -classpath oder der Befehlszeilenoption -cp enthalten sind.

Analyse von Fragen und Antworten aus Interviews für Java-Entwickler.  Teil 15 - 2Klassenlader sind Teil der Java-Laufzeitumgebung. Sobald die JVM eine Klasse anfordert, versucht der Klassenlader, die Klasse zu finden und die Klassendefinition unter Verwendung des vollständig qualifizierten Namens der Klasse in die Laufzeit zu laden. Die Methode java.lang.ClassLoader.loadClass() ist für das Laden der Klassendefinition zur Laufzeit verantwortlich. Es versucht, eine Klasse basierend auf ihrem vollständigen Namen zu laden. Wenn die Klasse noch nicht geladen wurde, delegiert es die Anfrage an den übergeordneten Klassenlader. Dieser Vorgang erfolgt rekursiv und sieht folgendermaßen aus:
  1. Der System-Classloader versucht, die Klasse in seinem Cache zu finden.

    • 1.1. Wenn die Klasse gefunden wird, ist der Ladevorgang erfolgreich abgeschlossen.

    • 1.2. Wenn die Klasse nicht gefunden wird, wird das Laden an den Extension Classloader delegiert.

  2. Der Extension Classloader versucht, die Klasse in seinem eigenen Cache zu finden.

    • 2.1. Wenn die Klasse gefunden wird, wird sie erfolgreich abgeschlossen.

    • 2.2. Wenn die Klasse nicht gefunden wird, wird das Laden an den Bootstrap Classloader delegiert.

  3. Bootstrap Classloader versucht, die Klasse in seinem eigenen Cache zu finden.

    • 3.1. Wenn die Klasse gefunden wird, ist der Ladevorgang erfolgreich abgeschlossen.

    • 3.2. Wenn die Klasse nicht gefunden wird, versucht der zugrunde liegende Bootstrap-Klassenlader, sie zu laden.

  4. Beim Laden:

    • 4.1. Erfolgreich – Laden der Klasse ist abgeschlossen.

    • 4.2. Wenn dies fehlschlägt, wird die Steuerung an den Extension Classloader übertragen.

  5. 5. Der Extension Classloader versucht, die Klasse zu laden, und wenn sie lädt:

    • 5.1. Erfolgreich – Laden der Klasse ist abgeschlossen.

    • 5.2. Wenn dies fehlschlägt, wird die Kontrolle an den System Classloader übertragen.

  6. 6. Der System-Klassenlader versucht, die Klasse zu laden, und wenn sie geladen wird:

    • 6.1. Erfolgreich – Laden der Klasse ist abgeschlossen.

    • 6.2. Nicht erfolgreich bestanden – eine Ausnahme wird generiert – ClassNotFoundException.

Das Thema Klassenlader ist umfangreich und sollte nicht vernachlässigt werden. Um es genauer kennenzulernen, empfehle ich Ihnen, diesen Artikel zu lesen , und wir werden nicht lange auf sich warten lassen und weitermachen.

12. Was sind Laufzeitdatenbereiche?

Laufzeitdatenbereiche – JVM-Laufzeitdatenbereiche. Die JVM definiert einige Laufzeitdatenbereiche, die während der Programmausführung benötigt werden. Einige davon werden beim Start der JVM erstellt. Andere sind Thread-lokal und werden nur erstellt, wenn der Thread erstellt wird (und zerstört, wenn der Thread zerstört wird). Die JVM-Laufzeitdatenbereiche sehen folgendermaßen aus: Analyse von Fragen und Antworten aus Interviews für Java-Entwickler.  Teil 15 - 3
  • Das PC-Register ist für jeden Thread lokal und enthält die Adresse der JVM-Anweisung, die der Thread gerade ausführt.

  • Der JVM-Stack ist ein Speicherbereich, der als Speicher für lokale Variablen und temporäre Ergebnisse verwendet wird. Jeder Thread verfügt über einen eigenen Stapel: Sobald der Thread beendet wird, wird auch dieser Stapel zerstört. Es ist erwähnenswert, dass der Vorteil von Stack gegenüber Heap in der Leistung liegt, während Heap sicherlich einen Vorteil bei der Speicherskalierung hat.

  • Nativer Methodenstapel – Ein Datenbereich pro Thread, der Datenelemente speichert, ähnlich dem JVM-Stack, zum Ausführen nativer (Nicht-Java-)Methoden.

  • Heap – wird von allen Threads als Speicher verwendet, der Objekte, Klassenmetadaten, Arrays usw. enthält, die zur Laufzeit erstellt werden. Dieser Bereich wird beim Starten der JVM erstellt und beim Herunterfahren zerstört.

  • Methodenbereich – Dieser Laufzeitbereich ist allen Threads gemeinsam und wird beim Start der JVM erstellt. Es speichert Strukturen für jede Klasse, wie den Runtime Constant Pool, Code für Konstruktoren und Methoden, Methodendaten usw.

13. Was ist ein unveränderliches Objekt?

In diesem Teil des Artikels, in den Fragen 14 und 15, gibt es bereits eine Antwort auf diese Frage, also werfen Sie einen Blick darauf, ohne Ihre Zeit zu verschwenden.

14. Was ist das Besondere an der String-Klasse?

Zu Beginn der Analyse haben wir wiederholt über bestimmte Funktionen von String gesprochen (hierfür gab es einen separaten Abschnitt). Fassen wir nun die Funktionen von String zusammen :
  1. Es ist das beliebteste Objekt in Java und wird für verschiedene Zwecke verwendet. In der Nutzungshäufigkeit steht es selbst primitiven Typen in nichts nach.

  2. Ein Objekt dieser Klasse kann ohne Verwendung des neuen Schlüsselworts erstellt werden – direkt durch Anführungszeichen String str = „string“; .

  3. String ist eine unveränderliche Klasse: Beim Erstellen eines Objekts dieser Klasse können seine Daten nicht geändert werden (wenn Sie zu einer bestimmten Zeichenfolge + „eine andere Zeichenfolge“ hinzufügen, erhalten Sie als Ergebnis eine neue, dritte Zeichenfolge). Die Unveränderlichkeit der String-Klasse macht sie threadsicher.

  4. Die String- Klasse ist finalisiert (hat den finalen Modifikator ) und kann daher nicht vererbt werden.

  5. String verfügt über einen eigenen String-Pool, einen Speicherbereich im Heap, der die von ihm erstellten String-Werte zwischenspeichert. In diesem Teil der Serie , in Frage 62, habe ich den String-Pool beschrieben.

  6. Java hat Analoga von String , die ebenfalls für die Arbeit mit Strings konzipiert sind – StringBuilder und StringBuffer , aber mit dem Unterschied, dass sie veränderbar sind. Mehr darüber können Sie in diesem Artikel lesen .

Analyse von Fragen und Antworten aus Interviews für Java-Entwickler.  Teil 15 - 4

15. Was ist Typkovarianz?

Um die Kovarianz zu verstehen, schauen wir uns ein Beispiel an. Nehmen wir an, wir haben eine Tierklasse:
public class Animal {
 void voice() {
   System.out.println("*тишина*");
 }
}
Und eine Hundeklasse erweitert es :
public class Dog extends Animal {

 @Override
 public void voice() {
   System.out.println("Гав, гав, гав!!!");
 }
}
Wie wir uns erinnern, können wir Objekte des Erbtyps leicht dem übergeordneten Typ zuordnen:
Animal animal = new Dog();
Dies wird nichts weiter als Polymorphismus sein. Praktisch und flexibel, nicht wahr? Nun, was ist mit der Liste der Tiere? Können wir einer Liste mit einem generischen Tier eine Liste mit Hundeobjekten geben ?
List<Dog> dogs = new ArrayList<>();
List<Animal> animals = dogs;
In diesem Fall wird die Zeile zur Zuordnung der Hundeliste zur Tierliste rot unterstrichen, d. h. Der Compiler übergibt diesen Code nicht. Obwohl diese Zuweisung recht logisch erscheint (schließlich können wir ein Dog- Objekt einer Variablen vom Typ Animal zuweisen ), ist sie nicht möglich. Denn wenn es erlaubt wäre, könnten wir ein Animal- Objekt in eine Liste einfügen, das ursprünglich als Dog gedacht war , während wir dachten, wir hätten nur Dogs in der Liste . Und dann verwenden wir zum Beispiel die Methode get() , um ein Objekt aus dieser Hundeliste zu nehmen , denken, es sei ein Hund, und rufen darauf eine Methode des Dog- Objekts auf, die Animal nicht hat . Und wie Sie verstehen, ist dies unmöglich – es wird ein Fehler auftreten. Aber glücklicherweise übersieht der Compiler diesen logischen Fehler nicht, wenn er eine Liste von Nachkommen einer Liste von Eltern zuordnet (und umgekehrt). In Java können Sie Listenobjekten nur Listenvariablen mit passenden Generika zuweisen. Dies nennt man Invariation. Wenn sie dies tun könnten, würde man es Kovarianz nennen. Das heißt, Kovarianz liegt vor, wenn wir ein Objekt vom Typ ArrayList<Dog> auf eine Variable vom Typ List<Animal> setzen könnten . Es stellt sich heraus, dass Kovarianz in Java nicht unterstützt wird? Egal wie es ist! Dies geschieht jedoch auf eine ganz besondere Art und Weise. Wofür wird das Design verwendet ? erweitert Animal . Es wird mit einem Generic der Variablen, auf die wir das Listenobjekt setzen möchten, mit einem Generic des Nachkommens platziert. Diese generische Konstruktion bedeutet, dass jeder Typ geeignet ist, der ein Nachkomme des Typs Animal ist (und der Typ Animal fällt ebenfalls unter diese Verallgemeinerung). Animal wiederum kann nicht nur eine Klasse, sondern auch eine Schnittstelle sein (lassen Sie sich nicht vom Schlüsselwort „extens“ täuschen ). Wir können unsere vorherige Aufgabe so erledigen: Analyse von Fragen und Antworten aus Interviews für Java-Entwickler.  Teil 15 - 5
List<Dog> dogs = new ArrayList<>();
List<? extends Animal> animals = dogs;
Als Ergebnis sehen Sie in der IDE, dass sich der Compiler über diese Konstruktion nicht beschweren wird. Lassen Sie uns die Funktionalität dieses Designs überprüfen. Nehmen wir an, wir haben eine Methode, die alle ihr zugeführten Tiere dazu bringt, Geräusche von sich zu geben:
public static void animalsVoice(List<? extends Animal> animals) {
 for (Animal animal : animals) {
   animal.voice();
 }
}
Geben wir ihm eine Liste von Hunden:
List<Dog> dogs = new ArrayList<>();
dogs.add(new Dog());
dogs.add(new Dog());
dogs.add(new Dog());
animalsVoice(dogs);
In der Konsole sehen wir die folgende Ausgabe:
Wuff wuff wuff!!! Wuff wuff wuff!!! Wuff wuff wuff!!!
Dies bedeutet, dass dieser Kovarianzansatz erfolgreich funktioniert. Bitte beachten Sie, dass dieses Generikum in der Liste enthalten ist . Erweitert Animal können wir keine neuen Daten jeglicher Art einfügen: weder vom Typ Dog noch einmal vom Typ Animal :
List<Dog> dogs = new ArrayList<>();
List<? extends Animal> animals = dogs;
animals.add(new Dog());
dogs.add(new Animal());
Tatsächlich wird der Compiler in den letzten beiden Zeilen das Einfügen von Objekten rot hervorheben. Dies liegt daran, dass wir nicht hundertprozentig sicher sein können, welche Liste von Objekten welchen Typs der Liste mit Daten durch das generische <? zugewiesen wird. erweitert Tier> . Analyse von Fragen und Antworten aus Interviews für Java-Entwickler.  Teil 15 - 6Ich möchte auch über Kontravarianz sprechen , da dieses Konzept normalerweise immer mit Kovarianz einhergeht und in der Regel gemeinsam danach gefragt wird. Dieses Konzept ist so etwas wie das Gegenteil von Kovarianz, da dieses Konstrukt den Erbtyp verwendet. Nehmen wir an, wir möchten eine Liste, der eine Liste von Typobjekten zugewiesen werden kann, die keine Vorfahren des Dog- Objekts sind . Allerdings wissen wir im Voraus nicht, um welche konkreten Typen es sich handelt. In diesem Fall ist eine Konstruktion der Form ? Superhund , für den alle Typen geeignet sind – die Vorfahren der Hundeklasse :
List<Animal> animals = new ArrayList<>();
List<? super Dog> dogs = animals;
dogs.add(new Dog());
dogs.add(new Dog());
Mit einem solchen generischen Objekt können wir sicher Objekte vom Typ Dog zur Liste hinzufügen , da es in jedem Fall über alle implementierten Methoden seiner Vorfahren verfügt. Wir können jedoch kein Objekt vom Typ Animal hinzufügen , da nicht sicher ist, dass sich darin Objekte dieses Typs befinden, nicht jedoch beispielsweise Dog . Schließlich können wir von einem Element dieser Liste eine Methode der Dog- Klasse anfordern, die Animal nicht haben wird . In diesem Fall tritt ein Kompilierungsfehler auf. Auch wenn wir die vorherige Methode implementieren wollten, aber mit diesem Generikum:
public static void animalsVoice(List<? super Dog> dogs) {
 for (Dog dog : dogs) {
   dog.voice();
 }
}
wir würden einen Kompilierungsfehler in der for- Schleife erhalten , da wir nicht sicher sein können, dass die zurückgegebene Liste Objekte vom Typ Dog enthält und ihre Methoden frei verwenden kann. Wenn wir die Methode dogs.get(0) auf dieser Liste aufrufen. - Wir erhalten ein Objekt vom Typ Object . Das heißt, damit die Methode AnimalsVoice() funktioniert , müssen wir zumindest kleine Manipulationen hinzufügen, um die Typdaten einzugrenzen:
public static void animalsVoice(List<? super Dog> dogs) {
 for (Object obj : dogs) {
   if (obj instanceof Dog) {
     Dog dog = (Dog) obj;
     dog.voice();
   }
 }
}
Analyse von Fragen und Antworten aus Interviews für Java-Entwickler.  Teil 15 - 7

16. Wie gibt es Methoden in der Object-Klasse?

In diesem Teil der Serie, in Absatz 11, habe ich diese Frage bereits beantwortet, daher empfehle ich Ihnen dringend, sie zu lesen, falls Sie dies noch nicht getan haben. Damit enden wir für heute. Wir sehen uns im nächsten Teil! Analyse von Fragen und Antworten aus Interviews für Java-Entwickler.  Teil 15 - 8
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION