interfejsy i polimorfizm

0

Przykład:

public interface Pojazd {
public int getPredkosc();
public double getSkok();
}
public class Rower implements Pojazd {
	public double skok;
	int predkosc;
	public Rower(int predkosc, double skok) {
		this.predkosc=predkosc;
		this.skok=skok;
	}
	public double getSkok() {
		return skok;
	}
	public int getPredkosc() {
		return predkosc;
	}
}

Jak to się dzieje że kiedy tworzę obiekt:

Pojazd apple = new Rower(20, 3.0);
System.out.println(cos.getSkok());

to to zadziała? Skąd wiadomo co robi metoda getSkok() skoro jest ona zdefiniowana w klasie Rower a obiekt apple jest typu Pojazd gdzie jest tylko zdeklarowana metoda getSkok()?

0

To co napisales na pewno nie zadziala. Zmiennej cos nigdzie nie ma. Ale to tylko mlautki problemik.

Wlasnie nie wiadomo co robi metoda getSkok() jesli wywolujesz ja uzywajac interfejsu - a raczej, czytajacy kod, nie wiedzacy jaki typ naprawde sie za interfejsem kryje, nie wie co zrobi ta metoda. Metoda interfejsu tylko dyktuje kontrakt - i klasy implementujace ten interfejs powinny sie go trzymac.

0

miało być oczywiście tak:

Pojazd apple = new Rower(20, 3.0);
System.out.println(apple.getSkok());

no i wtedy w wyniku dostaje 3.0, dalej nie rozumiem skąd typ Pojazd wiedzial jak działa metoda getSkok()?

0

Tak właśnie działa polimorfizm. Dodatkowo implementacja metody nie musi być nawet znana na etapie kompilacji, może być dostarczona już w trakcie wykonania programu.
Prawdopodobnie w dużym uproszczeniu i może nie do końca zgodnie z rzeczywistością(nigdy mi się nie chciało zagłębiać w szczegóły implementacji tego w javie): obiekt ma w sobie tablicę adresów wszystkich swoich metod. W momencie wywołania metody, środowisko uruchomieniowe podejrzy sobie gdzie jest ta konkretna implementacja i odpali ją.
To wszystko dzieje się poza udziałem programisty - w javie polimorfizm dostaje się niejako z automatu(w przeciwieństwie do takiego C++).

0

? Przecież właśnie na tym polega polimorfizm...
W chwili wywołania metody, na podstawie informacji typu RTTI (Run Time Type Information), następuje sprawdzenie jakiego typu jest obiekt na którym wywołujesz metodę (albo na podstawie np. Virtual Methods Pointers Table jak w C++) i wywolana zostaje metoda z odpowiedniej klasy.

3

Powoli powoli, bez technicznego podejścia implementacyjnego. Najpierw sobie wyjaśnij pojęcia:

skąd wiadomo co robi metoda getSkok() skoro jest ona zdefiniowana w klasie Rower a obiekt apple jest typu Pojazd

Nie masz OBIEKTU apple. Masz zmienną o nazwie apple. Ta zmienna NIE JEST obiektem. To jest... to jest tylko referencja. Referencja która wskazuje na "coś". Taki link.

Rower ukauka = new Rower(20, 3.0);
Rower a = ukauka;
Pojazd b = ukauka;
Object c = ukauka;
....

Ważne: po takiej serii nie masz czterech obiektów. Masz JEDEN. Tylko jeden stworzyłeś operatorem new, i tylko on gdzieś siedzi. On o sobie wszystko wie - no przecież sam go zrobiłeś operatorkiem new. Więc wie, która metoda ma co wołać. Skąd wie? A jego broszka i jego problem, krzyż mu na drogę. Problemu z tym wielkiego na dobrą sprawę nie ma, bo przecież zrobiłeś new Rower - a w definicji klasy Rower ładnie opisałeś. Że potem porobiłeś jakieś a,b,c... - to go nie interesuje.

No dobra - no to co to są te a,b,c ? No referencje - linki tylko. Wszystkie prowadzą w jedno miejsce. Do tego jednego obiektu Rower, do tego samego, do którego ukauka prowadzi...

a.getSkok(); // zwróci 3.0
b.setSkok( b.getSkok()  + 2 ); // właśnie ustawiłeś na 5.0
ukauka.getSkok(); // zwróci 5.0

Bo cały czas to na jednym pracuje obiekcie tym samym.

No to pytanie, co ten interfejs w ogóle? No interfejs to w sumie nic nie robi technicznie, on tylko pilnuje, żebyś w zmiennej "b" trzymał referencję do obiektów, które mają ten getSkok() i tyle.

Zwróc uwagę na zmienną c. Jako Object zadeklarowane. Czyli co? Czyli wiemy, że c wskazuje na coś co jest jakiegoś typu - cholera wie jakiego... jedno jest pewne, że klasyczne metody obiektu obsługuje. Czyli w zasadzie nie możesz nic sensownego zrobić, najwyżej c.toString() albo badziewne c.toHashCode()... ale czy na pewno ? Przecież w tej konkretnej chwili to nasze "c" wskazuje na ten twój rower - cały czas ten jeden obiekt, który sam o sobie to przecież dobrze wie, jakiej jest klasy. I co? I okazuje się, że możesz spokojnie na nim zrobić coś takiego:

int wynik = (Integer)c.getClass().getMethod("getSkok").invoke(c); // i masz piękne 5.0

Dobra, meritum, bo na 10 sposobów mam nadzieję wyjaśniłem, że masz jeden rower i w cholerę zmiennych prowadzących do tego samego rowerka...

Najprościej więc odpowiadając na pytanie dosłownie:

  1. apple jest linkiem, i jako link wie dokąd prowadzi. Naprawdę tylko tyle wie. Wie, że prowadzi do czegoś w pamięci. Nic więcej. Jedna liczba. Adres.

  2. A to coś w pamięci wie, że jest rowerem - i tylko tyle mu potrzeba, żeby uruchomić co trzeba.

  3. cała akcja z deklaracjami, interfejsami i innymi cudami w javie służy tylko temu, żebyś nie mógł przez pomyłkę do zmiennej "b" przypisać czegoś złego. Znaczy masz gwarancję, że to "coś-wskazywane-przez-b" w swojej deklaracji zadeklarowało że obsługuje metody o nazwach getSkok i getCosTam...

  4. gwarancja ta wynika stąd, że (patrz pierwsza część punktu (3)) kompilator nie pozwoli na jawne przypisanie czegoś, co jest nieobsługiwane

  5. w C++ na przykład to faktycznie cuda się dzieją, i tam to jest x rodzajów wskaźników i cuda niemożliwe. W javie nie. Jeśli ktoś mówi, że w Javie interfejsy to coś więcej, że tam coś się dzieje magicznego... jest w błędzie. Ja jestem złośliwy - pisałem funkcje modyfikujące w locie pola "final", napisanie funkcji która robi przypisanie niezgodne z typem to również żaden problem. I dopiero w trakcie wywołania metody by się wszystko wypieprzyło błędem mniej lub bardziej spektakularnym.

0
Ranides napisał(a)

pisałem funkcje modyfikujące w locie pola "final", napisanie funkcji która robi przypisanie niezgodne z typem to również żaden problem. I dopiero w trakcie wywołania metody by się wszystko wypieprzyło błędem mniej lub bardziej spektakularnym.

Podziel sie prosze kodem.

1

Na final ? Złośliwe, ale trywialne:

public class ChangeFinal {

    public static final int CONSTANT = 12;

    public static void main(String[] args) throws Exception {
        System.out.println(CONSTANT);

        Field field = ChangeFinal.class.getDeclaredField("CONSTANT");
        field.setAccessible(true);

        Field mods = Field.class.getDeclaredField("modifiers");
        mods.setAccessible(true);

        mods.setInt(field, field.getModifiers() & ~Modifier.FINAL);
        field.setInt(null, 44);

        System.out.println(CONSTANT);
    }

}

Na "nielegalne" przypisanie? Nie powinno być problemu - ale gotowego w życiu nie pisałem, bo takiego hardcore'u nie potrzebowałem ;) Podmiana pól final'owych ma z drugiej strony czasem zastosowanie, więc dzieło powstało. Uproszczona wersja, pomijająca walkę z SecurityManagerami, pokazująca ideę po prostu. Jeśli jednak na tym "przypisaniu" Ci zależy, to daj znać, zawsze można by przysiąść i naklepać "potworka" ;)

0

Dzieki. Chodzilo mi wlasnie o ustawianie final - nie wiedzialem ze to jest mozliwe ;d

0

dzięki za wyjaśnienia, mam jeszcze jedno pytanie dotyczące interfejsów w Java. W jednej z książek na temat inżynierii oprogramowania przeczytałem że modułom klienta nie można odwoływać się do żadnych elementów modułu serwera, które znajdują się poza interfejsem.

Czy tak jest w języku Java? Przecież chyba można stworzyć klasę która odwołuje się do dodatkowej metody w innej klasie korzystającej z interfejsu w którym tej dodatkowej metody nie ma udokumentowanej. Więc to co przeczytałem przeczy temu co jest w języku Java czy czegoś nie rozumiem?

0

Pewnie chodziło o RMI, gdzie udostępnia się interfejsy.

1 użytkowników online, w tym zalogowanych: 0, gości: 1