programowanieProgramista Backend

Wyjaśnij, jak działa porządek rozwiązywania przeciążeń metod (method resolution order) przy dziedziczeniu w Javie oraz jakie pułapki mogą się pojawić.

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź.

W Javie rozwiązywanie przeciążeń (overloading) i nadpisywanie (overriding) metod odbywa się zgodnie z określonymi zasadami. Przy przeciążeniu (overloading) kompilator wybiera najbardziej odpowiednią metodę spośród wszystkich wersji na etapie kompilacji, bazując na typie argumentów w miejscu wywołania.

WaŜne punkty:

  • Przy istnieniu przeciążonych metod we wszystkich klasach i klasach nadrzędnych, najpierw szuka się najwyższych możliwych dopasowań w bieżącej klasie, a następnie — w klasach nadrzędnych.
  • Wybór metody odbywa się na etapie kompilacji, a nie wykonania.
  • Przekształcenia typów (widening, autoboxing, varargs) są stosowane w razie potrzeby.

Przykład:

class Super { void print(Number n) { System.out.println("Super Number"); } } class Sub extends Super { void print(Integer i) { System.out.println("Sub Integer"); } } ... Sub s = new Sub(); s.print(5); // Sub Integer s.print(5.0); // Super Number

Tutaj print(Integer) zostanie wywołane dla argumentu typu Integer, a dla typu Double — metoda rodzicielska.

Pytanie z pułapką.

Jeśli w dziedziczeniu metoda została nadpisana (override) i przeciążona (overload), która z nich zostanie wywołana przy polimorfizmie?

Odpowiedź: Przy polimorfizmie zawsze wybierana jest wersja metody na podstawie rzeczywistego typu obiektu dla metod nadpisanych (overriding). Dla metod przeciążonych wybór odbywa się na etapie kompilacji na podstawie typu odniesienia.

class A { void test(Number n) { System.out.println("A:Number"); } } class B extends A { void test(Integer n) { System.out.println("B:Integer"); } @Override void test(Number n) { System.out.println("B:Number"); } } ... A obj = new B(); obj.test(1); // Wywoła B:Number, mimo że istnieje test(Integer)

Przykłady rzeczywistych błędów spowodowanych nieznajomością szczegółów tematu.


Historia

Programista dodał przeciążoną wersję metody do podklasy, spodziewając się, że zostanie ona wywołana dla wszystkich dziedziców. W produkcji okazało się, że wywołanie przez odniesienie do klasy rodzicielskiej prowadzi do wyboru metody na podstawie typu odniesienia, a nie obiektu. W efekcie wymagana logika nie była wykonywana.


Historia

W dużym projekcie błędnie przeciążono metodę equals(Object) z sygnaturą equals(MyClass obj), myśląc, że zastąpi standardowy equals. Przy porównaniach w kolekcjach HashSet działał domyślny equals(Object), co prowadziło do braku zgodności logiki i utraty danych.


Historia

Przy dodawaniu nowej wersji metody z varargs do hierarchii klas wydawało się, że dla tablic zostanie wywołana potrzebna wersja. Jednak przy przekazywaniu tablicy jawnie, kompilator wybrał nie to przeciążenie, w rezultacie czego przetwarzanie przebiegało niepoprawnie, a błąd długo pozostawał niezauważony.