JavaRush /Java Blog /Random-KO /Java 개발자 인터뷰의 질문과 답변을 분석합니다. 13부

Java 개발자 인터뷰의 질문과 답변을 분석합니다. 13부

Random-KO 그룹에 게시되었습니다
안녕하세요!
목표를 향한 움직임은 무엇보다도 움직임입니다.
그러므로 무언가를 성취하고 싶다고 생각하는 것만으로는 충분하지 않습니다. 가장 작은 단계라도 뭔가를 해야 하지만 매일 수행해야 하며, 이렇게 해야 최종 목표를 달성할 수 있습니다. 그리고 당신은 Java 개발자가 되기 위해 여기 왔으므로 매일 Java에 대한 지식을 심화시키기 위해 최소한 최소한의 단계라도 밟아야 합니다. 오늘의 Java 단계에서는 개발자에게 가장 인기 있는 인터뷰 질문 분석의 새로운 부분을 숙지하시기 바랍니다. Java 개발자 인터뷰의 질문과 답변을 분석합니다.  파트 13 - 1오늘은 주니어 전문가를 위한 질문의 실용적인 부분을 살펴보겠습니다. 인터뷰에서 실용적인 작업은 드문 일이 아닙니다. 그러한 상황에서 길을 잃지 않는 것이 중요하며, 냉정함을 유지하고 최적의 솔루션을 제공하거나 심지어 여러 가지를 제공하는 것이 중요합니다. 또한 문제를 해결할 때 침묵을 유지하지 말고 자신의 생각에 대해 논평하고 해결책을 쓰거나, 글을 쓴 후에 무엇을 했고 그 이유를 말로 설명하는 것이 좋습니다. 이는 침묵의 결정보다 면접관의 사랑을 훨씬 더 많이 받게 될 것입니다. 그럼 시작해 보겠습니다!

111. 스레드 간에 데이터를 교환하는 방법은 무엇입니까?

Java 개발자 인터뷰의 질문과 답변을 분석합니다.  파트 13 - 2스레드 간에 데이터를 교환하려면 원자 변수, 동기화된 컬렉션, 세마포어 등 다양한 접근 방식과 수단을 사용할 수 있습니다. 하지만 이 문제를 해결하기 위해 Exchanger 를 예로 들어 보겠습니다 . Exchanger 는 공통 동기화 지점을 생성하여 스레드 쌍 간의 요소 교환을 용이하게 하는 동시 패키지 의 동기화 클래스입니다 . 이를 사용하면 두 스레드 간의 데이터 교환이 단순화됩니다. 작동 방식은 매우 간단합니다. 두 개의 별도 스레드가 exchange() 메서드를 호출할 때까지 기다립니다 . 그들 사이에 교환 지점과 같은 것이 생성됩니다. 첫 번째 스레드는 자신의 개체를 배치하고 그 대가로 다른 스레드의 개체를 받고, 후자는 차례로 첫 번째 스레드의 개체를 받고 자신의 개체를 넣습니다. 즉, 첫 번째 스레드는 exchange() 메서드를 사용하고 다른 스레드가 동일한 개체에 대해 exchange() 메서드를 호출 하고 두 스레드 간에 데이터가 교환될 때까지 유휴 상태입니다 . 예를 들어 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();
     }
   }
 }
}
스레드 생성자에서 String 유형의 개체를 허용하는 Exchanger 개체를 정의 하고 시작 시( run 메서드에서) exchange() 를 사용하여 동일한 Exchanger 에서 이 메서드를 사용하는 다른 스레드와 메시지를 교환합니다 . main 에서 실행해보자 :
Exchanger<String> exchanger = new Exchanger<>();
CustomThread first = new CustomThread("Первый ", exchanger);
first.setMessage("Сообщение первого потока");
CustomThread second = new CustomThread("Второй", exchanger);
second.setMessage("Сообщение второго потока");
first.start();
second.start();
콘솔에 다음이 표시됩니다.
첫 번째 스레드가 메시지를 받았습니다: 두 번째 스레드의 메시지 두 번째 스레드가 메시지를 받았습니다: 첫 번째 스레드의 메시지 두 번째 스레드가 메시지를 받았습니다: 두 번째 스레드의 메시지 첫 번째 스레드가 메시지를 받았습니다: 첫 번째 스레드의 메시지 두 번째 스레드가 메시지를 받았습니다: 첫 번째 스레드의 메시지 첫 번째 스레드가 메시지를 받았습니다: 두 번째 스레드의 메시지... .
이는 스레드 간의 데이터 교환이 성공했음을 의미합니다.

112. Thread 클래스와 Runnable 인터페이스의 차이점은 무엇입니까?

가장 먼저 주목해야 할 점은 Thread 는 클래스이고 Runnable 은 인터페이스라는 점입니다. 이는 매우 분명한 차이점입니다. =D 또한 Thread는 Runnable (구성)을 사용한다고 Java 개발자 인터뷰의 질문과 답변을 분석합니다.  파트 13 - 3말할 것입니다 . 즉, 두 가지 방법이 있습니다.
  1. Thread 에서 상속 하고 run 메서드를 재정의한 다음 이 개체를 만들고 start() 메서드를 통해 스레드를 시작합니다 .

  2. 특정 클래스에서 Runnable을 구현하고 run() 메소드를 구현 한 다음 Thread 객체를 생성하여 Runnable 인터페이스 의 이 객체 구현을 해당 생성자에 할당합니다 . 마지막에는 start() 메소드를 사용하여 Thread 객체를 시작합니다 .

무엇이 바람직합니까? 조금 생각해 봅시다 :
  • Runnable 인터페이스를 구현할 때 스레드의 동작은 변경되지 않습니다. 본질적으로 당신은 스레드에 실행할 무언가를 제공하는 것입니다. 그리고 이것이 우리의 구성이며, 이는 좋은 접근 방식으로 간주됩니다.

  • Runnable을 구현하면 클래스에 더 많은 유연성이 제공됩니다. Thread 에서 상속받는 경우 수행하는 작업은 항상 스레드에서 수행됩니다. 그러나 Runnable을 구현한다면 단순히 스레드일 필요는 없습니다. 결국 스레드에서 실행하거나 일부 실행 서비스에 전달할 수 있습니다. 글쎄, 아니면 단일 스레드 응용 프로그램의 작업으로 어딘가에 전달하십시오.

  • Runnable을 사용하면 작업 실행을 스레드 제어 논리와 논리적으로 분리할 수 있습니다.

  • Java에서는 단일 상속만 가능하므로 하나의 클래스만 확장할 수 있습니다. 동시에 확장 가능한 인터페이스의 수는 무제한입니다(무제한은 아니지만 65535 이지만 이 제한에 도달할 가능성은 거의 없습니다).

글쎄, 정확히 무엇을 사용하는 것이 더 바람직한지는 당신의 결정에 달려 있습니다 ^^

113. 스레드 T1, T2 및 T3이 있습니다. 순차적으로 구현하는 방법은 무엇입니까?Java 개발자 인터뷰의 질문과 답변을 분석합니다.  파트 13 - 4

가장 먼저 떠오르는 가장 간단한 것은 Join() 메소드를 사용하는 것입니다 . 메서드가 호출된 스레드의 실행이 완료될 때까지 현재 스레드(메서드를 호출한)의 실행을 일시 중단합니다. 자체 스레드 구현을 만들어 보겠습니다.
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 + " - закончил свою работу");
 }
}
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();
콘솔 출력:
첫 번째 스레드 - 작업 시작 첫 번째 스레드 - 작업 완료 두 번째 스레드 - 작업 시작 두 번째 스레드 - 작업 완료 세 번째 스레드 - 작업 시작 세 번째 스레드 - 작업 완료
이는 우리가 작업을 완료했음을 의미합니다. 다음으로, 주니어 레벨 의 실제 작업으로 직접 이동합니다 .

실무적인 업무

114. 행렬 대각선 합 (Leetcode 문제)

조건: 주대각선에 있는 모든 요소와 주대각선에 속하지 않는 추가 대각선에 있는 모든 요소의 합을 계산합니다. Java 개발자 인터뷰의 질문과 답변을 분석합니다.  파트 13 - 51. 다음 형식의 행렬 사용: mat = [[1,2,3], [4,5,6], [7,8,9]] 출력은 - 25 여야 합니다. 2. 행렬 사용 - mat = [[1,1 ,1,1], [1,1,1,1], [1,1,1,1], [1,1,1,1]] 출력은 - 8 3이어야 합니다. 행렬 - mat = [[ 5]] 결론은 다음과 같아야 합니다. 5 읽기를 잠시 멈추고 결정을 실행하십시오. 내 솔루션은 다음과 같습니다.
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;
}
모든 것은 배열을 한 번 통과하면서 발생하며 그 동안 보고서에 대한 두 개의 인덱스가 있습니다. i - 배열의 행과 주 대각선의 열을 보고하기 위한 j - 추가 대각선의 열을 보고하기 위한 것입니다. 주 대각선 셀과 추가 대각선 셀이 일치하면 합계를 계산할 때 값 중 하나가 무시됩니다. 조건의 행렬을 사용하여 확인해 보겠습니다.
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));
콘솔 출력:
25 8 5

115. Move Zeroes (Leetcode 챌린지)

조건: 정수 배열에서 0이 아닌 요소의 상대적 순서를 유지하면서 모든 0을 끝으로 이동합니다. 1. 배열의 경우: [0,1,0,3,12] 출력은 다음과 같아야 합니다: [1,3,12,0,0] 2. 배열의 경우: [0] 출력은 다음과 같아야 합니다: [0] 잠시 멈추고 내 결정을 적어 보세요... 내 결정:
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;// заполняем последние элементы массива нулями согласно счётчику нулей
 }
}
시험:
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));
콘솔 출력:
[1, 2, 12, 9, 0, 0] [0]

116. 주어진 목록 <String> 이름. 각 이름의 첫 글자를 제거하고 정렬된 목록을 회전합니다.

1. 가장 먼저 떠오르는 것은 컬렉션을 위한 많은 보조 메서드를 포함하는 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. 또한 Java 버전 8 이상을 사용하는 경우 스트림을 통해 솔루션을 표시하면 됩니다.
public static List<String> processTheList(List<String> nameList) {
 return nameList.stream()
     .map(x -> x.substring(1))
     .sorted().collect(Collectors.toList());
}
선택한 솔루션에 관계없이 점검은 다음과 같을 수 있습니다.
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));
콘솔 출력:
[avid, eter, gor, mitriy, nna, ob, ohn]

117. 배열 뒤집기

해결 방법 1 다시 한 번 가장 먼저 생각나는 것은 보조 유틸리티 클래스 Collections 의 메서드를 사용하는 것입니다 . 하지만 배열이 있으므로 먼저 이를 컬렉션(목록)으로 변환해야 합니다.
public static Integer[] reverse(Integer[] arr) {
 List<Integer> list = Arrays.asList(arr);
 Collections.reverse(list);
 return list.toArray(arr);
}
솔루션 2 질문은 배열에 관한 것이었기 때문에 고전에 따르면 기성 기능을 사용하지 않고 솔루션을 보여줄 필요가 있다고 생각합니다.
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;
}
시험:
Integer[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9};
System.out.println(Arrays.toString(reverse(arr)));
콘솔 출력:
[9, 8, 7, 6, 5, 4, 3, 2, 1]

118. 문자열이 회문인지 확인하세요

Java 개발자 인터뷰의 질문과 답변을 분석합니다.  파트 13 - 6해결 방법 1 StringBuilder를 즉시 기억해 볼 가치가 있습니다 . 일반 String 에 비해 더 유연하고 다양한 메서드가 풍부합니다 . 우리는 특히 역방향 방법에 관심이 있습니다 .
public static boolean isPalindrome(String string) {
 string = string.toLowerCase(); //приводит всю строку к нижнему регистру
 StringBuilder builder = new StringBuilder();
 builder.append(string);
 builder.reverse(); // перевочиваем строку методом Builder-а
 return (builder.toString()).equals(string);
}
해결책: 다음 접근 방식은 기본적으로 "허점"을 사용하지 않는 것입니다. 문자열 뒷부분의 문자를 앞부분의 해당 문자와 ​​비교합니다.
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;
}
그리고 우리는 두 가지 접근 방식을 모두 확인합니다.
boolean isPalindrome = isPalindrome("Tenet");
System.out.println(isPalindrome);
콘솔 출력:
진실

119. 간단한 정렬 알고리즘(버블, 선택 또는 셔틀)을 작성하세요. 어떻게 개선할 수 있나요?

구현을 위한 간단한 알고리즘으로 선택 정렬(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 итог - числа оставшиеся вне текущей итерации отсортированы от самого наименьшего к большему
}
개선된 버전은 다음과 같습니다.
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;
}
자, 이제 정렬이 실제로 향상되었는지 확인해야 합니다. 성능을 비교해 보겠습니다.
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 + "%");
두 종류 모두 같은 주기로 시작되었습니다. 별도의 루프가 있는 경우 위 코드에서 정렬하면 두 번째로 배치된 경우보다 더 나쁜 결과가 표시됩니다. 이는 프로그램이 "워밍업"된 다음 조금 더 빠르게 작동하기 때문입니다. 그러나 나는 주제에서 조금 벗어났습니다. 콘솔에서 이 검사를 5번 실행한 후 성능이 다음과 같이 증가한 것을 확인했습니다. 36.41006735635892% 51.46131097160771% 41.88918834013988% 48.091980705743566% 37.120220461591444% 나로서는 이것이 꽤 좋습니다. 결과. Java 개발자 인터뷰의 질문과 답변을 분석합니다.  파트 13 - 7

120. int 유형 리터럴을 byte 유형 리터럴로 구성하기 위한 알고리즘(작업 순서)을 작성하세요. 기억에 무슨 일이 일어나는지 설명해보세요

  1. 바이트 값은 int로 변환됩니다. 1바이트의 메모리가 할당되지 않지만 모든 int 값과 마찬가지로 이 값이 아직 int 스택에 없으면 4입니다. 있는 경우 해당 링크가 수신됩니다.

  2. 두 개의 int 값이 추가되고 세 번째 값이 얻어집니다. 이를 위해 새로운 메모리 섹션(4바이트)이 할당됩니다(또는 int 스택에서 기존 값에 대한 참조가 수신됩니다).

    이 경우 두 int의 메모리는 여전히 점유되며 해당 값은 각각 int 스택에 저장됩니다.

사실, 여기가 우리 목록의 주니어 레벨 질문이 끝나는 곳입니다. 다음 글부터 Middle 수준의 문제를 이해해보겠습니다. 초보 개발자인 주니어에게도 미들 레벨의 질문이 활발하게 이뤄지고 있다는 점을 참고하고 싶습니다. 그러니 계속 지켜봐 주시기 바랍니다. 자, 오늘은 여기까지입니다. 또 뵙겠습니다!Java 개발자 인터뷰의 질문과 답변을 분석합니다.  파트 13 - 8
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION