JavaRush /Java Blog /Random-IT /Analisi di domande e risposte da interviste per sviluppat...

Analisi di domande e risposte da interviste per sviluppatori Java. Parte 13

Pubblicato nel gruppo Random-IT
Ciao!
Il movimento verso un obiettivo è, prima di tutto, movimento.
Pertanto, non è sufficiente pensare di voler ottenere qualcosa. Devi fare qualcosa, anche i passi più piccoli, ma fallo ogni giorno, e solo così raggiungerai l'obiettivo finale. E poiché sei qui per diventare sviluppatore Java, devi fare almeno un passo minimo per approfondire ogni giorno la tua conoscenza di Java. Per il passaggio Java di oggi, ti suggerisco di familiarizzare con la nuova parte dell'analisi delle domande più popolari nell'intervista per gli sviluppatori. Analisi di domande e risposte da interviste per sviluppatori Java.  Parte 13 - 1Oggi affronteremo la parte pratica delle domande per gli specialisti Junior. Un compito pratico durante un colloquio non è raro. È importante non perdersi in una situazione del genere, cercare di mantenere la calma e offrire la soluzione ottimale, o anche più soluzioni. Consiglierei inoltre di non restare in silenzio quando si risolve un problema, ma di commentare il filo del proprio pensiero e scrivere la soluzione, oppure dopo aver scritto, spiegare a parole cosa hai fatto e perché. Questo ti renderà caro all'intervistatore molto più di una decisione silenziosa. Quindi iniziamo!

111. Come scambiare dati tra thread?

Analisi di domande e risposte da interviste per sviluppatori Java.  Parte 13 - 2Per scambiare dati tra thread, è possibile utilizzare molti approcci e mezzi diversi: ad esempio, utilizzare variabili atomiche, raccolte sincronizzate e un semaforo. Ma per risolvere questo problema, darò un esempio con Exchanger . Exchanger è una classe di sincronizzazione del pacchetto concurrent che facilita lo scambio di elementi tra una coppia di thread creando un punto di sincronizzazione comune. Il suo utilizzo semplifica lo scambio di dati tra due thread. Il modo in cui funziona è abbastanza semplice: attende che due thread separati chiamino il suo metodo exchange() . Tra loro si crea qualcosa come un punto di scambio: il primo filo mette il suo oggetto e riceve in cambio l'oggetto dell'altro, e quest'ultimo, a sua volta, riceve l'oggetto del primo e mette il proprio. Cioè, il primo thread utilizza il metodo exchange() ed è inattivo finché un altro thread non chiama il metodo exchange() sullo stesso oggetto e i dati vengono scambiati tra loro. Ad esempio, considera la seguente implementazione della classe Thread :
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();
     }
   }
 }
}
Nel costruttore del thread, definiamo un oggetto Exchanger che accetta oggetti di tipo String e all'avvio (nel metodo run ) utilizziamo il suo exchange() per scambiare un messaggio con un altro thread che utilizza questo metodo nello stesso Exchanger . Eseguiamolo in main :
Exchanger<String> exchanger = new Exchanger<>();
CustomThread first = new CustomThread("Первый ", exchanger);
first.setMessage("Сообщение первого потока");
CustomThread second = new CustomThread("Второй", exchanger);
second.setMessage("Сообщение второго потока");
first.start();
second.start();
La console visualizzerà:
Il primo thread ha ricevuto il messaggio: Messaggio del secondo thread Il secondo thread ha ricevuto il messaggio: Messaggio del primo thread Il secondo thread ha ricevuto il messaggio: Messaggio del secondo thread Il primo thread ha ricevuto il messaggio: Messaggio del primo thread Il secondo il thread ha ricevuto il messaggio: Messaggio del primo thread Il primo thread ha ricevuto il messaggio: Messaggio del secondo thread... .
Ciò significa che lo scambio di dati tra thread ha esito positivo.

112. Qual è la differenza tra la classe Thread e l'interfaccia Runnable?

La prima cosa che noterò è che Thread è una classe, Runnable è un'interfaccia, il che è una differenza molto evidente =D Analisi di domande e risposte da interviste per sviluppatori Java.  Parte 13 - 3Dirò anche che Thread usa Runnable (composizione). Abbiamo cioè due strade:
  1. Eredita da Thread , sovrascrivi il metodo run, quindi crea questo oggetto e avvia il thread tramite il metodo start() .

  2. Implementa Runnable in una determinata classe, implementa il suo metodo run() e quindi crea un oggetto Thread , assegnando l'implementazione di questo oggetto dell'interfaccia Runnable al suo costruttore . Bene, alla fine, avvia l' oggetto Thread utilizzando il metodo start() .

Cosa è preferibile? Pensiamo un po':
  • Quando implementi l' interfaccia Runnable , non modifichi il comportamento del thread. Essenzialmente stai semplicemente dando al thread qualcosa da eseguire. E questa è la nostra composizione, che a sua volta è considerata un buon approccio.

  • l'implementazione di Runnable offre maggiore flessibilità alla tua classe. Se erediti da Thread , l'azione che esegui sarà sempre sul thread. Ma se implementi Runnable non deve essere solo un thread. Dopotutto, puoi eseguirlo in un thread o passarlo a qualche servizio esecutore. Bene, o semplicemente passalo da qualche parte come attività in un'applicazione a thread singolo.

  • L'utilizzo di Runnable consente di separare logicamente l'esecuzione dell'attività dalla logica di controllo del thread.

  • In Java è possibile solo l'ereditarietà singola, quindi è possibile estendere solo una classe. Allo stesso tempo, il numero di interfacce espandibili è illimitato (beh, non del tutto illimitato, ma 65535 , ma è improbabile che tu raggiunga mai questo limite).

Bene, sta a te decidere cosa è esattamente preferibile usare ^^

113. Ci sono fili T1, T2 e T3. Come implementarli in sequenza?Analisi di domande e risposte da interviste per sviluppatori Java.  Parte 13 - 4

La prima e più semplice cosa che mi viene in mente è usare il metodo join() . Sospende l'esecuzione del thread corrente (che ha chiamato il metodo) finché il thread su cui è stato chiamato il metodo non termina l'esecuzione. Creiamo la nostra implementazione del thread:
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 + " - закончил свою работу");
 }
}
Iniziamo tre di questi thread uno per uno utilizzando join() :
CustomThread t1 = new CustomThread("Первый поток");
t1.start();
t1.join();
CustomThread t2 = new CustomThread("Второй поток");
t2.start();
t2.join();
CustomThread t3 = new CustomThread("Третий поток");
t3.start();
t3.join();
Uscita console:
Il primo thread - ha iniziato il suo lavoro Il primo thread - ha terminato il suo lavoro Il secondo thread - ha iniziato il suo lavoro Il secondo thread - ha terminato il suo lavoro Il terzo thread - ha iniziato il suo lavoro Il terzo thread - ha terminato il suo lavoro
Ciò significa che abbiamo completato il nostro compito. Successivamente, passiamo direttamente alle attività pratiche a livello Junior .

Compiti pratici

114. Somma diagonale della matrice (problema Leetcode)

Condizione: calcola la somma di tutti gli elementi sulla diagonale principale e di tutti gli elementi sulla diagonale aggiuntiva che non fanno parte della diagonale principale. Analisi di domande e risposte da interviste per sviluppatori Java.  Parte 13 - 51. Con una matrice della forma: mat = [[1,2,3], [4,5,6], [7,8,9]] L'output dovrebbe essere - 25 2. Con una matrice - mat = [[1,1 ,1,1], [1,1,1,1], [1,1,1,1], [1,1,1,1]] L'output dovrebbe essere - 8 3. Con una matrice - mat = [[ 5]] La conclusione dovrebbe essere - 5 Metti in pausa la lettura e attua la tua decisione. La mia soluzione sarebbe la seguente:
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;
}
Tutto avviene con un passaggio attraverso l'array, durante il quale abbiamo due indici per il rapporto: i - per riportare le righe dell'array e le colonne della diagonale principale, j - per riportare le colonne della diagonale aggiuntiva. Se la cella della diagonale principale e quella aggiuntiva coincidono, uno dei valori viene ignorato nel calcolo della somma. Controlliamo utilizzando le matrici dalla condizione:
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));
Uscita console:
2585

115. Sposta gli zeri (sfida Leetcode)

Condizione: in un array di numeri interi, sposta tutti gli 0 alla fine, mantenendo l'ordine relativo degli elementi diversi da zero. 1. Con un array: [0,1,0,3,12] L'output dovrebbe essere: [1,3,12,0,0] 2. Con un array: [0] L'output dovrebbe essere: [0] Metti in pausa e scrivi la mia decisione... La mia decisione:
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;// заполняем последние элементы массива нулями согласно счётчику нулей
 }
}
Visita medica:
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));
Uscita console:
[1, 2, 12, 9, 0, 0] [0]

116. Elenco specificato <String> nomi. Rimuovi la prima lettera da ciascun nome e ruota l'elenco ordinato

1. La prima cosa che mi viene in mente sono i metodi della classe Collections , che contiene molti metodi ausiliari per le raccolte:
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. Inoltre, se utilizziamo Java versione 8 e successive, dobbiamo semplicemente mostrare la soluzione tramite flussi:
public static List<String> processTheList(List<String> nameList) {
 return nameList.stream()
     .map(x -> x.substring(1))
     .sorted().collect(Collectors.toList());
}
Qualunque sia la soluzione scelta, la verifica potrà essere la seguente:
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));
Uscita console:
[avid, eter, gor, mitriy, nna, ob, ohn]

117. Capovolgi l'array

Soluzione 1 Ancora una volta, la prima cosa che viene in mente è utilizzare i metodi della classe di utilità ausiliaria Collections . Ma poiché abbiamo un array, dobbiamo prima convertirlo in una raccolta (elenco):
public static Integer[] reverse(Integer[] arr) {
 List<Integer> list = Arrays.asList(arr);
 Collections.reverse(list);
 return list.toArray(arr);
}
Soluzione 2 Poiché la domanda riguardava un array, penso che sia necessario mostrare la soluzione senza utilizzare funzionalità già pronte, e per così dire, secondo i classici:
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;
}
Visita medica:
Integer[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9};
System.out.println(Arrays.toString(reverse(arr)));
Uscita console:
[9, 8, 7, 6, 5, 4, 3, 2, 1]

118. Controlla se una stringa è palindroma

Analisi di domande e risposte da interviste per sviluppatori Java.  Parte 13 - 6Soluzione 1 Vale subito la pena ricordare StringBuilder : è più flessibile e ricco di vari metodi rispetto al normale String . Siamo particolarmente interessati al metodo inverso :
public static boolean isPalindrome(String string) {
 string = string.toLowerCase(); //приводит всю строку к нижнему регистру
 StringBuilder builder = new StringBuilder();
 builder.append(string);
 builder.reverse(); // перевочиваем строку методом Builder-а
 return (builder.toString()).equals(string);
}
Soluzione: il prossimo approccio sarà senza utilizzare le “scappatoie” predefinite. Confrontiamo i caratteri dal retro della stringa con i caratteri corrispondenti dal davanti:
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;
}
E controlliamo entrambi gli approcci:
boolean isPalindrome = isPalindrome("Tenet");
System.out.println(isPalindrome);
Uscita console:
VERO

119. Scrivi un semplice algoritmo di ordinamento (Bubble, Selection o Shuttle). Come può essere migliorato?

Come semplice algoritmo per l'implementazione, ho scelto l'ordinamento di selezione - 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]; // так How отрезок постоянно уменьшается
   arr[i] = temp; // и выпадающие из него числа будут минимальными в текущем отрезке
 } // и How итог - числа оставшиеся вне текущей итерации отсортированы от самого наименьшего к большему
}
La versione migliorata sarebbe simile a questa:
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;
}
Bene, ora dobbiamo assicurarci se lo smistamento è davvero migliorato. Confrontiamo le prestazioni:
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 + "%");
Entrambi i tipi sono iniziati nello stesso ciclo, perché se ci fossero cicli separati, l'ordinamento dal codice sopra mostrerebbe un risultato peggiore rispetto a se fosse posizionato per secondo. Ciò è dovuto al fatto che il programma si “riscalda” e quindi funziona un po’ più velocemente. Ma vado un po' fuori tema. Dopo cinque esecuzioni di questo controllo nella console, ho notato un aumento delle prestazioni di: 36.41006735635892% 51.46131097160771% 41.88918834013988% 48.091980705743566% 37.120220461591444% Per quanto mi riguarda, questo è abbastanza buono risultato. Analisi di domande e risposte da interviste per sviluppatori Java.  Parte 13 - 7

120. Scrivere un algoritmo (sequenza di azioni) per comporre un letterale di tipo int con un letterale di tipo byte. Spiegare cosa succede alla memoria

  1. il valore del byte viene convertito in int. Non verrà allocato 1 byte di memoria, ma come tutti i valori int - 4, se questo valore non è ancora nello stack int. Se c'è, verrà semplicemente ricevuto un collegamento ad esso.

  2. Verranno aggiunti due valori int e si otterrà il terzo. Verrà allocata una nuova sezione di memoria - 4 byte (o verrà ricevuto un riferimento dallo stack int al valore esistente).

    In questo caso, la memoria di due int sarà comunque occupata e i loro valori verranno archiviati rispettivamente nello stack int.

In realtà, è qui che finiscono le domande di livello Junior del nostro elenco. A partire dal prossimo articolo, comprenderemo le problematiche di livello medio. Vorrei sottolineare che le domande di livello medio vengono poste attivamente anche agli sviluppatori entry-level - Junior. Quindi rimanete sintonizzati. Bene, per oggi è tutto: ci vediamo!Analisi di domande e risposte da interviste per sviluppatori Java.  Parte 13 - 8
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION