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 13

Veröffentlicht in der Gruppe Random-DE
Hallo!
Bewegung auf ein Ziel zu ist in erster Linie Bewegung.
Daher reicht es nicht aus, nur zu denken, dass man etwas erreichen möchte. Sie müssen etwas tun – auch die kleinsten Schritte –, aber tun Sie es jeden Tag, und nur so werden Sie das Endziel erreichen. Und da Sie hier sind, um Java-Entwickler zu werden, müssen Sie jeden Tag zumindest einen kleinen Schritt zur Vertiefung Ihrer Java-Kenntnisse unternehmen. Für den heutigen Java-Schritt empfehle ich Ihnen, sich mit dem neuen Teil der Analyse der beliebtesten Interviewfragen für Entwickler vertraut zu machen. Analyse von Fragen und Antworten aus Interviews für Java-Entwickler.  Teil 13 - 1Heute gehen wir den praktischen Teil der Fragen für Nachwuchsfachkräfte durch. Eine praktische Aufgabe bei einem Vorstellungsgespräch ist keine Seltenheit. Es ist wichtig, sich in einer solchen Situation nicht zu verlieren, einen kühlen Kopf zu bewahren und die optimale Lösung oder sogar mehrere anzubieten. Ich würde auch empfehlen, beim Lösen eines Problems nicht zu schweigen, sondern Ihren Gedankengang zu kommentieren und die Lösung aufzuschreiben oder nach dem Schreiben in Worten zu erklären, was Sie getan haben und warum. Dadurch machen Sie sich beim Interviewer viel beliebter als eine stille Entscheidung. Also lasst uns anfangen!

111. Wie tausche ich Daten zwischen Threads aus?

Analyse von Fragen und Antworten aus Interviews für Java-Entwickler.  Teil 13 - 2Um Daten zwischen Threads auszutauschen, können Sie viele verschiedene Ansätze und Mittel verwenden: Verwenden Sie beispielsweise atomare Variablen, synchronisierte Sammlungen und ein Semaphor. Aber um dieses Problem zu lösen, werde ich ein Beispiel mit Exchanger geben . Exchanger ist eine Synchronisationsklasse aus dem Concurrent- Paket, die den Austausch von Elementen zwischen einem Thread-Paar durch die Erstellung eines gemeinsamen Synchronisationspunkts erleichtert. Seine Verwendung vereinfacht den Datenaustausch zwischen zwei Threads. Die Funktionsweise ist recht einfach: Es wartet darauf, dass zwei separate Threads seine Methode „exchange()“ aufrufen . Zwischen ihnen entsteht so etwas wie ein Austauschpunkt: Der erste Thread legt sein Objekt ab und erhält im Gegenzug das Objekt des anderen, und dieser wiederum empfängt das Objekt des ersten und stellt sein eigenes ab. Das heißt, der erste Thread verwendet die Methode „exchange()“ und ist inaktiv, bis ein anderer Thread die Methode „exchange()“ für dasselbe Objekt aufruft und Daten zwischen ihnen ausgetauscht werden. Betrachten Sie als Beispiel die folgende Implementierung der Thread- Klasse :
public class CustomThread extends Thread {
 private String threadName;
 private String message;
 private Exchanger<String> exchanger;

 public CustomThread(String threadName, Exchanger<String> exchanger) {
   this.threadName = threadName;
   this.exchanger = exchanger;
 }

 public void setMessage(final String message) {
   this.message = message;
 }

 @Override
 public void run() {
   while (true) {
     try {
       message = exchanger.exchange(message);
       System.out.println(threadName + " поток получил сообщение: " + message);
       Thread.sleep(1000);
     } catch (Exception e) {
       e.printStackTrace();
     }
   }
 }
}
Im Thread-Konstruktor definieren wir ein Exchanger- Objekt , das Objekte vom Typ String akzeptiert , und beim Start (in der Ausführungsmethode ) verwenden wir dessen Exchange() , um eine Nachricht mit einem anderen Thread auszutauschen, der diese Methode im selben Exchanger verwendet . Lassen Sie es uns in main ausführen :
Exchanger<String> exchanger = new Exchanger<>();
CustomThread first = new CustomThread("Первый ", exchanger);
first.setMessage("Сообщение первого потока");
CustomThread second = new CustomThread("Второй", exchanger);
second.setMessage("Сообщение второго потока");
first.start();
second.start();
Die Konsole zeigt Folgendes an:
Der erste Thread hat die Nachricht erhalten: Nachricht des zweiten Threads Der zweite Thread hat die Nachricht erhalten: Nachricht des ersten Threads Der zweite Thread hat die Nachricht erhalten: Nachricht des zweiten Threads Der erste Thread hat die Nachricht erhalten: Nachricht des ersten Threads Der zweite Thread hat die Nachricht erhalten: Nachricht des ersten Threads Der erste Thread hat die Nachricht erhalten: Nachricht des zweiten Threads... .
Dies bedeutet, dass der Datenaustausch zwischen Threads erfolgreich ist.

112. Was ist der Unterschied zwischen der Thread-Klasse und der Runnable-Schnittstelle?

Das erste, was mir auffällt, ist , dass Thread eine Klasse und Runnable eine Schnittstelle ist, was ein sehr offensichtlicher Unterschied ist =D Analyse von Fragen und Antworten aus Interviews für Java-Entwickler.  Teil 13 - 3Ich sage auch, dass Thread Runnable (Komposition) verwendet . Das heißt, wir haben zwei Möglichkeiten:
  1. Von Thread erben , die run-Methode überschreiben, dann dieses Objekt erstellen und den Thread über die start()- Methode starten .

  2. Implementieren Sie Runnable in einer bestimmten Klasse, implementieren Sie deren run()- Methode und erstellen Sie dann ein Thread- Objekt , indem Sie diese Objektimplementierung der Runnable- Schnittstelle ihrem Konstruktor zuweisen . Nun, am Ende starten Sie das Thread- Objekt mit der start()- Methode .

Was ist vorzuziehen? Denken wir ein wenig nach:
  • Wenn Sie die Runnable- Schnittstelle implementieren , ändern Sie das Verhalten des Threads nicht. Im Wesentlichen geben Sie dem Thread nur etwas zum Laufen. Und das ist unsere Komposition, die wiederum als guter Ansatz gilt.

  • Durch die Implementierung von Runnable erhält Ihre Klasse mehr Flexibilität. Wenn Sie von Thread erben , erfolgt die Aktion, die Sie ausführen, immer im Thread. Aber wenn Sie Runnable implementieren , muss es nicht nur ein Thread sein. Schließlich können Sie es entweder in einem Thread ausführen oder an einen Executor-Dienst übergeben. Nun, oder übergeben Sie es einfach irgendwo als Aufgabe in einer Single-Thread-Anwendung.

  • Mit Runnable können Sie die Aufgabenausführung logisch von der Thread-Steuerungslogik trennen.

  • In Java ist nur eine einzelne Vererbung möglich, sodass nur eine Klasse erweitert werden kann. Gleichzeitig ist die Anzahl der erweiterbaren Schnittstellen unbegrenzt (na ja, nicht ganz unbegrenzt, aber 65535 , aber Sie werden diese Grenze wahrscheinlich nie erreichen).

Nun, es liegt an Ihnen, zu entscheiden, was genau Sie verwenden möchten ^^

113. Es gibt die Threads T1, T2 und T3. Wie kann man sie nacheinander umsetzen?Analyse von Fragen und Antworten aus Interviews für Java-Entwickler.  Teil 13 - 4

Das allererste und einfachste, was mir in den Sinn kommt, ist die Verwendung der Methode „join()“ . Es unterbricht die Ausführung des aktuellen Threads (der die Methode aufgerufen hat), bis die Ausführung des Threads, für den die Methode aufgerufen wurde, abgeschlossen ist. Erstellen wir unsere eigene Thread-Implementierung:
public class CustomThread extends Thread {
private String threadName;

 public CustomThread(final String  threadName){
   this.threadName = threadName;
 }

 @Override
 public void run() {
   System.out.println(threadName + " - начал свою работу");
   try {
     // происходит некая логика
     Thread.sleep(1000);
   } catch (InterruptedException e) {
     e.printStackTrace();
   }

   System.out.println(threadName + " - закончил свою работу");
 }
}
Lassen Sie uns nacheinander drei solcher Threads mit join() starten :
CustomThread t1 = new CustomThread("Первый поток");
t1.start();
t1.join();
CustomThread t2 = new CustomThread("Второй поток");
t2.start();
t2.join();
CustomThread t3 = new CustomThread("Третий поток");
t3.start();
t3.join();
Konsolenausgabe:
Der erste Thread – hat seine Arbeit begonnen. Der erste Thread – hat seine Arbeit beendet. Der zweite Thread – hat seine Arbeit begonnen. Der zweite Thread – hat seine Arbeit beendet. Der dritte Thread – hat seine Arbeit begonnen. Der dritte Thread – hat seine Arbeit beendet
Das bedeutet, dass wir unsere Aufgabe erfüllt haben. Als nächstes gehen wir direkt zu den praktischen Aufgaben auf der Junior- Ebene über .

Praktische Aufgaben

114. Matrix-Diagonalsumme (Leetcode-Problem)

Bedingung: Berechnen Sie die Summe aller Elemente auf der Hauptdiagonale und aller Elemente auf der Zusatzdiagonale, die nicht Teil der Hauptdiagonale sind. Analyse von Fragen und Antworten aus Interviews für Java-Entwickler.  Teil 13 - 51. Mit einer Matrix der Form: mat = [[1,2,3], [4,5,6], [7,8,9]] Die Ausgabe sollte - 25 sein. 2. Mit einer Matrix - mat = [[1,1 ,1,1], [1,1,1,1], [1,1,1,1], [1,1,1,1]] Die Ausgabe sollte - 8 3 sein. Mit a Matrix - mat = [[ 5]] Die Schlussfolgerung sollte lauten: - 5 Unterbrechen Sie das Lesen und setzen Sie Ihre Entscheidung um. Meine Lösung wäre folgende:
public static int countDiagonalSum(int[][] matrix) {
 int sum = 0;
 for (int i = 0, j = matrix.length - 1; i < matrix.length; i++, j--) {
   sum += matrix[i][i];
   if (j != i) {
     sum += matrix[i][j];
   }
 }
 return sum;
}
Alles geschieht mit einem Durchgang durch das Array, bei dem wir zwei Indizes für den Bericht haben: i – für die Meldung der Zeilen des Arrays und der Spalten der Hauptdiagonale, j – für die Meldung der Spalten der Zusatzdiagonale. Wenn die Zelle der Hauptdiagonale und die Zusatzdiagonale zusammenfallen, wird einer der Werte bei der Berechnung der Summe ignoriert. Überprüfen wir anhand der Matrizen der Bedingung:
int[][] arr1 = {
   {1, 2, 3},
   {4, 5, 6},
   {7, 8, 9}};
System.out.println(countDiagonalSum(arr1));

int[][] arr2 = {
   {1, 1, 1, 1},
   {1, 1, 1, 1},
   {1, 1, 1, 1},
   {1, 1, 1, 1}};
System.out.println(countDiagonalSum(arr2));

int[][] arr3 = {{5}};
System.out.println(countDiagonalSum(arr3));
Konsolenausgabe:
25 8 5

115. Nullen verschieben (Leetcode-Herausforderung)

Bedingung: Verschieben Sie in einem ganzzahligen Array alle Nullen an das Ende und behalten Sie dabei die relative Reihenfolge der Elemente ungleich Null bei. 1. Mit einem Array: [0,1,0,3,12] Die Ausgabe sollte sein: [1,3,12,0,0] 2. Mit einem Array: [0] Die Ausgabe sollte sein: [0] Halten Sie inne und schreiben Sie meine Entscheidung auf ... Meine Entscheidung:
public static void moveZeroes(int[] nums) {
 int counterWithoutNulls = 0;
 int counterWithNulls = 0;
 int length = nums.length;
 while (counterWithNulls < length) {
   if (nums[counterWithNulls] == 0) {// находим нулевые элементы и увеличиваем счётчик
     counterWithNulls++;
   } else { // сдвигаем элементы на количество найденных нулевых элементов слева
     nums[counterWithoutNulls++] = nums[counterWithNulls++];
   }
 }
 while (counterWithoutNulls < length) {
   nums[counterWithoutNulls++] = 0;// заполняем последние элементы массива нулями согласно счётчику нулей
 }
}
Untersuchung:
int[] arr1 = {1, 2, 0, 0, 12, 9};
moveZeroes(arr1);
System.out.println(Arrays.toString(arr1));

int[] arr2 = {0};
moveZeroes(arr2);
System.out.println(Arrays.toString(arr2));
Konsolenausgabe:
[1, 2, 12, 9, 0, 0] [0]

116. Gegebene Listen-<String>-Namen. Entfernen Sie den ersten Buchstaben jedes Namens und drehen Sie die sortierte Liste

1. Das erste, was mir in den Sinn kommt, sind die Methoden der Collections- Klasse , die viele Hilfsmethoden für Sammlungen enthält:
public static List<String> processTheList(List<String> nameList) {
 for (int i = 0; i < nameList.size(); i++) {
   nameList.set(i, nameList.get(i).substring(1));
 }
 Collections.sort(nameList);
 return nameList;
}
2. Wenn wir außerdem Java Version 8 und höher verwenden, müssen wir die Lösung einfach über Streams anzeigen:
public static List<String> processTheList(List<String> nameList) {
 return nameList.stream()
     .map(x -> x.substring(1))
     .sorted().collect(Collectors.toList());
}
Unabhängig von der gewählten Lösung kann die Prüfung wie folgt aussehen:
List<String> nameList = new ArrayList();
nameList.add("John");
nameList.add("Bob");
nameList.add("Anna");
nameList.add("Dmitriy");
nameList.add("Peter");
nameList.add("David");
nameList.add("Igor");

System.out.println(processTheList(nameList));
Konsolenausgabe:
[avid, eter, gor, mitriy, nna, ob, ohn]

117. Drehen Sie das Array um

Lösung 1 Auch hier fällt mir als Erstes die Verwendung der Methoden der Hilfsdienstprogrammklasse Collections ein . Da wir aber ein Array haben, müssen wir es zunächst in eine Sammlung (Liste) umwandeln:
public static Integer[] reverse(Integer[] arr) {
 List<Integer> list = Arrays.asList(arr);
 Collections.reverse(list);
 return list.toArray(arr);
}
Lösung 2 Da es sich bei der Frage um ein Array handelte, halte ich es für notwendig, die Lösung zu zeigen, ohne vorgefertigte, sofort einsatzbereite Funktionen zu verwenden, und zwar sozusagen nach den Klassikern:
public static Integer[] reverse(Integer[] arr) {
 for (int i = 0; i < arr.length / 2; i++) {
   int temp = arr[i];
   arr[i] = arr[arr.length - 1 - i];
   arr[arr.length - 1 - i] = temp;
 }
 return arr;
}
Untersuchung:
Integer[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9};
System.out.println(Arrays.toString(reverse(arr)));
Konsolenausgabe:
[9, 8, 7, 6, 5, 4, 3, 2, 1]

118. Überprüfen Sie, ob eine Zeichenfolge ein Palindrom ist

Analyse von Fragen und Antworten aus Interviews für Java-Entwickler.  Teil 13 - 6Lösung 1 Es lohnt sich, sich sofort an StringBuilder zu erinnern : Es ist flexibler und verfügt über zahlreiche verschiedene Methoden im Vergleich zum regulären String . Uns interessiert vor allem die umgekehrte Methode :
public static boolean isPalindrome(String string) {
 string = string.toLowerCase(); //приводит всю строку к нижнему регистру
 StringBuilder builder = new StringBuilder();
 builder.append(string);
 builder.reverse(); // перевочиваем строку методом Builder-а
 return (builder.toString()).equals(string);
}
Lösung: Der nächste Ansatz besteht darin, die standardmäßigen „Lücken“ nicht zu nutzen. Wir vergleichen die Zeichen von hinten mit den entsprechenden Zeichen von vorne:
public static boolean isPalindrome(String string) {
  string = string.toLowerCase();
 int length = string.length();
 int fromBeginning = 0;
 int fromEnd = length - 1;
 while (fromEnd > fromBeginning) {
   char forwardChar = string.charAt(fromBeginning++);
   char backwardChar = string.charAt(fromEnd--);
   if (forwardChar != backwardChar)
     return false;
 }
 return true;
}
Und wir prüfen beide Ansätze:
boolean isPalindrome = isPalindrome("Tenet");
System.out.println(isPalindrome);
Konsolenausgabe:
WAHR

119. Schreiben Sie einen einfachen Sortieralgorithmus (Bubble, Selection oder Shuttle). Wie kann es verbessert werden?

Als einfachen Algorithmus zur Implementierung habe ich die Auswahlsortierung gewählt – Selection Sort:
public static void selectionSorting(int[] arr) {
 for (int i = 0; i < arr.length - 1; i++) {
   int min = i;
   for (int j = i + 1; j < arr.length; j++) {
     if (arr[j] < arr[min]) {
       min = j; // выбираем минимальный элемент в текущем числовом отрезке
     }
   }
   int temp = arr[min]; // меняем местами минимальный элемент с элементом под индексом i
   arr[min] = arr[i]; // так Wie отрезок постоянно уменьшается
   arr[i] = temp; // и выпадающие из него числа будут минимальными в текущем отрезке
 } // и Wie итог - числа оставшиеся вне текущей итерации отсортированы от самого наименьшего к большему
}
Die verbesserte Version würde so aussehen:
public static void improvedSelectionSorting(int[] arr) {
 for (int i = 0, j = arr.length - 1; i < j; i++, j--) { // рассматриваемый отрезок с каждой итерацией
   // будет уменьшаться с ДВУХ сторон по одному элементу
   int min = arr[i];
   int max = arr[i];
   int minIndex = i;
   int maxIndex = i;
   for (int n = i; n <= j; n++) { // выбираем min и max на текущем отрезке
     if (arr[n] > max) {
       max = arr[n];
       maxIndex = n;
     } else if (arr[n] < min) {
       min = arr[n];
       minIndex = n;
     }
   }
   // меняем найденный минимальный элемент с позиции с индексом min на позицию с индексом i
   swap(arr, i, minIndex);

   if (arr[minIndex] == max) {// срабатывает, если элемент max оказался смещен предыдущей перестановкой -
     swap(arr, j, minIndex); // на старое место min, поэтому с позиции с индексом min смещаем его на позицию j
   } else {
     swap(arr, j, maxIndex); // простое обмен местами элементов с индексами max и j
   }
 }
}

static int[] swap(int[] arr, int i, int j) {
 int temp = arr[i];
 arr[i] = arr[j];
 arr[j] = temp;
 return arr;
}
Nun müssen wir uns vergewissern, ob sich die Sortierung wirklich verbessert hat. Vergleichen wir die Leistung:
long firstDifference = 0;
long secondDifference = 0;
long primaryTime;
int countOfApplying = 10000;
for (int i = 0; i < countOfApplying; i++) {
 int[] arr1 = {234, 33, 123, 4, 5342, 76, 3, 65,
     3, 5, 35, 75, 255, 4, 46, 48, 4658, 44, 22,
     678, 324, 66, 151, 268, 433, 76, 372, 45, 13,
     9484, 499959, 567, 774, 473, 3, 32, 865, 67, 43,
     63, 332, 24, 1};
 primaryTime = System.nanoTime();
 selectionSorting(arr1);
 firstDifference += System.nanoTime() - primaryTime;

 int[] arr2 = {234, 33, 123, 4, 5342, 76, 3, 65,
     3, 5, 35, 75, 255, 4, 46, 48, 4658, 44, 22,
     678, 324, 66, 151, 268, 433, 76, 372, 45, 13,
     9484, 499959, 567, 774, 473, 3, 32, 865, 67, 43,
     63, 332, 24, 1};
 primaryTime = System.nanoTime();
 improvedSelectionSorting(arr2);
 secondDifference += System.nanoTime() - primaryTime;
}

System.out.println(((double) firstDifference / (double) secondDifference - 1) * 100 + "%");
Beide Sorten begannen im selben Zyklus, weil Wenn es separate Schleifen gäbe, würde die Sortierung im obigen Code ein schlechteres Ergebnis liefern, als wenn sie an zweiter Stelle platziert würde. Das liegt daran, dass sich das Programm „aufwärmt“ und dann etwas schneller arbeitet. Aber ich gehe ein wenig vom Thema ab. Nach fünf Durchläufen dieser Überprüfung in der Konsole sah ich eine Leistungssteigerung um: 36,41006735635892 % 51,46131097160771 % 41,88918834013988 % 48,091980705743566 % 37,120220461591444 % Für mich ist das ziemlich gut Ergebnis. Analyse von Fragen und Antworten aus Interviews für Java-Entwickler.  Teil 13 - 7

120. Schreiben Sie einen Algorithmus (Abfolge von Aktionen) zum Zusammensetzen eines Literals vom Typ int mit einem Literal vom Typ byte. Erklären Sie, was mit dem Gedächtnis passiert

  1. Der Bytewert wird in int konvertiert. Dafür wird nicht 1 Byte Speicher zugewiesen, sondern wie bei allen int-Werten – 4, wenn dieser Wert noch nicht auf dem int-Stack liegt. Wenn ja, wird einfach ein Link dazu empfangen.

  2. Es werden zwei int-Werte addiert und der dritte erhalten. Dafür wird ein neuer Speicherabschnitt zugewiesen – 4 Bytes (oder es wird ein Verweis vom Int-Stack auf den vorhandenen Wert empfangen).

    In diesem Fall wird der Speicher von zwei Ints weiterhin belegt und ihre Werte werden jeweils auf dem Int-Stack gespeichert.

Eigentlich enden hier die Junior-Fragen aus unserer Liste. Ab dem nächsten Artikel werden wir uns mit Problemen der mittleren Ebene befassen. Ich möchte darauf hinweisen, dass Fragen der mittleren Ebene auch aktiv an Einsteiger-Entwickler (Junior) gestellt werden. Also bleibt gespannt. So, das ist alles für heute: Wir sehen uns!Analyse von Fragen und Antworten aus Interviews für Java-Entwickler.  Teil 13 - 8
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION