Poniższy wpis jest zbiorem zadań sprawdzających wiedzę z Javy, które zdarzyło mi się otrzymać na kilku rekrutacjach w ciągu ostatnich kilku lat. Pomyślałem, że warto zapisać i podzielić się tymi z nich, które okazały się mniej, lub bardziej podchwytliwe.
Pod każdym pytaniem wypisałem poprawną odpowiedź. Zachęcam do samodzielnego przetestowania podanych rozwiązań, tak by je dobrze zrozumieć i zapamiętać.
Lista pytań
- Czy konstruktor może być prywatny?
- Jakie zmiany wprowadzono w Java 8?
- Która kolekcja w Java przechowuje unikalne wartości?
- Czym różni się klasa abstrakcyjna od interfejsu?
- Jaka jest różnica między
==
, a.equals()
? - Czy można nadpisać prywatną metodę?
- Czym w Java różni się nadpisywanie (overriding) od przeciążania (overloading)?
- Wyjaśnij, co to jest String Pool
- Wyjątki checked, unchecked – czym się różnią?
- Czy po każdym bloku
try
musi następować blokcatch
? - Czym się różni parametr od argumentu?
Lista odpowiedzi
Czy konstruktor może być prywatny?
Poprawna odpowiedź brzmi: Tak, konstruktor może być prywatny, czyli oznaczony modyfikatorem private
. Takie podejście pozwala na kontrolę tworzenia nowych obiektów danej klasy. Konstruktory prywatne są używane w połączeniu np. ze wzorcem Factory Method lub wzorcem (w sumie to antywzorcem) Singleton.
Poniżej znajduje się przykład:
public class Car {
private Car() {} // prywatny konstruktor
public static Car getCarInstance() {
return new Car();
} // metoda statyczna zwracająca instancję klasy
public void showMessage() {
System.out.println("Just some message to print anything");
} // prosta metoda printująca łańcuch znaków
}
public class Main {
public static void main(String[] args) {
Car.getCarInstance().showMessage(); // "Just some message to print anything"
}
}
Jakie zmiany wprowadzono w Java 8?
Odpowiedź – są to między innymi:
- wyrażenia lambda
- stream API (strumienie)
- domyślne metody w interfejsach
- wartości opcjonalne
Która kolekcja w Java przechowuje unikalne wartości?
Odpowiedź – jest to Set, czyli kolekcja która zawiera jedynie unikalne elementy. Zbiory są implementacją interfejsu Set
i gwarantują, że każdy element jest przechowywany tylko raz. Oznacza to, że próba dodania duplikatu do zestawu nie powiedzie się.
Set<String> fruits = new HashSet<>();
// Dodaj elementy do zbioru
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Orange");
fruits.add("Apple"); // Duplikat - zostanie zignorowany
// Sprawdź rozmiar zbioru
System.out.println("Liczba owoców: " + fruits.size()); // 3
Czym różni się klasa abstrakcyjna od interfejsu?
Najważniejsze różnice to:
- Klasa abstrakcyjna to klasa, która zawierać zarówno metody abstrakcyjne, jak i zwykłe metody z implementacją. Interfejs może posiadać jedynie metody abstrakcyjne (ale od Javy 8 można tworzyć metody domyślne i statyczne w interfejsie, które zawierają implementacje metod).
- Klasa może dziedziczyć tylko po jednej klasie abstrakcyjnej, ale może implementować wiele interfejsów.
- Klasa abstrakcyjna może zawierać zmienne oznaczone jako
final, non-final, static i non-static
. - Metody klas abstrakcyjnych mogą mieć modyfikatory dostępu takie jak
public, private, protected, static
, natomiast metody interfejsów są domyślnie publiczne, nie można tam używać żadnych innych modyfikatorów dostępu. - Klasa abstrakcyjna może implementować interfejs, ale interfejs nie może implementować klasy abstrakcyjnej.
Jaka jest różnica między ==, a .equals()?
W uproszczeniu, ==
to porównanie referencji, czyli sprawdzenie czy oba obiekty wskazują na to samo miejsce w pamięci. Z kolei .equals()
porównuje wartości obiektów. Przykład znajduje się poniżej:
String s1 = "HELLO";
String s2 = "HELLO";
String s3 = new String("HELLO");
System.out.println(s1 == s2); // true
System.out.println(s1 == s3); // false
System.out.println(s1.equals(s2)); // true
System.out.println(s1.equals(s3)); // true
Czy można nadpisać prywatną metodę?
Odpowiedź brzmi: Nie, nie można nadpisać metody oznaczonej modyfikatorem private
, ponieważ nie jest ona widziana przez żadną inną klasę.
Czym w Java różni się nadpisywanie (overriding) od przeciążania (overloading)?
Poprawna odpowiedź:
Kiedy sygnatura metody (nazwa i parametry) jest taka sama w klasie nadrzędnej i klasie potomnej, nazywa się to nadpisywaniem (overriding). Kiedy co najmniej dwie metody w tej samej klasie mają tę samą nazwę, ale różne parametry, nazywa się to przeciążaniem (overloading). Poniżej napisałem przykłady obu tych pojęć:
Overloading:
class MathOperations {
// Przeciążona metoda add, która dodaje dwa inty
int add(int a, int b) {
return a + b;
}
// Przeciążona metoda add, która dodaje trzy inty
int add(int a, int b, int c) {
return a + c + b;
}
}
public class Main {
public static void main(String[] args) {
MathOperations math = new MathOperations();
System.out.println(math.add(2, 3)); // 5
System.out.println(math.add(2, 3, 4)); // 9
}
}
Overriding:
// Klasa nadrzędna
class Animal {
// Metoda, która zostanie nadpisana w klasie potomnej
void makeSound() {
System.out.println("Animal makes a sound");
}
}
// Klasa potomna
class Dog extends Animal {
// Nadpisanie metody makeSound z klasy nadrzędnej
@Override
void makeSound() {
System.out.println("Dog barks");
}
}
public class Main {
public static void main(String[] args) {
Animal myDog = new Dog();
myDog.makeSound(); // Dog barks
}
}
Wyjaśnij, co to jest String Pool
String Pool, znany również jako String Intern Pool, to specjalny obszar w pamięci zarządzany przez JVM, który jest używany do przechowywania unikalnych obiektów typu String. Głównym celem String Poola jest optymalizacja pamięci poprzez przechowywanie tylko jednej kopii każdego niezmiennego łańcuchu znaków.
Wyjątki checked, unchecked – czym się różnią?
To akurat jedno z częstszych pytań na rozmowach rekrutacyjnych, ale warto je przypomnieć.
Wyjątki kontrolowane (checked exceptions) są sprawdzane przez kompilator na etapie kompilacji. Oznacza to, że programista musi je obsłużyć, inaczej program się nie skompiluje. Przykład:

Widzimy tutaj, że kompilator wymaga od nas zabezpieczenia się przed brakiem pliku w podanej lokalizacji. Gdy opakujemy całość w try - catch
(lub kiedy dodamy wyjątek do sygnatury metody), kompilator nie zwraca już błędu:

Inaczej wygląda sprawa z wyjątkami niekontrolowanymi (unchecked exceptions). Są to wyjątki, które najczęściej wynikają z błędu programisty – np. dzielenie przez zero. Nie są one wyłapywane na etapie kompilacji, a kompilator nie wymusza na nas obsłużenia takiego wyjątku. Przykład:

Powyżej widać, jak próba wywołania metody length()
na niezainicjalizowanym obiekcie powoduje wyrzucenie NullPointerException
. Opakowanie tego fragmentu w try – catch pozwala na obsłużenie wyjątku:

Czy po każdym bloku try musi następować blok catch?
Odpowiedź brzmi – nie, nie musi. Mimo że konstrukcja try-catch
jest często używana w parze do obsługi wyjątków, istnieje kilka scenariuszy, w których blok catch
nie jest wymagany. Przykładem może być użycie bloku finally
po bloku try
, który zawsze zostanie wykonany, niezależnie od tego, czy wystąpił wyjątek, czy nie:
int[] numbers = {1, 2, 3, 4, 5};
try {
System.out.println(numbers[5]); // Wywoła ArrayIndexOutOfBoundsException
} finally {
System.out.println("Blok finally zawsze się wykona.");
}
Czym się różni parametr od argumentu?
Poprawna odpowiedź: Parametr i argument w kontekście metod w Java to dwa pojęcia, które często są ze sobą mylone. Parametr to zmienna, która jest deklarowana w definicji metody i służy do przyjmowania wartości podczas wywołania tej metody. Można go postrzegać jako miejsce na wartość, którą przekażemy później.
public int addSomeNumbers(int a, int b) {
return a + b;
}
W powyższym przypadku, a
i b
to parametry metody addSomeNumbers
.
Jeśli wywołamy tę metodę:
addSomeNumbers(3, 4);
To 3 i 4 staną się argumentami tej metody. Proste, prawda? 🙂