JavaRush /Blog Java /Random-FR /Analyse des questions et réponses des entretiens pour dév...

Analyse des questions et réponses des entretiens pour développeur Java. Partie 13

Publié dans le groupe Random-FR
Bonjour!
Le mouvement vers un but est avant tout un mouvement.
Il ne suffit donc pas de penser que vous voulez réaliser quelque chose. Vous devez faire quelque chose - même les plus petites étapes - mais faites-le tous les jours, et ce n'est qu'ainsi que vous atteindrez l'objectif final. Et puisque vous êtes ici pour devenir développeurs Java, vous devez faire au moins un pas minimal pour approfondir vos connaissances de Java chaque jour. Pour l'étape Java d'aujourd'hui, je vous suggère de vous familiariser avec la nouvelle partie de l'analyse des questions d'entretien les plus populaires pour les développeurs. Analyse des questions et réponses des entretiens pour développeur Java.  Partie 13 - 1Aujourd'hui, nous allons passer en revue la partie pratique des questions destinées aux spécialistes juniors. Une tâche pratique lors d’un entretien n’est pas rare. Il est important de ne pas se perdre dans une telle situation, d’essayer de garder la tête froide et de proposer la solution optimale, voire plusieurs. Je recommanderais également de ne pas rester silencieux lorsque vous résolvez un problème, mais de commenter votre réflexion et d'écrire la solution, ou après avoir écrit, d'expliquer avec des mots ce que vous avez fait et pourquoi. Cela vous fera apprécier l'intervieweur bien plus qu'une décision silencieuse. Alors, commençons!

111. Comment échanger des données entre les threads ?

Analyse des questions et réponses des entretiens pour développeur Java.  Partie 13 - 2Pour échanger des données entre les threads, vous pouvez utiliser de nombreuses approches et moyens différents : par exemple, utiliser des variables atomiques, des collections synchronisées et un sémaphore. Mais pour résoudre ce problème, je vais donner un exemple avec Exchanger . Exchanger est une classe de synchronisation du package concurrent qui facilite l'échange d'éléments entre une paire de threads en créant un point de synchronisation commun. Son utilisation simplifie l'échange de données entre deux threads. Son fonctionnement est assez simple : il attend que deux threads distincts appellent sa méthode Exchange() . Quelque chose comme un point d'échange se crée entre eux : le premier fil pose son objet et reçoit en retour l'objet de l'autre, et ce dernier, à son tour, reçoit l'objet du premier et met le sien. Autrement dit, le premier thread utilise la méthode Exchange() et reste inactif jusqu'à ce qu'un autre thread appelle la méthode Exchange() sur le même objet et que des données soient échangées entre eux. À titre d'exemple, considérons l'implémentation suivante de la 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();
     }
   }
 }
}
Dans le constructeur de thread, nous définissons un objet Exchanger qui accepte les objets de type String , et au démarrage (dans la méthode run ) nous utilisons son Exchange() pour échanger un message avec un autre thread qui utilise cette méthode dans le même Exchanger . Exécutons-le dans 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 affichera :
Le premier thread a reçu le message : Message du deuxième thread Le deuxième thread a reçu le message : Message du premier thread Le deuxième thread a reçu le message : Message du deuxième thread Le premier thread a reçu le message : Message du premier thread Le deuxième le thread a reçu le message : Message du premier thread Le premier thread a reçu le message : Message du deuxième thread... .
Cela signifie que l'échange de données entre les threads réussit.

112. Quelle est la différence entre la classe Thread et l'interface Runnable ?

La première chose que je noterai est que Thread est une classe, Runnable est une interface, ce qui est une différence très évidente =D Analyse des questions et réponses des entretiens pour développeur Java.  Partie 13 - 3Je dirai aussi que Thread utilise Runnable (composition). Autrement dit, nous avons deux manières :
  1. Héritez de Thread , remplacez la méthode run , puis créez cet objet et démarrez le thread via la méthode start() .

  2. Implémentez Runnable dans une certaine classe, implémentez sa méthode run() , puis créez un objet Thread , en attribuant cette implémentation d'objet de l' interface Runnable à son constructeur . Eh bien, à la fin, lancez l' objet Thread en utilisant la méthode start() .

Qu'est-ce qui est préférable ? Réfléchissons un peu :
  • Lorsque vous implémentez l' interface Runnable , vous ne modifiez pas le comportement du thread. Essentiellement, vous donnez simplement au fil quelque chose à exécuter. Et c’est notre composition, qui à son tour est considérée comme une bonne approche.

  • l'implémentation de Runnable donne plus de flexibilité à votre classe. Si vous héritez de Thread , alors l'action que vous effectuez sera toujours sur le thread. Mais si vous implémentez Runnable, il n’est pas nécessaire que ce soit simplement un thread. Après tout, vous pouvez soit l'exécuter dans un thread, soit le transmettre à un service exécuteur. Eh bien, ou transmettez-le simplement quelque part en tant que tâche dans une application monothread.

  • L'utilisation de Runnable vous permet de séparer logiquement l'exécution des tâches de la logique de contrôle des threads.

  • En Java, un seul héritage est possible, donc une seule classe peut être étendue. Dans le même temps, le nombre d'interfaces extensibles est illimité (enfin, pas tout à fait illimité, mais 65 535 , mais il est peu probable que vous atteigniez jamais cette limite).

Eh bien, c'est à vous de décider ce qu'il est préférable d'utiliser exactement ^^

113. Il existe des fils T1, T2 et T3. Comment les mettre en œuvre de manière séquentielle ?Analyse des questions et réponses des entretiens pour développeur Java.  Partie 13 - 4

La toute première et la plus simple chose qui me vient à l’esprit est d’utiliser la méthode join() . Il suspend l'exécution du thread actuel (qui a appelé la méthode) jusqu'à ce que le thread sur lequel la méthode a été appelée termine son exécution. Créons notre propre implémentation de 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 + " - закончил свою работу");
 }
}
Commençons trois de ces threads un par un en utilisant 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();
Sortie de la console :
Le premier fil - a commencé son travail Le premier fil - a terminé son travail Le deuxième fil - a commencé son travail Le deuxième fil - a terminé son travail Le troisième fil - a commencé son travail Le troisième fil - a terminé son travail
Cela signifie que nous avons terminé notre tâche. Ensuite, nous passons directement aux tâches pratiques au niveau Junior .

Tâches pratiques

114. Somme diagonale matricielle (problème Leetcode)

Condition : Calculer la somme de tous les éléments de la diagonale principale et de tous les éléments de la diagonale supplémentaire qui ne font pas partie de la diagonale principale. Analyse des questions et réponses des entretiens pour développeur Java.  Partie 13 - 51. Avec une matrice de la forme : mat = [[1,2,3], [4,5,6], [7,8,9]] Le résultat doit être - 25 2. Avec une matrice - mat = [[1,1 ,1,1], [1,1,1,1], [1,1,1,1], [1,1,1,1]] La sortie doit être - 8 3. Avec une matrice - mat = [[ 5]] La conclusion devrait être - 5 Arrêtez la lecture et mettez en œuvre votre décision. Ma solution serait la suivante :
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;
}
Tout se passe avec un seul passage à travers le tableau, au cours duquel nous avons deux index pour le rapport : i - pour rapporter les lignes du tableau et les colonnes de la diagonale principale, j - pour rapporter les colonnes de la diagonale supplémentaire. Si la cellule de la diagonale principale et celle supplémentaire coïncident, alors l'une des valeurs est ignorée lors du calcul de la somme. Vérifions en utilisant les matrices de la condition :
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));
Sortie de la console :
25 8 5

115. Déplacer les zéros (défi Leetcode)

Condition : dans un tableau d'entiers, déplacez tous les 0 vers la fin, en conservant l'ordre relatif des éléments non nuls. 1. Avec un tableau : [0,1,0,3,12] La sortie doit être : [1,3,12,0,0] 2. Avec un tableau : [0] La sortie doit être : [0] Faites une pause et écrivez ma décision... Ma décision :
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;// заполняем последние элементы массива нулями согласно счётчику нулей
 }
}
Examen:
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));
Sortie de la console :
[1, 2, 12, 9, 0, 0] [0]

116. Noms de liste <String> donnés. Supprimez la première lettre de chaque nom et faites pivoter la liste triée

1. La première chose qui vient à l'esprit, ce sont les méthodes de la classe Collections , qui contiennent de nombreuses méthodes auxiliaires pour les collections :
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. De plus, si nous utilisons Java version 8 et supérieure, nous devons simplement montrer la solution via des flux :
public static List<String> processTheList(List<String> nameList) {
 return nameList.stream()
     .map(x -> x.substring(1))
     .sorted().collect(Collectors.toList());
}
Quelle que soit la solution choisie, le contrôle peut être le suivant :
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));
Sortie de la console :
[avide, eter, gor, mitriy, nna, ob, ohn]

117. Retournez le tableau

Solution 1 Encore une fois, la première chose qui vient à l'esprit est d'utiliser les méthodes de la classe utilitaire auxiliaire Collections . Mais puisque nous avons un tableau, nous devons d’abord le convertir en une collection (liste) :
public static Integer[] reverse(Integer[] arr) {
 List<Integer> list = Arrays.asList(arr);
 Collections.reverse(list);
 return list.toArray(arr);
}
Solution 2 Puisque la question concernait un tableau, je pense qu'il est nécessaire de montrer la solution sans utiliser de fonctionnalités toutes faites, et pour ainsi dire, selon les classiques :
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;
}
Examen:
Integer[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9};
System.out.println(Arrays.toString(reverse(arr)));
Sortie de la console :
[9, 8, 7, 6, 5, 4, 3, 2, 1]

118. Vérifiez si une chaîne est un palindrome

Analyse des questions et réponses des entretiens pour développeur Java.  Partie 13 - 6Solution 1 Il convient de rappeler immédiatement StringBuilder : il est plus flexible et riche en méthodes diverses que le String classique . Nous sommes particulièrement intéressés par la méthode inverse :
public static boolean isPalindrome(String string) {
 string = string.toLowerCase(); //приводит всю строку к нижнему регистру
 StringBuilder builder = new StringBuilder();
 builder.append(string);
 builder.reverse(); // перевочиваем строку методом Builder-а
 return (builder.toString()).equals(string);
}
Solution : La prochaine approche consistera à ne pas utiliser les « échappatoires » prêtes à l'emploi. Nous comparons les caractères du début de la chaîne avec les caractères correspondants du début :
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;
}
Et nous vérifions les deux approches :
boolean isPalindrome = isPalindrome("Tenet");
System.out.println(isPalindrome);
Sortie de la console :
vrai

119. Écrivez un algorithme de tri simple (Bulle, Sélection ou Navette). Comment peut-il être amélioré ?

Comme algorithme simple d'implémentation, j'ai choisi le tri par sélection - 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 version améliorée ressemblerait à ceci :
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;
}
Eh bien, il faut maintenant vérifier si le tri s'est vraiment amélioré. Comparons les performances :
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 + "%");
Les deux types ont commencé dans le même cycle, car s'il y avait des boucles séparées, le tri dans le code ci-dessus afficherait un résultat pire que s'il était placé en deuxième position. Cela est dû au fait que le programme « se réchauffe » puis fonctionne un peu plus vite. Mais je sors un peu du sujet. Après cinq exécutions de cette vérification dans la console, j'ai constaté une augmentation des performances de : 36,41006735635892 % 51,46131097160771 % 41,88918834013988 % 48,091980705743566 % 37,120220461591444 % Quant à moi, c'est un plutôt bon résultat. Analyse des questions et réponses des entretiens pour développeur Java.  Partie 13 à 7

120. Écrivez un algorithme (séquence d'actions) pour composer un littéral de type int avec un littéral de type byte. Expliquer ce qui arrive à la mémoire

  1. la valeur de l'octet est convertie en int. Pas 1 octet de mémoire ne lui sera alloué, mais comme toutes les valeurs int - 4, si cette valeur n'est pas encore sur la pile int. Si tel est le cas, un lien vers celui-ci sera simplement reçu.

  2. Deux valeurs int seront ajoutées et la troisième sera obtenue. Une nouvelle section de mémoire lui sera allouée - 4 octets (ou une référence sera reçue de la pile int à la valeur existante).

    Dans ce cas, la mémoire de deux ints sera toujours occupée et leurs valeurs seront respectivement stockées sur la pile int.

En fait, c'est là que se terminent les questions de niveau junior de notre liste. À partir du prochain article, nous comprendrons les problèmes de niveau intermédiaire. Je voudrais noter que les questions de niveau intermédiaire sont également activement posées aux développeurs débutants - Junior. Alors restez à l'écoute. Eh bien, c'est tout pour aujourd'hui : à bientôt !Analyse des questions et réponses des entretiens pour développeur Java.  Partie 13 à 8
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION