[Newbe] Problem z polimorfizmem w javie.

0

Witam, mam rozterkę na temat polimorfizmu w javie i nie do końca rozumiem co się dzieje. Może ktoś będzie łaskaw odpowiedzieć na pytanie: dlaczego? Przejdźmy do rzeczy:

  1. Zakładam klasę figura z jedną metodą:
public class Figura {
     void malujNaZielono(){
         System.out.println("Maluje figure na zielono");
     }
}
  1. Zakładam 2 klasy Kolo i Kwadrat rozszerzające klasę Figura. Te klasy także posiadają jedną prostą metodę:
public class Kolo extends Figura {
    void liczPole(){
        System.out.println("Licze pole kola");
    }
}

i analogicznie

public class Kwadrat extends Figura {
    void liczPole(){
        System.out.println("Licze pole kwadratu");
    }
}
  1. Zakladam klase testowa i tworzę obiekty kolo i kwadrat które także są figurami.
public class TestClass {
    public static void main(String[] args) {
        Figura fig1 = new Kolo();
        Figura fig2 = new Kwadrat();        
    }
}

I teraz pytanie : dlaczego jeśli użyje takiego zapisu Figura fig1 = new Kolo(); będę miał dostęp do metody z klasy Kolo natomiast nie będę miał dostępu do metody z klasy Figura? (analogicznie przy Kwadracie). Przecież zarówno Kolo jak i Kwadrat są podtypami typu Figura? Rozumiem dlaczego tworząc takie obiekty: Kolo fig1 = new Kolo(); i Kwadrat fig2 = new Kwadrat(); będę miał dostęp do wszystkich metod, ale nie rozumiem dlaczego nie ma dostępu do metod z Figura skoro obiekty podtypu Kolo i Kwadrat pochodzą z klas dziedziczących po Figura (Kolo i Kwadrat są przecież figurami?) Może ktoś to wytłumaczyć łopatologicznie jak dla debila ? ehhhhh fuck :-D
screenshot-20171115164637.png

0

będę miał dostęp do metody z klasy Kolo natomiast nie będę miał dostępu do metody z klasy Figura

Bzdura. Zresztą twój screenshot to potwierdza. Widać jasno ze masz dostęp do metod z klasy Figura a nie masz dostępu do metod z klasy Kolo, co ma sens. Trzymasz "jakąś figurę" więc mozesz wykonywać operacje tylko wspólne dla figur.

0

Zmień IDE.

public class TestClass {
    public static void main(String[] args) {
        Figura fig1 = new Kolo();
        Figura fig2 = new Kwadrat();
        fig1.malujNaZielono();   // => Maluje figure na zielono 
   }
}
0

Jest odwrotnie niż napisałeś.
Tworząc to za pomocą

Figura fig1 = new Kolo();

Twoja zmienna fig1 jest traktowana jako obiekt typu Figura i masz tylko dostęp do metod tej klasy. Jeśli chcesz skorzystać z metod z klasy Kolo musisz użyć rzutowania:

if (fig1 instanceof Kolo) {
	((Kolo) fig1).liczPole();
}

Warto też za każdym razem sprawdzać, czy dany obiekt jest instancją danej klasy za pomocą instanceof, jak w przykładzie powyżej, bo inaczej ryzykujesz, że pojawi Ci się ClassCastException

0
Waleq napisał(a):

Jest odwrotnie niż napisałeś.
Tworząc to za pomocą

Figura fig1 = new Kolo();

Twoja zmienna fig1 jest traktowana jako obiekt typu Figura i masz tylko dostęp do metod tej klasy. Jeśli chcesz skorzystać z metod z klasy Kolo musisz użyć rzutowania:

if (fig1 instanceof Kolo) {
	((Kolo) fig1).liczPole();
}

Warto też za każdym razem sprawdzać, czy dany obiekt jest instancją danej klasy za pomocą instanceof, jak w przykładzie powyżej, bo inaczej ryzykujesz, że pojawi Ci się ClassCastException

Tak, dałem ciała bo właśnie chodziło mi o to że mam dostęp do metody z klasy figura a nie mam dostępu do metody z klasy Kolo pomimo że tworzę new Kolo()

0
Shalom napisał(a):

będę miał dostęp do metody z klasy Kolo natomiast nie będę miał dostępu do metody z klasy Figura

Bzdura. Zresztą twój screenshot to potwierdza. Widać jasno ze masz dostęp do metod z klasy Figura a nie masz dostępu do metod z klasy Kolo, co ma sens. Trzymasz "jakąś figurę" więc mozesz wykonywać operacje tylko wspólne dla figur.

Tak, dałem ciała bo właśnie chodziło mi o to że mam dostęp do metody z klasy figura a nie mam dostępu do metody z klasy Kolo pomimo że tworzę new Kolo(). No własnie, dlaczego nie mam? przecież skoro mam new Kolo() to już nie mówię o "jakiejś figurze" tylko specyficznie o kole (które jest "jakąś figurą")

1

Ale trzymasz obiekt przez referencje do Figury! Wyobraź sobie ze masz w kodzie tak:

public static void main(String[] args){
    Figura figura = new Kolo();
    Figura figura2 = new Kwadrat();
    funkcja(figura);
    funkcja(figura2);
}
public void funkcja(Figura f){
    // skad mamy wiedzieć jaka to figura? o_O Możesz wywołać tylko metody z klasy Figura!
}

Przecież wewnątrz funkcja co prawda masz jakaś konkretną figurę, ale nie wiadomo jaką. U ciebie w kodzie jest dokładnie tak samo -> nie wiadomo co to za figura bo trzymasz ją za pomocą referencji do typu bazowego. A jeszcze lepiej, co jak zrobimy:

Figura fig = randomBoolean() ? new Kolo() : new Kwadrat();

I teraz jaka to figura? Jak sie wylosowało true to koło a jak false to kwadrat. Skąd kompilator miałby a etapie kompilacji wiedzieć?

0

Możesz ryzykować, rzutować:

    Figura fig = r.nextBoolean() ? new Kolo() : new Kwadrat();    
    ((Kolo)fig).liczPole();

i oglądać albo napis Licze pole kola albo komunikat o błędzie rzutowania.

0
Shalom napisał(a):

będę miał dostęp do metody z klasy Kolo natomiast nie będę miał dostępu do metody z klasy Figura

Bzdura. Zresztą twój screenshot to potwierdza. Widać jasno ze masz dostęp do metod z klasy Figura a nie masz dostępu do metod z klasy Kolo, co ma sens. Trzymasz "jakąś figurę" więc mozesz wykonywać operacje tylko wspólne dla figur.

Witam, zrobiłem taki test:

class Figura {

    void liczPole(){ System.out.println("Licze pole figury"); }
}
class Kolo extends Figura {

    void liczPole(){ System.out.println("Licze pole kola"); }
}
class TestClass {
    public static void main (String [] dupa){

        Figura fig1 = new Kolo();
        fig1.liczPole();
    }
}

Po skompilowaniu i uruchomieniu:
screenshot-20171118154910.png
Dlaczego właśnie tak? Tłumacze sobie to w ten sposób: moj obiekt fig1 jest typu Figura i mam dostep tylko do metod z klasy Figura (zgodnie z Twoim wcześniejszym postem). Ale ponieważ klasa Kolo dziedziczy po Figura oraz w klasie Kolo mam metode o tej samej nazwie, to metoda z Kolo przesłania tą metode z Figura. Dlatego właśnie ta z Kolo jest wykonana. Tak to działa?
PS wie ktoś jak w intelliJ zrobić aby nie wyświetlały się w konsoli linie : Connected to... disconnected from.... Wcześniej tego nie było, z jakims updatem do IntelliJ uruchomiły się te dodatkowe linie w konsoli.

0

Dokładnie tak,przesłaniasz metodę z klasy, z której dziedziczysz.

0

Teraz ja czegoś nie rozumiem, skoro nie ma rzutowania (Kolo) to dlaczego wykonała się metoda z klasy Kolo? Przecież bez rzutowania nie powinno być dostępu do metod klasy Kolo, więc nie powinno być dostępu to przesłoniętej metody, tylko powinna być wykonywana metoda z klasy Figura. Wiem, że źle myślę bo widzę, co pokazuje kompilator ale czy może ktoś tym razem mi łopatologicznie wytłumaczyć dlaczego w tym przypadku metoda klasy Kolo działa?

0

Mylisz dwie kwestie - etap kompilacji i etap runtime.
Na etapie kompilacji, w trakcie statycznej analizy kodu, kompilator NIE WIE jaką Figurę trzymasz. Co zresztą pokazywałem w kodzie wyżej -> możesz przypisać tam jedną albo drugą figurę i kompilator nie może stwierdzić którą. W efekcie kompilator zachowawczo pozwala ci w kodzie umieścić jedynie odwołania do metod z klasy Figura, bo te na pewno są poprawne.
Na etapie działania programu wywoływany jest kod z "faktycznego" obiektu który masz. W efekcie obiekt Kolo wykona "swój" kod dla tejże metody, bo "nadpisał" sobie kod z klasy bazowej.

Jeśli interesują cię "technikalia" to np. C++ realizuje takie polimorficzne wywołania za pomocą tablicy wskaźników do funkcji. Wywołanie metody z danego obiektu to wyciągnięcie adresu funkcji z tablicy i wywołanie jej. Jeśli w klasie pochodnej nadpisujesz metodę to w tejże tablicy dla obiektów klasy pochodnej zmienia się adres funkcji na tą nową. Kompilator zamienia wywołania funkcji w kodzie na coś w stylu obiekt.tablica_funkcji[jakiś_numer].wykonaj(), w efekcie wykonuje się metoda z klasy pochodnej.

Co do komunikatów to czasem nie odpalasz tego w trybie debug? Bo tak wyglądają te komunikaty, jak podłączenie debugera.

0

Czyli rzutowanie w tym przypadku jest tylko po to, żeby kompilator pozwolił na używanie metod z klasy Koło ale na etapie działania programu nawet bez rzutowania program i sam zauważy, że Koło o typie Figura jest prawdziwym kołem i tak jakby zrzutuje sobie sam ten obiekt na typ Koło. Dobrze rozumiem czy znowu coś mieszam?

0

Rzutowanie działa na etapie KOMPILACJI i generalnie w 99% to znak że coś jest źle w kodzie skoro musisz rzutować albo robić instanceof ;) To jest takie powiedzenie kompilatrowi "wiem co robie!" ;]
Na etapie działania program nic nie "zauważy" ale automatycznie wykona funkcje z podanego obieku. Nie ma tam potrzeby żadnego "rzutowania" - obiekt po prostu ma dostęp do listy swoich funkcji i je wykonuje.

0

To jeszcze ostatnie pytanko ;) Skoro mówisz, że w 99% przypadków to oznacza, że coś w kodzie jest źle, to w przypadku tego akurat kodu wystarczy dodać metodę liczPole() w klasie Figura a następnie w klasach rozszerzających przesłonić te metody, tak? A to jeśli np. te bardziej szczegółowe klasy mają swoje unikalne metody, które chce aby miały tylko one. Przykładowo niech to będzie metoda liczbaKatowProstych() w klasie Kwadrat (oczywiście wiadomo, że to zawsze będzie 4, to tylko głupi przykład). Jak w takim przypadku poradzić sobie bez rzutowania w przypadku gdy chce wywołać tą metodę a obiekt utworzyłem tak : Figura kwadrat = new Kwadrat() ?

0

Wtedy nie ma sensu trzymać takiego obiektu jako "Figure". Bo jak byś tego chciał używać później w programie? Przecież skoro chcesz rzutować to znaczy że wiesz ze dany obiekt jest Kwadratem a nie Kołem, wiec czemu po prostu nie trzymasz go jako Kwadrat tylko jako Figure? To bez sensu. Jeśli piszesz jakiś specjalny kod operujący na tych kątach to logicznym jest że masz pod ręką tylko jakieś Wielokąty a nie Koło. Trzymanie obiektów pochodnych jako obiekty bazowe / interfejsy ma sens tylko jeśli w danym kodzie interesuje cię tylko ta "wspólna" funkcjonalność.

Np. twój program przechowuje konfiguracje w plikach .txt, .xml i .json, w praktyce w programie jest ci obojętne z jakim plikiem pracujesz, ot ty potrzebujesz czytać i pisać konfiguracje i tyle. Więc robisz interfejs ConfigurationFile i 3 implementacje. W samym programie w trakcie wczytywania konfiguracji sprawdzasz jaki masz plik i na tej podstawie tworzysz sobie ten XMLConfigurationFile albo TxtConfigurationFile albo jakiś inny, ale w całym programie operujesz tylko na interfejsie ConfigurationFile.

0

Ok, mniej więcej chyba rozumiem, po prostu mieszają mi się te wszystkie pojęcia. Za dużo czytania, za mało praktyki ;) dzięki wielkie za odpowiedzi.

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