50 вопросов на Java собеседовании

🔥 🚀 Важно для всех, кто работает с Java! 🔥
На JavaRocks ты найдешь уникальные туториалы, практические задачи и редкие книги, которых не найти в свободном доступе. Присоединяйся к нашему Telegram-каналу JavaRocks — стань частью профессионального сообщества!

Введение

Если вы проходите собеседование на позицию Java-программиста, будьте готовы продемонстрировать свои навыки кодирования. Независимо от того, новичок ли вы в Java или опытный программист – в этой статье собраны типовые вопросы и ответы, которые помогут вам подготовиться.

1. Как перевернуть строку в Java?

Перевернуть строку можно, преобразовав ее в массив символов и пройдя по нему в обратном порядке. Один из подходов – использовать StringBuilder и цикл for, чтобы поочередно добавлять символы с конца строки к началу.

Вот пример реализации:

public class StringPrograms {

	public static void main(String[] args) {
		String str = "123";

		System.out.println(reverse(str));
	}

	public static String reverse(String in) {
		if (in == null)
			throw new IllegalArgumentException("Null is not valid input");

		StringBuilder out = new StringBuilder();

		char[] chars = in.toCharArray();

		for (int i = chars.length - 1; i >= 0; i--)
			out.append(chars[i]);

		return out.toString();
	}

}
Run Code

Метод проверяет, что входная строка не равна null. Вместо обычной строки используется StringBuilder, так как он более эффективен при многократном добавлении символов. Поскольку индексация в Java начинается с нуля, цикл начинается с chars.length - 1 и идет до нуля включительно.

2. Как поменять местами два числа без использования третьей переменной в Java?

Перестановка чисел без использования третьей переменной – это трехэтапный процесс, который лучше всего представить в виде кода:

b = b + a; // теперь b содержит сумму двух чисел
a = b - a; // b - a = (b + a) - a = b (значение a заменено на b)
b = b - a; // (b + a) - b = a (значение b заменено на a)

Следующий пример кода показывает один из способов реализации метода замены чисел:

public class SwapNumbers {

public static void main(String[] args) {
	int a = 10;
	int b = 20;

    System.out.println("a is " + a + " and b is " + b);

	a = a + b;
	b = a - b;
	a = a - b;

    System.out.println("After swapping, a is " + a + " and b is " + b);
    }

}
Run Code

Вывод показывает, что значения целых чисел успешно поменялись местами:

a is 10 and b is 20
After swapping, a is 20 and b is 10

3. Напишите программу на Java для проверки наличия гласной в строке.

Следующий пример кода показывает, как с помощью регулярного выражения проверить, содержит ли строка гласные:

public class StringContainsVowels {

	public static void main(String[] args) {
		System.out.println(stringContainsVowels("Hello")); // true
		System.out.println(stringContainsVowels("TV")); // false
	}

	public static boolean stringContainsVowels(String input) {
		return input.toLowerCase().matches(".*[aeiou].*");
	}

}
Run Code

Метод matches() проверяет, соответствует ли вся строка заданному регулярному выражению. Конструкция .*[aeiou].* означает, что строка должна содержать хотя бы одну из гласных латинского алфавита — a, e, i, o или u. Приведение строки к нижнему регистру необходимо для учета прописных букв.

4. Напишите программу на Java для проверки того, является ли заданное число простым числом.

Один из подходов – проверить, делится ли число n на любое значение от 2 до n/2. Если хотя бы один делитель найден, число не является простым.

Пример реализации:

public class PrimeNumberCheck {

	public static void main(String[] args) {
		System.out.println(isPrime(19)); // true
		System.out.println(isPrime(49)); // false
	}

	public static boolean isPrime(int n) {
		if (n == 0 || n == 1) {
			return false;
		}
		if (n == 2) {
			return true;
		}
		for (int i = 2; i <= n / 2; i++) {
			if (n % i == 0) {
				return false;
			}
		}

		return true;
	}

}
Run Code

Однако этот подход не самый эффективный. Более оптимально проверять делители только до квадратного корня из числа. Если число N делится на какое-либо число M, где M находится в диапазоне от 2 до √N, значит, N не простое.

5. Напишите программу на Java для печати последовательности Фибоначчи с использованием рекурсии.

Последовательность Фибоначчи – это числовой ряд, в котором каждое последующее число равно сумме двух предыдущих. В примере ниже последовательность начинается с 0 и 1. Один из вариантов – использовать цикл for для вывода:

public class PrintFibonacci {

	public static void printFibonacciSequence(int count) {
		int a = 0;
		int b = 1;
		int c = 1;

		for (int i = 1; i <= count; i++) {
			System.out.print(a + ", ");

            a = b;
			b = c;
			c = a + b;
		}
	}

	public static void main(String[] args) {
    	printFibonacciSequence(10);
	}

}
Run Code
0, 1, 1, 2, 3, 5, 8, 13, 21, 34,

Вы также можете использовать рекурсию последовательности Фибоначчи, поскольку каждое число Фибоначчи генерируется путем сложения двух предыдущих чисел в последовательности:

F(N) = F(N-1) + F(N-2)

В следующем примере класса показано, как использовать рекурсию для вычисления последовательности Фибоначчи длиной 10 чисел:

public class PrintFibonacciRecursive {

    public static int fibonacci(int count) {
		if (count <= 1)
			return count;

		return fibonacci(count - 1) + fibonacci(count - 2);
	}

	public static void main(String args[]) {
    	int seqLength = 10;

    	System.out.print("A Fibonacci sequence of " + seqLength + " numbers: ");

    	for (int i = 0; i < seqLength; i++) {
      	    System.out.print(fibonacci(i) + " ");
    	}
  	}

}
Run Code
A Fibonacci sequence of 10 numbers: 0 1 1 2 3 5 8 13 21 34

6. Как проверить, содержит ли список целых чисел только нечетные числа в Java?

Вы можете использовать цикл for и проверить, является ли каждый элемент нечетным:

public static boolean onlyOddNumbers(List<Integer> list) {
	for (int i : list) {
		if (i % 2 == 0)
			return false;
	}

	return true;
}
Run Code

Если список большой, можно использовать параллельный поток (parallelStream) для ускорения обработки. Пример реализации:

public static boolean onlyOddNumbers(List<Integer> list) {
	return list
			.parallelStream() // параллельный поток для более быстрой обработки
			.anyMatch(x -> x % 2 != 0); // возвращает false, как только встретится четное число
}
Run Code

Если вам интересно, почему x % 2 != 0 определяет нечетное число, можете заглянуть в статью Википедии Операция по модулю в Википедии .

7. Как проверить, является ли строка палиндромом в Java?

Палиндром – это строка, которая читается одинаково слева направо и справа налево. Чтобы проверить строку на палиндромность, можно сравнить символы с начала и конца строки, двигаясь навстречу к середине.

Пример реализации с использованием метода charAt(int index):

boolean checkPalindromeString(String input) {
	boolean result = true;
	int length = input.length();

	for (int i = 0; i < length/2; i++) {
		if (input.charAt(i) != input.charAt(length - i - 1)) {
			result = false;
			break;
		}
	}

	return result;
}
Run Code

Метод проверяет пары символов с противоположных концов строки. Если хотя бы одна пара не совпадает, строка не является палиндромом. Как только обнаружено несоответствие, цикл прерывается, и возвращается false.

8. Как удалить пробелы из строки в Java?

В следующем примере кода показан один из способов удаления пробелов из строки с использованием Character.isWhitespace() метода:

String removeWhiteSpaces(String input) {
	StringBuilder output = new StringBuilder();
	
	char[] charArray = input.toCharArray();
	
	for (char c : charArray) {
		if (!Character.isWhitespace(c))
			output.append(c);
	}
	
	return output.toString();
}
Run Code

Метод removeWhiteSpaces проверяет каждый символ строки и добавляет в результат только те, которые не являются пробельными символами (' ', '\t', '\n' и т.д.). Для этого используется Character.isWhitespace(c).

Узнайте больше об удалении пробелов и других символов из строки в Java .

9. Как удалить начальные и конечные пробелы из строки в Java?

Класс String предоставляет два метода для этой задачи: trim() и strip(). Метод strip() был добавлен в Java 11 и считается более надежным, чем trim(), так как использует стандарт Unicode для определения пробельных символов.

Различия между методами:

  • trim() удаляет символы с кодовой точкой ≤ U+0020.
  • strip() использует метод Character.isWhitespace(), который работает с кодовыми точками Unicode и охватывает больше типов пробелов.

Метод strip() является рекомендуемым способом удаления начальных и конечных пробелов. Вот пример:

String removeWhiteSpaces(String input) {
	StringBuilder output = new StringBuilder();
	
	char[] charArray = input.toCharArray();
	
	for (char c : charArray) {
		if (!Character.isWhitespace(c))
			output.append(c);
	}
	
	return output.toString();
}
Run Code

Так как строки в Java неизменяемы, результат метода strip() нужно присвоить новой переменной – исходная строка не изменится.

Обратите внимание: пример со StringBuilder из предыдущего блока удаляет все пробельные символы, включая внутри строки. strip() и trim() удаляют только пробелы по краям.

10. Как отсортировать массив в Java?

Класс Arrays из стандартной библиотеки Java предоставляет несколько перегруженных методов sort() для сортировки массивов примитивов и объектов.

Если нужно отсортировать массив примитивных типов (например, int[]) в естественном порядке, достаточно использовать Arrays.sort():

int[] array = {1, 2, 3, -1, -2, 4};

Arrays.sort(array);

System.out.println(Arrays.toString(array));
Run Code

Если вы работаете с массивом объектов, то объекты должны реализовывать интерфейс Comparable, чтобы Arrays.sort() знал, как их сравнивать. В случае, если нужно задать свою логику сортировки, можно передать объект Comparator:

String[] words = {"banana", "apple", "cherry"};

Arrays.sort(words, Comparator.reverseOrder());

System.out.println(Arrays.toString(words));

Пояснение:

  • Comparable используется для естественного порядка сортировки (например, по алфавиту или по числу).
  • Comparator позволяет задать альтернативные правила (например, по длине строки, в обратном порядке и т.д.).

Узнайте больше о Comparable и Comparator в Java .

11. Как создать сценарий дедлока (deadlock) в Java

Взаимоблокировка (deadlock) – это ситуация, при которой два или более потока навсегда блокируют друг друга, ожидая освобождения ресурсов. Она возникает, когда потоки захватывают одни и те же объекты в разном порядке.

Следующий пример демонстрирует программно созданную взаимоблокировку с участием трех потоков и трех объектов:

public class ThreadDeadlock {

    public static void main(String[] args) throws InterruptedException {
        Object obj1 = new Object();
        Object obj2 = new Object();
        Object obj3 = new Object();
    
        Thread t1 = new Thread(new SyncThread(obj1, obj2), "t1");
        Thread t2 = new Thread(new SyncThread(obj2, obj3), "t2");
        Thread t3 = new Thread(new SyncThread(obj3, obj1), "t3");
        
        t1.start();
        Thread.sleep(5000);
        t2.start();
        Thread.sleep(5000);
        t3.start();        
    }

}

class SyncThread implements Runnable {

    private Object obj1;
    private Object obj2;

    public SyncThread(Object o1, Object o2) {
        this.obj1 = o1;
        this.obj2 = o2;
    }

    @Override
    public void run() {
        String name = Thread.currentThread().getName();

        System.out.println(name + " acquiring lock on " + obj1);
        synchronized (obj1) {
            System.out.println(name + " acquired lock on " + obj1);
            work();
            System.out.println(name + " acquiring lock on " + obj2);
            synchronized (obj2) {
                System.out.println(name + " acquired lock on " + obj2);
                work();
            }
            System.out.println(name + " released lock on " + obj2);
        }
        System.out.println(name + " released lock on " + obj1);
        System.out.println(name + " finished execution.");
    }

    private void work() {
        try {
            Thread.sleep(30000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
Run Code

Каждый поток захватывает один объект и пытается получить второй, уже занятый другим потоком. Так все три потока оказываются в состоянии ожидания друг друга, не в силах продолжить выполнение – происходит взаимоблокировка.

Для диагностики таких ситуаций можно использовать дамп потоков (thread dump) в JVM. Узнайте больше о взаимоблокировках в Java .

12. Как найти факториал целого числа в Java?

Факториал числа n — это произведение всех положительных целых чисел от 1 до n:

F(n) = F(1)*F(2)...F(n-1)*F(n)

Один из распространенных способов вычисления факториала – через рекурсию. Ниже приведен пример:

public static long factorial(long n) {
	if (n == 1)
		return 1;
	else
		return (n * factorial(n - 1));
}
Run Code

Функция factorial вызывает саму себя, пока не достигнет базового случая – n == 1. На каждом шаге возвращается произведение текущего значения n и результата factorial(n - 1).

Однако при больших значениях n рекурсивный подход может привести к ошибке StackOverflowError.

Чтобы избежать этой проблемы, можно использовать итеративный способ:

public static long factorialIterative(long n) {
    long result = 1;

    for (long i = 2; i <= n; i++) {
        result *= i;
    }

    return result;
}

Итеративный подход не создает глубокой цепочки вызовов и работает надежно даже при больших значениях n.

13. Как перевернуть связанный список в Java?

Класс LinkedList предоставляет метод descendingIterator(), который возвращает итератор для обхода элементов в обратном порядке. Это позволяет легко создать новый список с элементами в перевернутом виде.

Пример:

LinkedList<Integer> ll = new LinkedList<>();

ll.add(1);
ll.add(2);
ll.add(3);

System.out.println(ll);

LinkedList<Integer> ll1 = new LinkedList<>();

ll.descendingIterator().forEachRemaining(ll1::add);

System.out.println(ll1);
Run Code

Метод descendingIterator() возвращает итератор, начинающий обход с конца списка. Метод forEachRemaining() используется для добавления каждого элемента в новый список ll1.

Узнайте больше об обращении связанного списка с точки зрения структур данных и алгоритмов.

14. Как реализовать двоичный поиск в Java?

Бинарный (двоичный) поиск – это эффективный алгоритм поиска, который применяется только к отсортированным массивам. Он работает по следующему принципу:

  • Если ключ меньше среднего элемента – выполняйте поиск в первой половине массива.
  • Если ключ больше среднего элемента, то поиск нужно выполнять только во второй половине массива.
  • Если ключ равен среднему элементу массива, то поиск завершается.
  • Наконец, если ключ не найден во всем массиве, то он должен вернуть -1. Это указывает на то, что элемент отсутствует.

Следующий пример кода реализует двоичный поиск:

public static int binarySearch(int arr[], int low, int high, int key) {
	int mid = (low + high) / 2;

	while (low <= high) {
		if (arr[mid] < key) {
			low = mid + 1;
		} else if (arr[mid] == key) {
			return mid;
		} else {
			high = mid - 1;
		}
		mid = (low + high) / 2;
	}

	if (low > high) {
		return -1;
	}

	return -1;
}
Run Code

Метод принимает массив, границы поиска (low, high) и значение key, которое нужно найти. При каждом шаге диапазон поиска сокращается вдвое. Если элемент найден, возвращается его индекс. Если нет — возвращается -1.

Убедитесь, что массив отсортирован, прежде чем использовать бинарный поиск.

15. Напишите программу на Java, иллюстрирующую сортировку слиянием.

Сортировка слиянием (Merge Sort) – это один из самых эффективных алгоритмов сортировки, основанный на принципе «разделяй и властвуй». Сначала массив разбивается на подмассивы до тех пор, пока каждый из них не будет содержать по одному элементу, а затем начинается этап слияния, на котором подмассивы объединяются в отсортированном порядке.

Пример реализации:

public class MergeSort {

	public static void main(String[] args) {
		int[] arr = { 70, 50, 30, 10, 20, 40, 60 };

		int[] merged = mergeSort(arr, 0, arr.length - 1);

		for (int val : merged) {
			System.out.print(val + " ");
		}
	}

	public static int[] mergeTwoSortedArrays(int[] one, int[] two) {
		int[] sorted = new int[one.length + two.length];

		int i = 0;
		int j = 0;
		int k = 0;

		while (i < one.length && j < two.length) {
			if (one[i] < two[j]) {
				sorted[k] = one[i];
				k++;
				i++;
			} else {
				sorted[k] = two[j];
				k++;
				j++;
			}
		}

		if (i == one.length) {
			while (j < two.length) {
				sorted[k] = two[j];
				k++;
				j++;
			}
		}

		if (j == two.length) {
			while (i < one.length) {
				sorted[k] = one[i];
				k++;
				i++;
			}
		}

		return sorted;
	}

	public static int[] mergeSort(int[] arr, int lo, int hi) {
		if (lo == hi) {
			int[] br = new int[1];
			br[0] = arr[lo];

			return br;
		}

		int mid = (lo + hi) / 2;

		int[] fh = mergeSort(arr, lo, mid);
		int[] sh = mergeSort(arr, mid + 1, hi);

		int[] merged = mergeTwoSortedArrays(fh, sh);

		return merged;
	}

}
Run Code

Обратите внимание:

  • Метод mergeSort рекурсивно делит массив пополам.
  • Метод mergeTwoSortedArrays сливает два отсортированных массива в один.
  • Алгоритм стабилен и имеет гарантированную сложность O(n log n).

16. Можно ли создать пирамиду символов на Java?

В Java можно создать текстовую пирамиду с помощью вложенных циклов. Обычно внешний цикл отвечает за строки, а внутренний – за пробелы и символы (например, звездочки).

Пример: пирамида из *, высотой 5 строк:

public class CharacterPyramid {

    public static void main(String[] args) {
        int rows = 5;

        for (int i = 1; i <= rows; i++) {
            // пробелы перед звёздами
            for (int j = i; j < rows; j++) {
                System.out.print(" ");
            }

            // звезды
            for (int k = 1; k <= (2 * i - 1); k++) {
                System.out.print("*");
            }

            System.out.println();
        }
    }

}

Результат:

    *
   ***
  *****
 *******
*********

Каждая строка состоит из нужного количества пробелов и символов. Количество звезд на строке вычисляется по формуле 2 * i - 1, чтобы получалась симметричная пирамида.

Программы-шаблоны — очень популярная тема для интервью. Этот тип вопросов используется для оценки способностей к логическому мышлению интервьюируемого. Обратитесь к Pyramid Pattern Programs in Java за примерами различных способов создания пирамидальных шаблонов.

17. Напишите программу на Java, которая проверяет, содержат ли два массива одинаковые элементы.

Чтобы проверить, содержат ли два массива одинаковые элементы, вам нужно сначала создать набор элементов из обоих массивов, а затем сравнить элементы в этих наборах, чтобы найти элемент, которого нет в обоих наборах. Следующий пример кода показывает, как проверить, содержат ли два массива только общие элементы:

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

public class ArraySameElements {

	public static void main(String[] args) {
		Integer[] a1 = {1,2,3,2,1};
		Integer[] a2 = {1,2,3};
		Integer[] a3 = {1,2,3,4};
		
		System.out.println(sameElements(a1, a2));
		System.out.println(sameElements(a1, a3));
	}

	static boolean sameElements(Object[] array1, Object[] array2) {
		Set<Object> uniqueElements1 = new HashSet<>(Arrays.asList(array1));
		Set<Object> uniqueElements2 = new HashSet<>(Arrays.asList(array2));
		
		// если размеры множеств разные — элементы точно не совпадают
		if (uniqueElements1.size() != uniqueElements2.size()) return false;
		
		for (Object obj : uniqueElements1) {
			// если хотя бы одного элемента нет во втором множестве
			if (!uniqueElements2.contains(obj)) return false;
		}
		
		return true;
	}

}
Run Code
true
false

Пояснение:

  • Дубликаты удаляются при преобразовании в Set.
  • Сравниваются только уникальные элементы.
  • Если множества равны по размеру и содержат одни и те же значения – результат true, иначе false.

18. Как получить сумму всех элементов целочисленного массива в Java?

Один из простейших способов – использовать цикл for для прохождения по всем элементам массива и накопления суммы:

int[] array = { 1, 2, 3, 4, 5 };

int sum = 0;

for (int i : array)
	sum += i;

System.out.println(sum);
Run Code

Цикл for (int i : array) перебирает каждый элемент массива и прибавляет его к переменной sum. После завершения цикла переменная sum содержит сумму всех чисел.

19. Как найти второе по величине число в массиве в Java?

Существует несколько способов решения этой задачи. Один из них – отсортировать массив и взять предпоследний элемент. Однако сортировка – это более затратная операция, особенно при больших массивах.

Более эффективный способ – пройтись по массиву один раз и сохранить два значения: максимальное и второе по величине. Пример:

private static int findSecondHighest(int[] array) {
    int highest = Integer.MIN_VALUE;
    int secondHighest = Integer.MIN_VALUE;

    for (int i : array) {
        if (i > highest) {
            secondHighest = highest;
            highest = i;
        } else if (i > secondHighest && i != highest) {
            secondHighest = i;
        }
    }

    return secondHighest;
}
Run Code

В процессе обхода массива переменная highest сохраняет наибольшее число, а secondHighest отслеживает второе по величине значение. Если текущий элемент больше highest, оба значения обновляются. Если он меньше highest, но все еще больше secondHighest, и при этом не равен highest, то это потенциальное второе по величине число. Такой подход позволяет обойтись без сортировки и найти нужный результат за один проход по массиву.

20. Как перетасовать массив в Java?

Один из способов перемешать массив — использовать класс Random для генерации случайных индексов и обмена элементов местами. Пример:

int[] array = { 1, 2, 3, 4, 5, 6, 7 };

Random rand = new Random();

for (int i = 0; i < array.length; i++) {
	int randomIndexToSwap = rand.nextInt(array.length);
	int temp = array[randomIndexToSwap];
	array[randomIndexToSwap] = array[i];
	array[i] = temp;
}

System.out.println(Arrays.toString(array));
Run Code

Каждый элемент массива обменивается местами с элементом под случайным индексом. Таким образом создается случайная последовательность.

При желании можно повторить процесс несколько раз в дополнительном цикле, чтобы добиться более сильного перемешивания. Также для более надежного алгоритма используется Fisher–Yates shuffle.

21. Как найти строку в текстовом файле в Java?

Один из способов – использовать класс Scanner для построчного чтения содержимого файла и метод contains() – метод для проверки, содержит ли строка нужное значение.:

boolean findStringInFile(String filePath, String str) throws FileNotFoundException {
	File file = new File(filePath);

	Scanner scanner = new Scanner(file);

	// читаем файл построчно
	while (scanner.hasNextLine()) {
		String line = scanner.nextLine();
		if (line.contains(str)) {
			scanner.close();
			return true;
		}
	}
	scanner.close();

	return false;
}
Run Code

Метод возвращает true, если искомая строка найдена хотя бы в одной строке файла. Если нет – возвращает false.

Обратите внимание, этот способ работает корректно, только если искомая строка не содержит символов перевода строки (\n), так как чтение идет построчно.

22. Как напечатать дату в определенном формате в Java?

Для вывода даты в заданном формате используется класс SimpleDateFormat , которому передается строка-шаблон:

String pattern = "MM-dd-yyyy";
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(pattern);

String date = simpleDateFormat.format(new Date());
System.out.println(date); // 06-23-2020
Run Code

Метод format() преобразует объект Date в строку в указанном формате.

Также доступны другие шаблоны, например "dd.MM.yyyy", "yyyy/MM/dd HH:mm:ss" и т.д. Узнайте больше о Java SimpleDateFormat .

23. Как объединить два списка в Java?

Для объединения двух списков можно использовать метод addAll(), который добавляет все элементы одного списка в другой:

List<String> list1 = new ArrayList<>();
list1.add("1");
List<String> list2 = new ArrayList<>();
list2.add("2");

List<String> mergedList = new ArrayList<>(list1);
mergedList.addAll(list2);
System.out.println(mergedList); // [1, 2]
Run Code

Пояснение:

  • Создается новый список mergedList на основе list1.
  • С помощью addAll() в него добавляются элементы из list2.
  • Исходные списки при этом не изменяются.

Метод addAll() работает для любых реализаций ListArrayList, LinkedList и других.

24. Напишите программу на Java, которая сортирует HashMap по значению.

HashMap не является упорядоченной коллекцией, поэтому элементы в ней могут располагаться в произвольном порядке. Чтобы отсортировать элементы по значению, можно скопировать содержимое HashMap во временный список, отсортировать его, а затем записать результат в LinkedHashMap, которая сохраняет порядок вставки:

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

public class SortHashMapByValue {

	public static void main(String[] args) {
		Map<String, Integer> scores = new HashMap<>();

		scores.put("David", 95);
		scores.put("Jane", 80);
		scores.put("Mary", 97);
		scores.put("Lisa", 78);
		scores.put("Dino", 65);

		System.out.println(scores);

		scores = sortByValue(scores);

		System.out.println(scores);
	}

	private static Map<String, Integer> sortByValue(Map<String, Integer> scores) {
		Map<String, Integer> sortedByValue = new LinkedHashMap<>();

		// получаем набор пар ключ-значение
		Set<Entry<String, Integer>> entrySet = scores.entrySet();
		System.out.println(entrySet);

		// создаём список, потому что Set неупорядочен
		List<Entry<String, Integer>> entryList = new ArrayList<>(entrySet);
		System.out.println(entryList);

		// сортируем список по значению
		entryList.sort((x, y) -> x.getValue().compareTo(y.getValue()));
		System.out.println(entryList);

		// заполняем новую карту
		for (Entry<String, Integer> e : entryList)
			sortedByValue.put(e.getKey(), e.getValue());

		return sortedByValue;
	}

}
Run Code

Программа создает HashMap с именами и баллами, затем сортирует пары по значению и сохраняет результат в LinkedHashMap, чтобы сохранить порядок. В процессе используются методы entrySet(), ArrayList и метод сортировки по значению через компаратор. Вывод в консоль демонстрирует шаги преобразования: изначальный порядок, преобразованный список и отсортированную карту.

25. Как удалить все вхождения заданного символа из входной строки в Java?

В классе String нет отдельного метода для удаления символов, но можно использовать метод replace() для замены нужного символа на пустую строку:

String str1 = "abcdABCDabcdABCD";
		
str1 = str1.replace("a", ""); 

System.out.println(str1); // bcdABCDbcdABCD
Run Code

Метод replace("a", "") удаляет все вхождения символа 'a' из строки. Поскольку строки в Java являются неизменяемыми, результат замены не меняет исходную строку, а создает новую. Поэтому полученное значение необходимо либо присвоить новой переменной, либо перезаписать существующую.

Метод replace() чувствителен к регистру. Если нужно удалить символ вне зависимости от регистра, сначала можно привести строку к нижнему или верхнему регистру с помощью toLowerCase() или toUpperCase(). Узнайте больше об удалении символов из строки в Java .

26. Как получить отдельные символы и их количество в строке в Java?

Для подсчета количества вхождений каждого символа в строке можно сначала преобразовать строку в массив символов, а затем пройтись по нему и сохранить счетчики в HashMap:

String str1 = "abcdABCDabcd";

char[] chars = str1.toCharArray();

Map<Character, Integer> charsCount = new HashMap<>();

for (char c : chars) {
	if (charsCount.containsKey(c)) {
		charsCount.put(c, charsCount.get(c) + 1);
	} else
		charsCount.put(c, 1);
}

System.out.println(charsCount); // {a=2, A=1, b=2, B=1, c=2, C=1, d=2, D=1}
Run Code

Метод toCharArray() преобразует строку в массив символов, по которому можно удобно итерироваться. Для подсчета количества каждого символа используется HashMap, где символы выступают в роли ключей, а их количество – в роли значений. При этом регистр символов учитывается: 'a' и 'A' считаются разными символами.

27. Можете ли вы доказать, что String объект в Java является неизменяемым программно?

В следующем примере кода показано, как доказать, что String объект является неизменяемым, а комментарии в коде поясняют каждый шаг:

String s1 = "Java"; // строка "Java" создана в пуле, и ссылка присвоена переменной s1

String s2 = s1; // s2 также содержит ту же ссылку на "Java" в пуле

System.out.println(s1 == s2); // доказательство того, что s1 и s2 ссылаются на один и тот же объект

s1 = "Python"; 
// значение переменной s1 изменилось выше — так как же строки считаются неизменяемыми?

// в этом случае в пуле создается новая строка "Python"
// теперь s1 ссылается на новую строку в пуле 
// НО исходная строка "Java" осталась неизменной и продолжает существовать в пуле
// s2 всё ещё ссылается на исходную строку "Java" в пуле

// доказательство того, что s1 и s2 теперь ссылаются на разные объекты
System.out.println(s1 == s2); 

System.out.println(s2); 
// выводит "Java", что подтверждает: исходная строка осталась без изменений, следовательно, строки в Java неизменяемы
Run Code

Обратите внимание, что объекты String в Java неизменяемы – при изменении строки создается новый объект. Исходная строка остается в памяти (в пуле строк) и может использоваться другими переменными.

Это свойство делает строки безопасными для многопоточной среды и позволяет JVM эффективно управлять памятью с помощью пула строк.

28. Можете ли вы написать код, демонстрирующий наследование в Java?

В Java ключевое слово extends используется для создания подклассов. Ниже приведен пример, где класс Cat наследует переменную color от родительского класса Animal и добавляет собственный метод:

class Animal {
	String color;
}

class Cat extends Animal {
	void meow() {
		System.out.println("Meow");
	}
}
Run Code

Класс Cat является подклассом класса Animal и наследует поле color, определённое в родительском классе. Метод meow() присутствует только в Cat и не определён в Animal, поэтому он является частью поведения, характерного исключительно для подкласса.

Наследование позволяет повторно использовать код и расширять функциональность базовых классов.

29. Как продемонстрировать проблему ромба с множественным наследованием в Java?

Проблема ромба (diamond problem) возникает, когда класс наследует от нескольких родительских классов, реализующих один и тот же метод. В таком случае возникает неоднозначность: непонятно, какую версию метода нужно вызывать.

Java не поддерживает множественное наследование классов именно для предотвращения этой проблемы. Ниже приведен пример, который иллюстрирует ситуацию:

interface I {
	void foo();
}
class A implements I {
	public void foo() {}
}

class B implements I {
	public void foo() {}
}

// Ошибка компиляции: Java не поддерживает наследование от двух классов
class C extends A, B { // не скомпилируется
	public void bar() {
		super.foo();
	}
}
Run Code

Интерфейс I реализуется двумя классами — A и B, каждый из которых содержит собственную реализацию метода foo(). Если попытаться создать класс C, наследующий одновременно и A, и B, это приведет к ошибке компиляции, поскольку конструкция class C extends A, B в Java недопустима. Такая ситуация наглядно демонстрирует, почему Java запрещает множественное наследование классов: возникает неоднозначность, какой именно метод foo() должен быть унаследован и вызван в C.

30. Как проиллюстрировать пример try-catch в Java?

В Java конструкция try-catch используется для обработки исключений. Ниже приведен пример, в котором перехватывается исключение FileNotFoundException::

try {
	FileInputStream fis = new FileInputStream("test.txt");
} catch(FileNotFoundException e) {
	e.printStackTrace();
}
Run Code

Если файл test.txt не существует, будет выброшено исключение, и управление передается в блок catch.

Начиная с Java 7, вы также можете перехватывать несколько исключений в одном блоке catch, как показано в следующем примере. Это полезно, когда у вас есть один и тот же код во всех блоках catch.

public static void foo(int x) throws IllegalArgumentException, NullPointerException {
	// какой-то код
}

public static void main(String[] args) {
	try {
		foo(10);
	} catch (IllegalArgumentException | NullPointerException e) {
		System.out.println(e.getMessage());
	}
}
Run Code

Метод foo может выбрасывать два типа исключений. Оба перехватываются в одном блоке catch через |. Такой подход уменьшает дублирование кода и делает обработку более компактной.

31. Напишите программу на Java, которая отображает NullPointerException.

Если вы вызываете функцию в null, она выбросит исключение NullPointerException, как показано в следующем примере кода:

public static void main(String[] args) {
	printString(null, 3);
	
}

static void printString(String s, int count) {
	for (int i = 0; i < count; i++) {
		System.out.println(s.toUpperCase()); // Исключение в потоке "main" java.lang.NullPointerException
	}
}
Run Code

Для ранней проверки необходимо предусмотреть проверку на null, как показано в следующем примере кода:

static void printString(String s, int count) {
	if (s == null) return;
	for (int i = 0; i < count; i++) {
		System.out.println(s.toUpperCase());
	}
}
Run Code

Также, в зависимости от требований проекта, можно явно выбросить исключение с пояснением:

static void printString(String s, int count) {
    if (s == null) 
        throw new IllegalArgumentException("Входная строка не должна быть null");

    for (int i = 0; i < count; i++) {
        System.out.println(s.toUpperCase());
    }
}

Такой подход помогает быстрее выявить ошибку и указать, что метод был вызван с недопустимым аргументом. Это может быть полезно при написании библиотек или API, где важно контролировать входные значения.

32. Как создать запись в Java?

Record – это особый тип классов, появившийся в Java 16. Он позволяет описывать неизменяемые структуры данных с минимальным количеством кода. При создании записи автоматически генерируются методы equals(), hashCode(), toString() и методы доступа к полям.

Записи являются финальными по умолчанию и неявно наследуются от java.lang.Record. Это удобный способ описания POJO (Plain Old Java Object), когда нужен только набор полей и стандартное поведение. Пример:

import java.util.Map;
 
public record EmpRecord(int id, String name, long salary, Map<String, String> addresses) {
}
Run Code

Вся структура и поведение объекта описываются в одной строке – не нужно писать конструктор и геттеры вручную.

Узнайте больше о записях в Java . Подробности о POJO см. в статье Plain old Java object на Wikipedia .

33. Как создать текстовые блоки в Java?

Начиная с Java 15, появилась возможность использовать текстовые блоки – это удобный способ создания многострочных строк. Текстовый блок заключается в тройные двойные кавычки и сохраняет форматирование строки. Пример:

String textBlock = """
		Hi
		Hello
		Yes""";
Run Code

Такой блок эквивалентен строке:

String text = "Hi\nHello\nYes";

Текстовые блоки делают код чище и читаемее, особенно при работе с JSON, SQL, HTML и другими многострочными данными. Функция особенно полезна при работе с шаблонами или длинными строками, где важно сохранить структуру.

34. Покажите пример выражений switch и операторов case с несколькими метками в Java.

Выражения switch были добавлены в качестве стандартной функции в Java 14. Они позволяют использовать switch как выражение, возвращающее значение, и поддерживают несколько меток case для одной ветки. Пример с несколькими метками и использованием yield:

int choice = 2;

int x = switch (choice) {
    case 1, 2, 3:
	    yield choice;
    default:
	    yield -1;
};

System.out.println("x = " + x); // x = 2
Run Code

switch возвращает значение, которое присваивается переменной x. Метки case 1, 2, 3 объединены в одну ветку, а ключевое слово yield используется для возврата значения из блока.

Вы также можете использовать лямбда-выражения в выражениях switch.

String day = "TH";
String result = switch (day) {
    case "M", "W", "F" -> "MWF";
    case "T", "TH", "S" -> "TTS";

    default -> {
	    if (day.isEmpty())
		    yield "Please insert a valid day.";
	    else
		    yield "Looks like a Sunday.";
    }
};

System.out.println(result); // TTS
Run Code

Ветви с -> являются более краткой формой. default может содержать блок кода и использовать yield для возврата значения. Несколько меток в case позволяют обрабатывать сразу несколько значений одинаково.

35. Как скомпилировать и запустить класс Java из командной строки?

Этот пример относится к следующему файлу Java:

public class Test {

public static void main(String args[]) {
		System.out.println("Hi");
	}

}
Run Code

Вы можете скомпилировать его с помощью следующей команды:

javac Test.java

Эта команда создаст файл Test.class в той же директории.

Чтобы запустить класс, используйте следующую команду:

java Test

Начиная с java 11 команда также скомпилирует программу, если файл класса отсутствует:

java Test.java

Если класс находится в пакете, например com.example, то он должен находиться внутри папки com/example. Команда для компиляции и запуска:

java com/example/Test.java

Если вашему классу требуются дополнительные JAR-файлы для компиляции и запуска (например, log4j), используйте параметр java -cp или --class-path. Например:

java -cp .:~/.m2/repository/log4j/log4j/1.2.17/log4j-1.2.17.jar  com/example/Test.java

36. Как создать перечисление в Java?

Перечисление (enum) — это специальный тип, используемый для определения ограниченного набора постоянных значений. В Java перечисления неявно наследуются от класса java.lang.Enum и реализуют интерфейсы Serializable и Comparable.

Пример базового перечисления:

public enum ThreadStates {
	START,
	RUNNING,
	WAITING,
	DEAD;
}
Run Code

ThreadStates содержит четыре фиксированные константы: STARTRUNNINGWAITING, и DEAD. Эти значения можно использовать как тип данных для переменных, аргументов методов и т.д. Перечисления в Java могут также содержать поля, конструкторы и методы, что позволяет использовать их как полноценные объекты. Узнайте больше о перечислениях в Java .

37. Как использовать forEach() метод в Java?

Метод forEach() предоставляет удобный способ выполнить действие над каждым элементом коллекции. Вместо использования явного итератора, можно сократить код, передав лямбда-выражение или ссылку на метод.

Пример с использованием итератора:

List<String> list = new ArrayList<>();

Iterator<String> it = list.iterator();

while (it.hasNext()) {
	System.out.println(it.next());
}
Run Code

Альтернативный вариант с использованием forEach() и ссылки на метод:

List<String> list = new ArrayList<>();

list.forEach(System.out::print);
Run Code

Метод forEach() позволяет обойтись без явного цикла while и итератора, что делает код более компактным и читаемым. В приведенном примере каждому элементу списка применяется метод print, который выводит содержимое списка в консоль. Такой подход особенно удобен при работе с коллекциями, потоками (streams) и функциональными операциями.

38. Как написать интерфейс с методом default и static?

Java 8 представила методы по умолчанию (default) и статические (static) методы в интерфейсах. Это позволило частично сблизить интерфейсы с абстрактными классами. Следующий пример кода показывает, как объявить интерфейс с такими методами:

public interface Interface1 {
	
	// обычный абстрактный метод
	void method1(String str);
	
	default void log(String str) {
		System.out.println("I1 logging::" + str);
	}
	
	static boolean isNull(String str) {
		System.out.println("Interface Null Check");

		return str == null ? true : "".equals(str) ? true : false;
	}

}
Run Code

Узнайте больше о методах default и staticинтерфейсах в разделе Изменения интерфейсов Java 8 .

39. Как создать функциональный интерфейс?

Интерфейс, содержащий ровно один абстрактный метод, называется функциональным интерфейсом. Главное преимущество таких интерфейсов в том, что их можно использовать с лямбда-выражениями, избавляясь от необходимости писать полноценные анонимные классы.

Аннотация @FunctionalInterface указывает, что интерфейс должен содержать только один абстрактный метод. Это правило будет проверяться на этапе компиляции. Ниже приведен пример функционального интерфейса:

@FunctionalInterface
interface Foo {
	void test();
}
Run Code

40. Покажите пример использования лямбда-выражений в Java.

Runnable – классический пример функционального интерфейса. Он содержит только один абстрактный метод, поэтому вы можете использовать лямбда-выражение для его реализации. Это позволяет сократить количество кода и сделать его более читаемым.

Пример:

Runnable r1 = () -> System.out.println("My Runnable");

41. Приведите примеры перегрузки и переопределения в Java.

Когда в классе определены два или более методов с одинаковым именем, но разными параметрами, это называется перегрузкой метода. Следующий пример показывает перегруженные версии метода print:

class Foo {
	void print(String s) {
		System.out.println(s);
	}

	void print(String s, int count) {
		while (count > 0) {
			System.out.println(s);
			count--;
		}
	}

}
Run Code

Методы print имеют одинаковое имя, но разную сигнатуру (разное количество параметров). Это позволяет вызывать метод с различными наборами аргументов.

Когда метод суперкласса переопределяется в подклассе, это называется переопределением. В следующем примере метод printName() реализован как в родительском, так и в дочернем классе:

class Base {
	void printName() {
		System.out.println("Base Class");
	}
}

class Child extends Base {
	@Override
	void printName() {
		System.out.println("Child Class");
	}
}
Run Code

Аннотация @Override указывает, что метод printName() в классе Child переопределяет одноименный метод из класса Base. Узнайте больше о переопределении и перегрузке в Java .

42.-49. Угадай результат

Проверьте себя, угадав вывод следующих фрагментов кода.


String s1 = "abc";
String s2 = "abc";

System.out.println("s1 == s2 is:" + s1 == s2);
Run Code

Ответ

false

Вывод данного оператора происходит false потому, что +оператор имеет более высокий приоритет, чем == оператор. Поэтому данное выражение оценивается как “s1 == s2 is:abc” == “abc”, что равно false.


String s3 = "JournalDev";
int start = 1;
char end = 5;

System.out.println(s3.substring(start, end));
Run Code

Ответ

ourn

Вывод данного оператора – ourn. Первый символ автоматически приводится к типу  int. Затем, поскольку индекс первого символа равен 0, он начнется с o и будет печатать до  n. Обратите внимание, что  String substring метод создает подстроку, которая начинается с индекса  start и продолжается до символа с индексом  end - 1.


HashSet shortSet = new HashSet();

	for (short i = 0; i < 100; i++) {
    shortSet.add(i);
    shortSet.remove(i - 1);
}

System.out.println(shortSet.size());
Run Code

Ответ

100

i — переменная типа short, а i - 1 — результат типа int, автоупаковывается в Integer. Коллекция HashSet, содержащая Short, не находит в себе Integer-объект и не удаляет ничего. В итоге размер остается 100.


try {
	if (flag) {
  		while (true) {
   		}
   	} else {
   		System.exit(1);
   	}
} finally {
   	System.out.println("In Finally");
}
Run Code

Ответ

Если flag == true, программа входит в бесконечный цикл. Если false завершает выполнение через System.exit(1). В обоих случаях блок finally не выполняется.


String str = null;
String str1="abc";

System.out.println(str1.equals("abc") | str.equals(null));
Run Code

Ответ

Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.equals(Object)" because "<local1>" is null

Этот оператор печати вызовет исключение java.lang.NullPointerException, потому что логический оператор OR (||) оценивает оба операнда перед тем, как вернуть результат. Поскольку переменная str равна null, вызов метода .equals() приведет к исключению. Всегда рекомендуется использовать сокращенные логические операторы, такие как ||и &&, которые оценивают литеральные значения слева направо. В этом случае, поскольку первый литерал вернет true, он пропустит оценку второго литерала.


String x = "abc";
String y = "abc";

x.concat(y);

System.out.print(x);
Run Code

Ответ

abc

Метод concat() возвращает новую строку, но результат не присваивается переменной. Переменная x остается неизменной.


public class MathTest {

 	public void main(String[] args) {  		
   		int x = 10 * 10 - 10;
   		
   		System.out.println(x);
   	}
   
}
Run Code

Ответ

Error: Main method is not static in class MathTest, please define the main method as:
   public static void main(String[] args)

Хотя может показаться, что вопрос касается порядка выполнения математических операторов, на самом деле он направлен на то, чтобы заметить, что основной метод не был объявлен как static, но он должен быть определён как public static void main(String[] args). Без static JVM не сможет вызвать его при запуске программы.


public class Test {
   
  	public static void main(String[] args) {
   		try {
   			throw new IOException("Hello");
   		} catch(IOException | Exception e) {
   			System.out.println(e.getMessage());
   		}
   	}
}
Run Code

Ответ

Test.java:5: error: cannot find symbol
   			throw new IOException("Hello");
   			          ^
  symbol:   class IOException
  location: class Test
Test.java:6: error: cannot find symbol
   		}catch(IOException | Exception e) {
   		       ^
  symbol:   class IOException
  location: class Test
2 errors

Исключение IOException уже включено в Exception, и оба нельзя использовать вместе в многократном catch через |. Кроме того, IOException не импортирован – потребуется import java.io.IOException. Этот код приводит к ошибке времени компиляции.

50. Найдите 5 ошибок в следующем фрагменте кода.

package com.digitalocean.programming-interviews;

public class String Programs {

	static void main(String[10] args) {
		String s = "abc"
		System.out.println(s);
	}
}
Run Code

Ответ

  1. Имя пакета не может содержать дефисов. Допустимы только буквы, цифры и подчеркивания.
  2. Имя класса не может содержать пробелов. String Programs – недопустимо, нужно, например, StringPrograms.
  3. Основной метод – нет public, поэтому он не запустится.
  4. Аргумент основного метода не должен указывать размер. String[10] args – синтаксически неверно, должно быть String[] args.
  5. В определении строки String s = “abc” отсутствует точка с запятой.

Заключение

В эту подборку из 50 вопросов для собеседования по программированию на Java вошли вопросы от начального до экспертного уровня, которые помогут вам подготовиться к собеседованию.

Перевод статьи «Top 50 Java Programming Interview Questions».

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Прокрутить вверх