JavaRush /Blogue Java /Random-PT /Análise de perguntas e respostas de entrevistas para dese...

Análise de perguntas e respostas de entrevistas para desenvolvedor Java. Parte 13

Publicado no grupo Random-PT
Olá!
O movimento em direção a um objetivo é, antes de tudo, movimento.
Portanto, não basta apenas pensar que deseja alcançar algo. Você precisa fazer alguma coisa - mesmo os menores passos - mas faça-os todos os dias, e só assim você alcançará o objetivo final. E como você está aqui para se tornar um desenvolvedor Java, você precisa dar pelo menos um passo mínimo para aprofundar seu conhecimento sobre Java todos os dias. Para a etapa Java de hoje, sugiro que você se familiarize com a nova parte da análise das perguntas mais populares das entrevistas para desenvolvedores. Análise de perguntas e respostas de entrevistas para desenvolvedor Java.  Parte 13 - 1Hoje passaremos pela parte prática das questões para especialistas Juniores. Uma tarefa prática em uma entrevista não é incomum. É importante não se perder nessa situação, tentar manter a cabeça fria e oferecer a solução ideal, ou mesmo várias. Eu também recomendaria não ficar calado ao resolver um problema, mas comentar sua linha de pensamento e escrever a solução, ou depois de escrever, explicar em palavras o que você fez e por quê. Isso tornará você querido pelo entrevistador muito mais do que uma decisão silenciosa. Então vamos começar!

111. Como trocar dados entre threads?

Análise de perguntas e respostas de entrevistas para desenvolvedor Java.  Parte 13 - 2Para trocar dados entre threads, você pode usar muitas abordagens e meios diferentes: por exemplo, usar variáveis ​​atômicas, coleções sincronizadas e um semáforo. Mas para resolver esse problema vou dar um exemplo com Exchanger . Exchanger é uma classe de sincronização do pacote concorrente que facilita a troca de elementos entre um par de threads criando um ponto de sincronização comum. Seu uso simplifica a troca de dados entre dois threads. A forma como funciona é bastante simples: ele espera que dois threads separados chamem seu método exchange() . Cria-se entre eles algo como um ponto de troca: o primeiro fio coloca seu objeto e recebe em troca o objeto do outro, e este, por sua vez, recebe o objeto do primeiro e coloca o seu. Ou seja, o primeiro thread usa o método exchange() e fica ocioso até que outro thread chame o método exchange() no mesmo objeto e os dados sejam trocados entre eles. Como exemplo, considere a seguinte implementação da 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();
     }
   }
 }
}
No construtor da thread, definimos um objeto Exchanger que aceita objetos do tipo String , e na inicialização (no método run ) utilizamos seu exchange() para trocar uma mensagem com outra thread que utiliza esse método no mesmo Exchanger . Vamos executá-lo em main :
Exchanger<String> exchanger = new Exchanger<>();
CustomThread first = new CustomThread("Первый ", exchanger);
first.setMessage("Сообщение первого потока");
CustomThread second = new CustomThread("Второй", exchanger);
second.setMessage("Сообщение второго потока");
first.start();
second.start();
O console exibirá:
O primeiro tópico recebeu a mensagem: Mensagem do segundo tópico O segundo tópico recebeu a mensagem: Mensagem do primeiro tópico O segundo tópico recebeu a mensagem: Mensagem do segundo tópico O primeiro tópico recebeu a mensagem: Mensagem do primeiro tópico O segundo tópico recebeu a mensagem: Mensagem do primeiro tópico O primeiro tópico recebeu a mensagem: Mensagem do segundo tópico... .
Isso significa que a troca de dados entre threads foi bem-sucedida.

112. Qual é a diferença entre a classe Thread e a interface Runnable?

A primeira coisa que vou observar é que Thread é uma classe, Runnable é uma interface, o que é uma diferença bem óbvia =D Análise de perguntas e respostas de entrevistas para desenvolvedor Java.  Parte 13 - 3Direi também que Thread usa Runnable (composição). Ou seja, temos duas maneiras:
  1. Herde de Thread , substitua o método run , então crie este objeto e inicie o thread através do método start() .

  2. Implemente Runnable em uma determinada classe, implemente seu método run() e, em seguida, crie um objeto Thread , atribuindo a implementação desse objeto da interface Runnable ao seu construtor . Bem, no final, inicie o objeto Thread usando o método start() .

