JavaRush /Java Blog /Random-JA /Java開発者へのインタビューからの質問と回答の分析。パート13
Константин
レベル 36

Java開発者へのインタビューからの質問と回答の分析。パート13

Random-JA グループに公開済み
こんにちは!
目標に向かって進むことは、まず第一に、動くことです。
したがって、何かを達成したいと考えるだけでは十分ではありません。何かをする必要があります。それはたとえ小さなステップであっても、それを毎日実行することによってのみ、最終目標を達成することができます。そして、皆さんは Java 開発者になるためにここにいるのですから、Java の知識を深めるために毎日少なくとも最小限のステップを踏む必要があります。今日の Java ステップでは、開発者にとって最も人気のある面接の質問の分析の新しい部分に慣れておくことをお勧めします。 Java開発者へのインタビューからの質問と回答の分析。 パート 13 - 1今日はジュニアスペシャリスト向けの質問の実践的な部分を取り上げます。面接で実務的な仕事が課されることも珍しくありません。このような状況では迷子にならず、冷静さを保ち、最適な解決策を 1 つまたは複数提案することが重要です。また、問題を解決するときは黙っているのではなく、自分の思考の流れをコメントして解決策を書くか、書いた後に何をしたのか、なぜやったのかを言葉で説明することをお勧めします。そうすることで、黙って決断するよりもはるかに面接官に好感を持たれるでしょう。それでは始めましょう!

111. スレッド間でデータを交換するにはどうすればよいですか?

Java開発者へのインタビューからの質問と回答の分析。 パート13-2スレッド間でデータを交換するには、アトミック変数、同期コレクション、セマフォなど、さまざまなアプローチと手段を使用できます。しかし、この問題を解決するために、 Exchangerを使用した例を示します。 Exchanger は、共通の同期ポイントを作成することによって、スレッドのペア間の要素の交換を容易にする同時実行パッケージの同期クラスです。これを使用すると、2 つのスレッド間のデータ交換が簡素化されます。その仕組みは非常に単純です。2 つの別個のスレッドが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();
コンソールには以下が表示されます。
最初のスレッドがメッセージを受信しました: 2 番目のスレッドのメッセージ 2 番目のスレッドがメッセージを受信しました: 最初のスレッドのメッセージ 2 番目のスレッドがメッセージを受信しました: 2 番目のスレッドのメッセージ 最初のスレッドがメッセージを受信しました: 最初のスレッドのメッセージ 2 番目スレッドはメッセージを受信しました: 最初のスレッドのメッセージ 最初のスレッドはメッセージを受信しました: 2 番目のスレッドのメッセージ... 。
これは、スレッド間のデータ交換が成功したことを意味します。

112. Thread クラスと Runnable インターフェイスの違いは何ですか?

最初に注意するのは、Threadはクラスであり、Runnableはインターフェイスであるということです。これは非常に明らかな違いです =D また、 Thread はRunnable (合成)を使用しているJava開発者へのインタビューからの質問と回答の分析。 パート 13 - 3とも言います。つまり、次の 2 つの方法があります。
  1. Threadから継承し、 run メソッドをオーバーライドしてから、このオブジェクトを作成し、start()メソッドを通じてスレッドを開始します。

  2. 特定のクラスにRunnableを実装し、そのrun()メソッドを実装してから、Threadオブジェクトを作成し、 Runnableインターフェイスのこのオブジェクト実装をそのコンストラクターに割り当てます。最後に、start()メソッドを使用してThreadオブジェクトを起動します。

何が好ましいでしょうか?少し考えてみましょう:
  • Runnableインターフェイスを実装しても、スレッドの動作は変更されません。本質的には、スレッドに実行するものを与えるだけです。これが私たちの構成であり、結果として良いアプローチであると考えられます。

  • Runnableを実装すると、クラスの柔軟性が高まります。Threadから継承する場合、実行するアクションは常にスレッド上で行われます。ただし、 Runnable を実装する場合、それは単なるスレッドである必要はありません。結局のところ、スレッド内で実行することも、実行サービスに渡すこともできます。または、それをシングルスレッド アプリケーションのタスクとしてどこかに渡すだけです。

  • Runnableを使用すると、タスクの実行をスレッド制御ロジックから論理的に分離できます。

  • Java では単一継承のみが可能なため、拡張できるクラスは 1 つだけです。同時に、拡張可能なインターフェイスの数は無制限です (まったく無制限ではありませんが、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() を使用して、このような 3 つのスレッドを 1 つずつ開始してみましょう。
CustomThread t1 = new CustomThread("Первый поток");
t1.start();
t1.join();
CustomThread t2 = new CustomThread("Второй поток");
t2.start();
t2.join();
CustomThread t3 = new CustomThread("Третий поток");
t3.start();
t3.join();
コンソール出力:
最初のスレッド - 作業を開始しました 最初のスレッド - 作業を終了しました 2 番目のスレッド - 作業を開始しました 2 番目のスレッド - 作業を終了しました 3 番目のスレッド - 作業を開始しました 3 番目のスレッド - 作業を終了しました
これは、私たちの任務が完了したことを意味します。次に、ジュニアレベルの実践的なタスクに直接進みます。

実践的なタスク

114.行列対角和(リートコード問題)

条件: 主対角上のすべての要素と、主対角の一部ではない追加対角上のすべての要素の合計を計算します。 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;
}
すべては配列の 1 回のパスで行われ、その間にレポート用の 2 つのインデックスがあります。i - 配列の行と主対角の列をレポートするため、j - 追加の対角の列をレポートするためです。主対角のセルと追加の対角のセルが一致する場合、合計を計算するときに値の 1 つが無視されます。条件の行列を使用して確認してみましょう。
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 (リートコードチャレンジ)

条件: 整数配列で、ゼロ以外の要素の相対的な順序を維持しながら、すべての 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));
コンソール出力:
[熱心、エター、ゴール、ミトリ、ナ、オブ、オーン]

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. 簡単な並べ替えアルゴリズム (バブル、選択、またはシャトル) を作成します。どうすれば改善できるでしょうか?

実装のための簡単なアルゴリズムとして、選択ソート - 選択ソートを選択しました。
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 + "%");
どちらの種類も同じサイクルで開始されました。個別のループがある場合、上記のコード内でソートすると、2 番目に配置した場合よりも悪い結果が表示されます。これは、プログラムが「ウォームアップ」してから少し速く動作するためです。しかし、少し話が逸れます。コンソールでこのチェックを 5 回実行したところ、次のようなパフォーマンスの向上が見られました。 36.41006735635892% 51.46131097160771% 41.88918834013988% 48.091980705743566% 37.120220461591444% 私にとって、これはかなり良い結果です。結果。 Java開発者へのインタビューからの質問と回答の分析。 パート 13 - 7

120. int 型のリテラルと byte 型のリテラルを合成するアルゴリズム (一連のアクション) を作成します。記憶に何が起こるかを説明する

  1. byte値はintに変換されます。1 バイトのメモリが割り当てられるわけではありませんが、すべての int 値と同様に、この値がまだ int スタックにない場合は 4 になります。存在する場合、それへのリンクが受信されるだけです。

  2. 2 つの int 値が加算され、3 番目の値が取得されます。新しいメモリ セクション (4 バイト) が割り当てられます (または、int スタックから既存の値への参照が受信されます)。

    この場合、2 つの int のメモリは依然として占有されており、それらの値はそれぞれ int スタックに格納されます。

実際、これで、リストのジュニア レベルの質問が終わります。次回からは中級レベルの問題を理解していきます。中級レベルの質問は、初心者レベルの開発者、つまりジュニアにも積極的に尋ねられることに注意してください。乞うご期待。さて、今日はここまでです。また会いましょう!Java開発者へのインタビューからの質問と回答の分析。 パート 13 - 8
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION