JavaRush /Java Blog /Random EN /Analysis of questions and answers from interviews for Jav...

Analysis of questions and answers from interviews for Java developer. Part 13

Published in the Random EN group
Hello!
Movement towards a goal is, first of all, movement.
Therefore, it is not enough just to think that you want to achieve something. You need to do something - even the smallest steps - but do them every day, and only in this way will you achieve the final goal. And since you are here to become Java developers, you need to take at least a minimal step towards deepening your knowledge of Java every day. For today's Java step, I suggest you familiarize yourself with the new part of the analysis of the most popular interview questions for developers. Analysis of questions and answers from interviews for Java developer.  Part 13 - 1Today we will go through the practical part of the questions for Junior specialists. A practical task at an interview is not uncommon. It is important not to get lost in such a situation, try to keep a cool head and offer the optimal solution, or even several. I would also recommend not remaining silent when solving a problem, but commenting on your train of thought and writing the solution, or after writing, explain in words what you did and why. This will endear you to the interviewer much more than a silent decision. So let's get started!

111. How to exchange data between threads?

Analysis of questions and answers from interviews for Java developer.  Part 13 - 2To exchange data between threads, you can use many different approaches and means: for example, use atomic variables, synchronized collections, and a semaphore. But to solve this problem, I will give an example with Exchanger . Exchanger is a synchronization class from the concurrent package that facilitates the exchange of elements between a pair of threads by creating a common synchronization point. Its use simplifies the exchange of data between two threads. The way it works is quite simple: it waits for two separate threads to call its exchange() method . Something like an exchange point is created between them: the first thread puts its object and receives the object of the other in return, and the latter, in turn, receives the object of the first and puts his own. That is, the first thread uses the exchange() method and is idle until another thread calls the exchange() method on the same object and data is exchanged between them. As an example, consider the following implementation of the Thread class :
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();
     }
   }
 }
}
In the thread constructor, we define an Exchanger object that accepts objects of type String , and at startup (in the run method ) we use its exchange() to exchange a message with another thread that uses this method in the same Exchanger . Let's run it 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();
The console will display:
The first thread received the message: Message of the second thread The second thread received the message: Message of the first thread The second thread received the message: Message of the second thread The first thread received the message: Message of the first thread The second thread received the message: Message of the first thread The first thread received the message: Message of the second thread... .
This means that data exchange between threads is successful.

112. What is the difference between the Thread class and the Runnable interface?

The first thing I’ll note is that Thread is a class, Runnable is an interface, which is a very obvious difference =D Analysis of questions and answers from interviews for Java developer.  Part 13 - 3I’ll also say that Thread uses Runnable (composition). That is, we have two ways:
  1. Inherit from Thread , override the run method, then create this object and start the thread through the start() method .

  2. Implement Runnable in a certain class, implement its run() method , and then create a Thread object , assigning this object implementation of the Runnable interface to its constructor . Well, at the end, launch the Thread object using the start() method .

What is preferable? Let's think a little:
  • When you implement the Runnable interface , you do not change the behavior of the thread. Essentially you are just giving the thread something to run. And this is our composition, which in turn is considered a good approach.

  • implementing Runnable gives more flexibility to your class. If you inherit from Thread , then the action you perform will always be on the thread. But if you implement Runnable it doesn't have to be just a thread. After all, you can either run it in a thread or pass it to some executor service. Well, or just pass it somewhere as a task in a single-threaded application.

  • Using Runnable allows you to logically separate task execution from thread control logic.

  • In Java, only single inheritance is possible, so only one class can be extended. At the same time, the number of expandable interfaces is unlimited (well, not quite unlimited, but 65535 , but you are unlikely to ever hit this limit).

Well, it’s up to you to decide what exactly is preferable to use ^^

113. There are threads T1, T2 and T3. How to implement them sequentially?Analysis of questions and answers from interviews for Java developer.  Part 13 - 4

The very first and simplest thing that comes to mind is using the join() method . It suspends execution of the current thread (that called the method) until the thread on which the method was called finishes executing. Let's create our own thread implementation:
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 + " - закончил свою работу");
 }
}
Let's start three such threads one by one using 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();
Console output:
The first thread - started its work The first thread - finished its work The second thread - started its work The second thread - finished its work The third thread - started its work The third thread - finished its work
This means that we have completed our task. Next, we move directly to practical tasks at the Junior level .

Practical tasks

114. Matrix Diagonal Sum (Leetcode problem)

Condition: Calculate the sum of all elements on the main diagonal and all elements on the additional diagonal that are not part of the main diagonal. Analysis of questions and answers from interviews for Java developer.  Part 13 - 51. With a matrix of the form: mat = [[1,2,3], [4,5,6], [7,8,9]] The output should be - 25 2. With a matrix - mat = [[1,1 ,1,1], [1,1,1,1], [1,1,1,1], [1,1,1,1]] The output should be - 8 3. With a matrix - mat = [[ 5]] The conclusion should be - 5 Pause reading and implement your decision. My solution would be the following:
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;
}
Everything happens with one pass through the array, during which we have two indexes for the report: i - for reporting the rows of the array and columns of the main diagonal, j - for reporting the columns of the additional diagonal. If the cell of the main diagonal and the additional one coincide, then one of the values ​​is ignored when calculating the sum. Let's check using the matrices from the 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));
Console output:
25 8 5

115. Move Zeroes (Leetcode challenge)

Condition: In an integer array, move all 0's to the end, maintaining the relative order of non-zero elements. 1. With an array: [0,1,0,3,12] The output should be: [1,3,12,0,0] 2. With an array: [0] The output should be: [0] Pause and write my decision... My decision:
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;// заполняем последние элементы массива нулями согласно счётчику нулей
 }
}
Examination:
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));
Console output:
[1, 2, 12, 9, 0, 0] [0]

116. Given List <String> names. Remove the first letter from each name and rotate the sorted list

1. The first thing that comes to mind is the methods of the Collections class , which contains many auxiliary methods for 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. Also, if we use Java version 8 and higher, we simply have to show the solution via streams:
public static List<String> processTheList(List<String> nameList) {
 return nameList.stream()
     .map(x -> x.substring(1))
     .sorted().collect(Collectors.toList());
}
Regardless of the chosen solution, the check may be as follows:
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));
Console output:
[avid, eter, gor, mitriy, nna, ob, ohn]

117. Flip the array

Solution 1 Again, the first thing that comes to mind is to use the methods of the auxiliary utility class Collections . But since we have an array, we first need to convert it into a collection (list):
public static Integer[] reverse(Integer[] arr) {
 List<Integer> list = Arrays.asList(arr);
 Collections.reverse(list);
 return list.toArray(arr);
}
Solution 2 Since the question was about an array, I think it is necessary to show the solution without using ready-made functionality out of the box, and so to speak, according to the classics:
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;
}
Examination:
Integer[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9};
System.out.println(Arrays.toString(reverse(arr)));
Console output:
[9, 8, 7, 6, 5, 4, 3, 2, 1]

118. Check if a string is a palindrome

Analysis of questions and answers from interviews for Java developer.  Part 13 - 6Solution 1 It’s worth immediately remembering StringBuilder : it is more flexible and rich in various methods compared to regular String . We're especially interested in the reverse method :
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: The next approach will be without using the “loopholes” out of the box. We compare the characters from the back of the string with the corresponding characters from the front:
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;
}
And we check both approaches:
boolean isPalindrome = isPalindrome("Tenet");
System.out.println(isPalindrome);
Console output:
true

119. Write a simple sorting algorithm (Bubble, Selection or Shuttle). How can it be improved?

As a simple algorithm for implementation, I chose selection sorting - 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 итог - числа оставшиеся вне текущей итерации отсортированы от самого наименьшего к большему
}
The improved version would look like this:
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;
}
Well, now we need to make sure whether sorting has really improved. Let's compare performance:
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 + "%");
Both sorts started in the same cycle, because if there were separate loops, sorting from in the code above would show a worse result than if it was placed second. This is due to the fact that the program “warms up” and then works a little faster. But I'm going a little off topic. After five runs of this check in the console, I saw an increase in performance by: 36.41006735635892% 51.46131097160771% 41.88918834013988% 48.091980705743566% 37.120220461591444% As for me, this is a pretty good result. Analysis of questions and answers from interviews for Java developer.  Part 13 - 7

120. Write an algorithm (sequence of actions) for composing a literal of type int with a literal of type byte. Explain what happens to memory

  1. byte value is converted to int. Not 1 byte of memory will be allocated for it, but like all int values ​​- 4, if this value is not yet on the int stack. If there is, a link to it will simply be received.

  2. Two int values ​​will be added and the third will be obtained. A new memory section will be allocated for it - 4 bytes (or a reference will be received from the int stack to the existing value).

    In this case, the memory of two ints will still be occupied, and their values ​​will be stored on the int stack, respectively.

Actually, this is where the Junior level questions from our list end. Starting from the next article, we will understand Middle level issues. I would like to note that Middle-level questions are also actively asked to entry-level developers - Junior. So stay tuned. Well, that’s all for today: see you!Analysis of questions and answers from interviews for Java developer.  Part 13 - 8
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION