![Разбор вопросов и ответов с собеседований на Java-разработчика. Часть 13 - 1](https://cdn.javarush.com/images/article/5ee918ab-5450-428f-ba5e-1f5cec317723/800.jpeg)
111. Как между потоками обмениваться данными?
![Разбор вопросов и ответов с собеседований на Java-разработчика. Часть 13 - 2](https://cdn.javarush.com/images/article/49a8372d-89c3-4401-9c24-c4fe1af1841b/512.jpeg)
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();
}
}
}
}
В конструкторе потока мы задаём объект Exchanger, принимающий объекты типа String, а в запуске (в методе 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![Разбор вопросов и ответов с собеседований на Java-разработчика. Часть 13 - 3](https://cdn.javarush.com/images/article/4ecd768e-4daa-4421-b59b-54a6f6e68cad/512.jpeg)
Наследоваться от Thread, переопределить метод run, после чего создать данный объект и запустить поток через метод start().
Реализовать Runnable в определенном классе, реализовать его метод run(), после чего создать объект Thread, задав ему в конструктор этот объект-реализацию интерфейса Runnable. Ну и в конце запустить объект Thread с помощью метода start().
при реализации интерфейса Runnable вы не изменяете поведение потока. По сути вы просто даете потоку что-то запустить. А это у нас композиция, что в свою очередь считается хорошим подходом.
реализация Runnable даёт больше гибкости вашему классу. Если вы наследуетесь от Thread, то действие, которое вы выполняете, всегда будет в потоке. Но если вы реализуете Runnable, это не обязательно будет просто поток. Ведь вы можете как запустить его в потоке, так и передать его какой-либо службе-исполнителю. Ну или просто передать его куда-то как задачу в однопоточном приложении.
использование Runnable позволяет логически отделить выполнение задачи от логики управления потоками.
в Java возможно только одиночное наследование, поэтому можно расширить только один класс. В то же время количество расширяемых интерфейсов неограниченно (ну не совсем неограниченное, а 65535, но вряд ли вы когда-то упретесь в этот лимит).
113. Есть потоки Т1, Т2 и Т3. Как реализовать их последовательное выполнение?![Разбор вопросов и ответов с собеседований на 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. Matrix Diagonal Sum (задача с Leetcode)
Условие: Подсчитайте сумму всех элементов на основной диагонали и всех элементов на дополнительной диагонали, которые не являются частью основной диагонали.![Разбор вопросов и ответов с собеседований на Java-разработчика. Часть 13 - 5](https://cdn.javarush.com/images/article/0cf6711c-fd4a-4dc1-9e1a-42780ffe4d69/512.jpeg)
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));
Вывод в консоли:
115. Move Zeroes (задача с Leetcode)
Условие: В целочисленном массиве переместите все 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));
Вывод в консоль:
116. Given List <String> names. Удалите первую букву из каждого имени и поверните отсортированный список
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)));
Вывод в консоли:
118. Проверить, является ли строка палиндромом
![Разбор вопросов и ответов с собеседований на Java-разработчика. Часть 13 - 6](https://cdn.javarush.com/images/article/345b415d-c34c-496d-9919-0b847d1635a0/256.jpeg)
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. Написать простой алгоритм сортировки (Bubble, Selection или Shuttle). Как его можно улучшить?
В качестве просто алгоритма для реализации я выбрал сортировку выбором — 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]; // так как отрезок постоянно уменьшается
arr[i] = temp; // и выпадающие из него числа будут минимальными в текущем отрезке
} // и как итог - числа оставшиеся вне текущей итерации отсортированы от самого наименьшего к большему
}
Улучшенный вариант будет выглядеть следующим образом:
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 + "%");
Обе сортировки запустились в одном и том же цикле, т.к. если бы были отдельные циклы, сортировка из в коде выше показывала бы худший результат, нежели если её поставить второй.
Это связано с тем, что программа как бы “разогревается” и дальше работает немного быстрее.
Но я немного отошёл от темы.
После пяти запусков данной проверки в консоли я увидел увеличение производительности на:
36.41006735635892%
51.46131097160771%
41.88918834013988%
48.091980705743566%
37.120220461591444%
Как по мне, это довольно-таки хороший результат.
![Разбор вопросов и ответов с собеседований на Java-разработчика. Часть 13 - 7](https://cdn.javarush.com/images/article/b384661d-92d6-4d81-a04f-44815f94d744/512.jpeg)
120. Напишите алгоритм (последовательность действий) составления литерала типа int с литералом типа byte. Объясните, что происходит с памятью
byte значение приводится к int. Для него будет выделен не 1 байт памяти, а как и для всех int значений — 4, если этого значения ещё нет в int стеке. Если же есть — просто будет получена ссылка на него.
Два int значения будут сложены и получится третье. Под него выделится новый участок памяти — 4 байта (либо будет получена ссылка из int стека на существующее значение).
При этом память двух int всё ещё будет занята, и их значения будут храниться в int стеке соответственно.
![Разбор вопросов и ответов с собеседований на Java-разработчика. Часть 13 - 8](https://cdn.javarush.com/images/article/ebd6bb3d-5860-496d-a94c-4d3e594fb27a/512.jpeg)
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