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

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

Published in the Random EN group
Hello!
Moving towards a goal is first and foremost a 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 reach the ultimate goal. And since you are here to become a Java developer, you need to take at least a minimal step towards deepening your knowledge of Java every day. As today's Java step, I suggest that you familiarize yourself with the new part of the analysis of the most popular questions in interviews for developers. Analysis of questions and answers from interviews for a 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 best solution, or even several. I would also recommend not to be silent when solving the problem, but to comment on the course of your thoughts and writing the solution, or, after writing, explain in words what and why you did. This will endear the interviewer to you 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 a Java developer.  Part 13 - 2To exchange data between threads, you can use many different approaches and tools: for example, use atomic variables, synchronized collections, 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 items between a pair of threads by creating a common synchronization point. Its use simplifies the exchange of data between two threads. Its mechanism is quite simple: it waits until two separate threads call its exchange() method.. Something like an exchange point is created between them: the first thread puts its object and receives the object of another in return, and the latter, in turn, receives the object of the first and puts its own. That is, the first thread uses the exchange() method and remains 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 output:
First thread received a message: Second thread's message Second thread received a message: First thread's message Second thread received a message: Second thread's message First thread received a message: First thread's message Second thread received a message: First thread's message First thread received a message: Second thread's message .. .
This means that the data exchange between threads is successful.

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

The first thing I will 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 a Java developer.  Part 13 - 3I will 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 via the start() method .

  2. Implement Runnable in a specific class, implement its run() method , and then create a Thread object by setting its constructor to this implementation of the Runnable interface . Finally, start the Thread object using the start() method .

What is preferable? Let's think a little:
  • when you implement the Runnable interface , you don't change the behavior of the thread. Essentially, you're 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 flow 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, what exactly is preferable to use, it's up to you to decide ^^

113. There are T1, T2 and T3 streams. How to implement their sequential execution?Analysis of questions and answers from interviews for a Java developer.  Part 13 - 4

The very first and simplest thing that comes to mind is the use of the join() method . It suspends the execution of the current thread (the one that called the method) until the thread on which the method was called finishes executing. Let's create our own flow 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 in turn 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:
First thread - started its work First thread - finished its work Second thread - started its work Second thread - finished its work Third thread - started its work Third thread - finished its work
This means that we have coped with our task. Next, we proceed directly to the practical tasks of the Junior level .

Practical tasks

114. Matrix Diagonal Sum (problem with Leetcode)

Condition: Count 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 a 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 will 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 a single pass through the array, during which we have two indexes for the report: i - for the report of the array rows and columns of the main diagonal, j - for the report of the columns of the additional diagonal. If the cell of the main diagonal and the additional one are the same, 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 (task with Leetcode)

Condition: In an integer array, move all 0s to the end, preserving the relative order of the nonzero 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 solution... My solution:
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 helper 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 above, we simply have to show the solution through streams:
public static List<String> processTheList(List<String> nameList) {
 return nameList.stream()
     .map(x -> x.substring(1))
     .sorted().collect(Collectors.toList());
}
Regardless of the solution chosen, the check can 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 an array

Solution 1 Again, the first thing that comes to mind is to use the methods of the helper, utility class Collections . But since we have an array, we first need to convert it to 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 the ready-made functionality out of the box, but, 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 a Java developer.  Part 13 - 6Solution 1 It's worth remembering StringBuilder right away : it is more flexible and rich in various methods compared to a regular String . We are 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 the use of “loopholes” out of the box. 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 test 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 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 that the 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 cycles, sorting from in the code above would show a worse result than if it was put second. This is due to the fact that the program, as it were, “warms up” and then works a little faster. But I got a little off topic. After five runs of this test in the console, I saw a performance increase of: 36.41006735635892% 51.46131097160771% 41.88918834013988% 48.091980705743566% 37.120220461591444% I think this is a pretty good result . Analysis of questions and answers from interviews for a Java developer.  Part 13 - 7

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

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

  2. The two int values ​​will be added together to get the third one. Under it, a new memory area will be allocated - 4 bytes (or a link will be received from the int stack to an 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 deal with the issues of the Middle level. I 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 soon!Analysis of questions and answers from interviews for a Java developer.  Part 13 - 8 Analysis of questions and answers from interviews for a Java developer.  Part 13 - 9
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION