Kategorie szkoleń | Egzaminy | Kontakt
  • 1
  • 14
  • 170

Witam, mam problem ze zrozumieniem działania programu. Mam pewne przypuszczenia, ale może najpierw kod:

 

class A 
{
    int x = 5;
} 
class B extends A 
{
    int x = 6;
} 
public class CovariantTest 
{
    public A getObject() 
    {
       return new A();
    } 
    public static void main(String[]args) 
    {
       CovariantTest c1 = new SubCovariantTest();
       System.out.println(c1.getObject().x);
    }
}

class SubCovariantTest extends CovariantTest 
{
    public B getObject() 
    {
       return new B();
    }
}

 

Wynikiem działania programu jest:

5

Wydawać by się mogło, że używając c1.getObject(), dostajemy obiekt klasy B (polimorfizm - wywołana zostaje przesłonięta metoda z obiektu klasy SubCovariantTest zwracająca new B()), więc odwołujemy się do zmiennej instancji x o wartości 6.

Natomiast wychodzi na to, że używając c1.getObject(), dostajemy obiekt klasy A, pomimo że używamy przesłoniętej metody, która powinna zwracać B. Dlatego wartość zmiennej x wynosi 5. Dlaczego wywołanie getObject() na obiekcie klasy SubCovariantTest daje w rezultacie obiekt klasy A?

Maciej_Krauze
  • Zapytał
  • @ Maciej_Krauze | 12.11.2014
    • lider
    • laureat
    • 45
    • 16
    • 58

Odpowiedź (1)

  • 13

Kilka faktów dla przypomnienia:
- Polimorfizm i związane z tym przesłanianie metod (inne spotykane określenia to "nadpisanie" lub "przedefiniowywanie", ang. overriding) dotyczy tylko i wyłącznie metod instancyjnych (co od razu wyklucza metody statyczne, pola składowe, klasy zarówno instancyjne jak statyczne)
- Polimorficzny i dynamiczny dobór właściwej metody z klasy potomnej odbywa się w czasie wykonania (ang. runtime)
- W pozostałych przypadkach (statyczne elementy i pola instancyjne), gdy zachodzi dziedziczenie mówimy o ukrywaniu (ang. hiding) metod statycznych lub pól w klasie potomnej – tym razem jest to rozwiązywane na etapie kompilacji, a decydującym czynnikiem jest typ referencji, a nie obiekt klasy potomnej, bo kompilator go po prostu może nie znać na tym etapie.

Spójrzmy na to z innej strony – może będzie łatwiej zrozumieć mechanizm i wynik działania tego przykładu, który podałeś.

Taki zapis:
CovariantTest c1 = new SubCovariantTest(); zawiera rzutowanie w górę (ang. upcasting), a że jest to rzutowanie bezpieczne (nie jest możliwe wystąpienie ClassCastException), to właśnie dlatego kompilator nie wymaga od nas jawnej konwersji typu:

CovariantTest c1 = (CovariantTest)new SubCovariantTest();


Ale niejawnie poprzez zapis CovariantTest c1 = new XYZ(); dajemy kompilatorowi wskazówkę: Hej! Cokolwiek będzie się odwoływało do XYZ, a nie jest polimorficzne, rzutuj na CovariantTest. Czyli w dwóch słowach: zmienna referencyjna i jej typ (CovariantTest c1) mają znaczenie przy braku właściwości polimorficznych, a nie typ obiektu potomnego.

Sprawdź taki przypadek:
SubCovariantTest c1 = new SubCovariantTest();
System.out.println((((CovariantTest)c1).getObject()).x);

Tu jawnie rzutujemy w górę, w odróżnieniu od niejawnego rzutowania poprzez uproszczony zapis CovariantTest c1 = new SubCovariantTest().
Efekt jest identyczny (wynik 5, a nie 6), pomimo iż nie mamy wątpliwości, że c1.getObject() MUSI zwrócić instancję klasy B.

Czyli wywołanie c1.getObject() było faktycznie polimorficzne, ale ze względu, iż nastąpiło niejawne rzutowanie do CovariantTest, kompilator już wiedział z góry, iż odwołanie się pośrednio do elementu niepolimorficznego, czyli "x" w c1.getObject().x, tyczy się klasy A (bo CovariantTest.getObject() zwraca typ A) i tam powinien on szukać ostatecznie wartości zmiennej.

Na koniec jeszcze jedno ćwiczenie. Rozbijmy zapis c1.getObject().x na części składowe:

 

CovariantTest c1 = new SubCovariantTest();
? a = c1.getObject();

 

Jaki typ może być wstawiony zamiast pytajnika? Tylko i wyłącznie "A". Jeżeli zastąpimy pytajnik typem "B" to wówczas kompilator zgłosi błąd. Więc, jeśli tylko "A" jest znane kompilatorowi w klasie CovariantTest, to w konsekwencji tylko "x" z tego typu "A" jest dostępne na tym etapie i użyte przez kompilator w przykładzie podanym w pytaniu.

  • Odpowiedział
  • @ | 12.11.2014
  • TRENER ALTKOM AKADEMII
Komentarze
Dziękuję, Adamie, za dokładne wyjaśnienie. Czy to jest tak że c1.getObject() zwraca instancję klasy B ale z referencją typu A? Coś jakby metoda zwracała "A a = new B();"? Jeśli tak to chyba już wszystko rozumiem.
Skomentował : @ Maciej_Krauze ,13.11.2014
  • 45
  • 16
  • 58
Tak, dokładnie - poprawiłem nieco końcówkę mojej poprzedniej odpowiedzi. Popatrz, z poziomu klasy CovariantTest kompilator tylko „wie”, iż getObject() zwraca typ A - a że chcemy dostać się do „x”, czyli elementu niepolimorficznego, to będzie w konsekwencji wartość zmiennej „x” z klasy A.
Skomentował : @ TRENER ALTKOM AKADEMII ,13.11.2014
Dodałem jeszcze proste ćwiczenie do mojej pierwszej odpowiedzi:
...
CovariantTest c1 = new SubCovariantTest();
? a = c1.getObject();
...

Skomentował : @ TRENER ALTKOM AKADEMII ,13.11.2014