O que é preferível? Vamos pensar um pouco:
  • Ao implementar a interface Runnable , você não altera o comportamento do thread. Essencialmente, você está apenas dando ao thread algo para executar. E esta é a nossa composição, que por sua vez é considerada uma boa abordagem.

  • implementar Runnable dá mais flexibilidade à sua classe. Se você herdar de Thread , a ação executada sempre estará no thread. Mas se você implementar Runnable não precisa ser apenas um thread. Afinal, você pode executá-lo em uma thread ou passá-lo para algum serviço executor. Bem, ou simplesmente passe-o para algum lugar como uma tarefa em um aplicativo de thread único.

  • Usar Runnable permite separar logicamente a execução de tarefas da lógica de controle de thread.

  • Em Java, apenas a herança única é possível, portanto, apenas uma classe pode ser estendida. Ao mesmo tempo, o número de interfaces expansíveis é ilimitado (bem, não totalmente ilimitado, mas 65535 , mas é improvável que você atinja esse limite).

Bem, cabe a você decidir o que exatamente é preferível usar ^^

113. Existem roscas T1, T2 e T3. Como implementá-los sequencialmente?Análise de perguntas e respostas de entrevistas para desenvolvedor Java.  Parte 13 - 4

A primeira e mais simples coisa que vem à mente é usar o método join() . Ele suspende a execução do thread atual (que chamou o método) até que o thread no qual o método foi chamado termine de ser executado. Vamos criar nossa própria implementação 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 + " - закончил свою работу");
 }
}
Vamos iniciar três desses threads, um por um, usando 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();
Saída do console:
O primeiro tópico - iniciou seu trabalho O primeiro tópico - terminou seu trabalho O segundo tópico - iniciou seu trabalho O segundo tópico - terminou seu trabalho O terceiro tópico - iniciou seu trabalho O terceiro tópico - terminou seu trabalho
Isso significa que completamos nossa tarefa. A seguir, passamos diretamente para as tarefas práticas do nível Junior .

Tarefas práticas

114. Soma Diagonal da Matriz (problema Leetcode)

Condição: Calcule a soma de todos os elementos da diagonal principal e de todos os elementos da diagonal adicional que não fazem parte da diagonal principal. Análise de perguntas e respostas de entrevistas para desenvolvedor Java.  Parte 13 - 51. Com uma matriz no formato: mat = [[1,2,3], [4,5,6], [7,8,9]] A saída deve ser - 25 2. Com uma matriz - mat = [[1,1 ,1,1], [1,1,1,1], [1,1,1,1], [1,1,1,1]] A saída deve ser - 8 3. Com uma matriz - mat = [[ 5]] A conclusão deve ser - 5 Faça uma pausa na leitura e implemente sua decisão. Minha solução seria a seguinte:
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;
}
Tudo acontece com uma passagem pelo array, durante a qual temos dois índices para o relatório: i - para reportar as linhas do array e colunas da diagonal principal, j - para reportar as colunas da diagonal adicional. Se a célula da diagonal principal e a adicional coincidirem, um dos valores será ignorado no cálculo da soma. Vamos verificar usando as matrizes da condição:
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));
Saída do console:
25 8 5

115. Mover Zeroes (desafio Leetcode)

Condição: em uma matriz inteira, mova todos os 0 para o final, mantendo a ordem relativa dos elementos diferentes de zero. 1. Com uma matriz: [0,1,0,3,12] A saída deve ser: [1,3,12,0,0] 2. Com uma matriz: [0] A saída deve ser: [0] Faça uma pausa e escreva minha decisão... Minha decisão:
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;// заполняем последние элементы массива нулями согласно счётчику нулей
 }
}
Exame:
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));
Saída do console:
[1, 2, 12, 9, 0, 0] [0]

116. Nomes de lista <String> fornecidos. Remova a primeira letra de cada nome e gire a lista classificada

1. A primeira coisa que vem à mente são os métodos da classe Collections , que contém muitos métodos auxiliares para coleções:
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. Além disso, se usarmos Java versão 8 e superior, basta mostrar a solução por meio de streams:
public static List<String> processTheList(List<String> nameList) {
 return nameList.stream()
     .map(x -> x.substring(1))
     .sorted().collect(Collectors.toList());
}
Independentemente da solução escolhida, a verificação pode ser a seguinte:
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));
Saída do console:
[ávido, éter, gor, mitriy, nna, ob, ohn]

117. Inverta a matriz

Solução 1 Novamente, a primeira coisa que vem à mente é usar os métodos da classe de utilidade auxiliar Collections . Mas como temos um array, primeiro precisamos convertê-lo em uma coleção (lista):
public static Integer[] reverse(Integer[] arr) {
 List<Integer> list = Arrays.asList(arr);
 Collections.reverse(list);
 return list.toArray(arr);
}
Solução 2 Como a questão era sobre um array, acho que é necessário mostrar a solução sem usar funcionalidades prontas e prontas para uso, ou seja, de acordo com os clássicos:
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;
}
Exame:
Integer[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9};
System.out.println(Arrays.toString(reverse(arr)));
Saída do console:
[9, 8, 7, 6, 5, 4, 3, 2, 1]

118. Verifique se uma string é um palíndromo

Análise de perguntas e respostas de entrevistas para desenvolvedor Java.  Parte 13 - 6Solução 1 Vale lembrar imediatamente StringBuilder : é mais flexível e rico em vários métodos comparado ao String normal . Estamos especialmente interessados ​​no método reverso :
public static boolean isPalindrome(String string) {
 string = string.toLowerCase(); //приводит всю строку к нижнему регистру
 StringBuilder builder = new StringBuilder();
 builder.append(string);
 builder.reverse(); // перевочиваем строку методом Builder-а
 return (builder.toString()).equals(string);
}
Solução: A próxima abordagem será sem usar as “brechas” prontas para uso. Comparamos os caracteres do final da string com os caracteres correspondentes da frente:
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 verificamos ambas as abordagens:
boolean isPalindrome = isPalindrome("Tenet");
System.out.println(isPalindrome);
Saída do console:
verdadeiro

119. Escreva um algoritmo de classificação simples (Bubble, Selection ou Shuttle). Como pode ser melhorado?

Como um algoritmo simples para implementação, escolhi classificação por seleção - classificação por seleção:
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 итог - числа оставшиеся вне текущей итерации отсортированы от самого наименьшего к большему
}
A versão melhorada ficaria assim:
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;
}
Bem, agora precisamos ter certeza se a classificação realmente melhorou. Vamos comparar o desempenho:
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 + "%");
Ambas as sortes começaram no mesmo ciclo, porque se houvesse loops separados, a classificação no código acima mostraria um resultado pior do que se fosse colocado em segundo lugar. Isso se deve ao fato do programa “aquecer” e depois funcionar um pouco mais rápido. Mas estou saindo um pouco do assunto. Após cinco execuções dessa verificação no console, observei um aumento no desempenho em: 36,41006735635892% 51,46131097160771% 41,88918834013988% 48,091980705743566% 37,120220461591444% Quanto a mim, isso é muito bom resultado. Análise de perguntas e respostas de entrevistas para desenvolvedor Java.  Parte 13 - 7

120. Escreva um algoritmo (sequência de ações) para compor um literal do tipo int com um literal do tipo byte. Explique o que acontece com a memória

  1. o valor do byte é convertido em int. Não será alocado 1 byte de memória para ele, mas como todos os valores int - 4, se esse valor ainda não estiver na pilha int. Se houver, um link para ele será simplesmente recebido.

  2. Dois valores int serão somados e o terceiro será obtido. Uma nova seção de memória será alocada para ele - 4 bytes (ou uma referência será recebida da pilha int para o valor existente).

    Nesse caso, a memória de dois ints ainda estará ocupada e seus valores serão armazenados na pilha int, respectivamente.

Na verdade, é aqui que terminam as questões de nível júnior da nossa lista. A partir do próximo artigo, entenderemos questões de nível médio. Gostaria de observar que perguntas de nível médio também são feitas ativamente a desenvolvedores iniciantes - Junior. Então fique ligado. Bom, por hoje é tudo: até mais!Análise de perguntas e respostas de entrevistas para desenvolvedor Java.  Parte 13 - 8
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION