Dziedziczenie Java - problem poczatkujacego

Dziedziczenie Java - problem poczatkujacego
WR
  • Rejestracja:prawie 3 lata
  • Ostatnio:prawie 3 lata
  • Postów:1
0

Hej. To będzie mój pierwszy post na forum więc witam wszystkich i przejdę od razu do rzeczy.

Otóż uczę się od miesiąca Javy (chcę zostać testerem automatyzującym) i natrafiłam na problem przy dziedziczeniu. Otóż:
Załóżmy, że mamy klasę Animal i dziedziczacą po niej klasę Dog.
Jaka jest różnica pomiędzy deklaracją nowego obiektu
1.Animal reks = new Animal(); a
2.Animal fuks = new Dog(); ?

3.Dog max = new Dog(); w miarę ogarniam.

Jak tworzę takie klasy w IntelliJ, to przy tworzeniu obiektu w drugi sposób cała psowosc mojego Fuksa gdzieś znika i nie widzę różnicy w dostępie do metod i pól w przypadkach 1 i 2. Oba mają dostęp do pól i metod wyłącznie w obrębie klasy Animal. Dlaczego?
Czy zapis drugi został stworzony wyłącznie pod klasy abstrakcyjne (bo jakimś cudem jak zrobię abstract Animal, to deklaracja obiektu 2 jest prawidłowa, chociaż jak rozumiem typ Fuksa to będzie Animal, a przecież nie można tworzyć obiektu typu klasy abstrakcyjnej. Pewnie umknął mi jakiś niuans, ale przyznam że im więcej się nad tym zastanawiam i szukam, tym mniej rozumiem.
Wiem, że deklaracja 1 w wypadku abstrakcyjności Animal oczywiście odpada). Czy metoda 2 ma jeszcze jakieś inne zastosowanie (poza utworzeniem obiektu typu Animal którego normalnie nie możnaby było utworzyć przy klasie abstrakcyjnej?).
Bo w momencie kiedy wszystkie klasy są konkretne, to nie widzę ZADNEJ różnicy pomiędzy reksem a fuksem (a na pewno jakaś jest).

Dzięki z góry

_13th_Dragon
  • Rejestracja:prawie 20 lat
  • Ostatnio:11 dni
2

new Animal(); NIE powinno się skompilować, zaś osiąga się to poprzez public abstract class Animal ...
Jeżeli klasa Dog nie ma żadnej własnej metody (nie liczymy @Override oraz Konstruktora) to:
zapisy:

  • Animal fuks = new Dog();
  • Dog max = new Dog();
    niczym się nie różnią.
    Natomiast jeżeli ma własne metody to:
  • max.frisk();
  • ((Dog)fuks).frisk();
    Zawsze możesz sprawdzić if(fuks instanceof Dog)
    Rozważ Animal[] array=new Animal[] {new Dog(), new Cat(), new Cow()};

Wykonuję programy na zamówienie, pisać na Priv.
Asm/C/C++/Pascal/Delphi/Java/C#/PHP/JS oraz inne języki.
edytowany 2x, ostatnio: _13th_Dragon
WR
Ok. Dzięki. Trochę mi to wyjaśniło. Ale dalej nie rozumiem różnicy pomiędzy reksem, a fuksem. Fuks ma dostęp tylko do metod klasy Animal (tak samo jak reks). Co się stało z klasą Dog? Co zostało z psowosci fuksa?
_13th_Dragon
reks nie ma prawa zaistnieć ponieważ ty jako człowiek https://pl.wikipedia.org/wiki/Cz%C5%82owiek_(rodzaj) też należysz do Animal czyli Animal wieronika=new Human(); natomiast Animal x=Animal(); co to ma być? Czy istnieje coś po prosty zewrzecie? Jak mam odpowiedzieć na pytanie czym się różni wieronika od x? Może tym że wieronika istnieje (no przynajmniej jest wielka szansa że wciąż tak) zaś x na 100% jakiś abstrakcyjny wymyśl rodem z "since fiction".
WR
No jakbyś zrobił np grę, w której są oprócz zwierząt np. elementy terenu, które się nie poruszają w odróżnieniu od zwierząt, to deklaracja Animal x = new Animal już jak najbardziej wg mnie miałaby sens. A ja jako Human od Animal różnie się tym np, że mam metodę speak, której Animals nie mają. Czyli hierarchia byłaby taka: klasy: Alive i notAlive rozszerzają Obiekt, Animal rozszerzaj isAlive, Human rozszerza Animal itd. Ale to może przyklad był nietrafiony. Chodzi mi ogólnie o zasadę, o to, że mój program tak samo traktuje Animal x = new Animal, jak i Animal y = new Dog.
_13th_Dragon
Nie powinien, Animal musi być być abstract, dopiero jak coś konkretnego opisuje klasa to już przestaje być abstrakcyjną.
WR
Ok. Dzięki za pomoc.
AS
  • Rejestracja:ponad 6 lat
  • Ostatnio:ponad rok
  • Postów:21
5

Tak przeczytałem od góry do dołu i zabrakło mi takich określeń jak "polimorfizm" oraz "wirtualne wywołanie metod". Tak w szybkim skrócie, w Twoim przypadku oznacza to, że możesz zadeklarować sobie metody w "typie referencji" a zostaną one zawsze wywołane na "typie obiektu". Dzięki temu możesz tworzyć obiekty dziedziczące pewne stany lub/i zachowania poprzez klasy abstrakcyjne albo tworzyć wielokrotne kontrakty oparte tylko o zachowania poprzez interfejsy. Wielokrotne w sensie, że klasa może implementować wiele interfejsów. Przy pomocy tych dwóch podejść (klasy albo interfejsy) możesz tworzyć kolekcje i wywoływać na nich metody opisane na typie referencji, nie martwić się że każda może być inaczej zaimplementowana a i tak dla każdej instancji wykonają się prawidłowo. Możesz też przekazywać takie abstrakcyjne typy jako parametry w metodach, konstruktorach a w momencie wywołania przekazać odpowiednią implementację. To działanie można jeszcze dodatkowo zawęzić dzięki typom generycznym oraz PECS (Producer Extends, Consumer Super) Dodatkowe rzeczy które wynikają z dziedziczenia to - klasa dziedzicząca nie ma bezpośredniego dostępu do prywatnych pól klasy nadrzędnej (trzeba używać getterów i setterów) oraz nie są dziedziczone konstruktory. Czasami trzeba ręcznie w klasie nadrzędnej dodać konstruktor bezparametrowy. Tak w skrócie, bo pisane na kolanie na telefonie...

edytowany 3x, ostatnio: Ashapola
_13th_Dragon
Nie ma się do czego doczepić oprócz jednego - tekst nie dla początkującego.
WR
Dzięki Ashapola. Tylko dwa pytania: w jakim sensie konstruktory nie są dziedziczone? Jeśli stworzę klasę Dog która rozszerza Animal i tworzę obiekt Dog x = new Dog(); , to wedle mojej wiedzy najpierw zostaje wywolany konstruktor klasy Animal, a następnie klasy Dog (czyli Dog odziedziczył konstruktor po Animal. Chyba, że to się nie nazywa dziedziczenie). I mimo wszystko nie rozumiem, jakiego typu jest obiekt, który stworzę w ten sposób: Animal y = new Dog();. Czy on jest typu Dog, czy typu Animal? Skoro ma dostęp tylko do pól i metod Animal, to stawiam że jest jest typu Ani
WR
No i urwało mi tekst. No więc kończąc: chyba y jest typu Animal (i to jest fajne, bo mogę sobie potem wrzucić takiego y do listy innych obiektów typu Animal, nawet jak to będzie lista Cats czy Fishes to skoro wszystkie są typu Animal to się Java nie przyczepi, jeśli dobrze rozumiem). Ale... Co się po drodze dzieje z psowoscia mojego psa, skoro mój y nie ma już dostępu do metod psa? To na cholerę tam ten pies?
_13th_Dragon
Jeżeli klasa Animal nie ma konstruktora domyślnego Animal() czyli np. Animal(String name) to nie dasz rady stworzyć klasy Dog pochodzącą od Animal bez jawnego wywołania konstruktora Animal w tym przypadku Dog(String name) { super(name); } Więc konstruktor nie jest dziedziczony. Jeżeli ciebie interesuje wyłącznie psowość twego psa to po kiego ci dziedziczenie? Tak jak mówiłem wcześniej zawsze możesz użyć instanceof aby sprawdzić czy ten Animal to Dog, Cat czy też Cow
piotrpo
  • Rejestracja:ponad 7 lat
  • Ostatnio:16 dni
  • Postów:3277
4

Dobra, będzie kontrowersyjnie. Nie kopię zwierząt jak by co....

Klasa abstrakcyjna, określająca co można zrobić z sierściuchem

Kopiuj
public abstract class Animal{
  public abstract String kick();
}
Kopiuj
public class Dog extends Animal{
  @Override //wskazuje, że metoda z klasy nadrzędnej jest nadpisywana
  public String kick(){
    return "Wof Wof";//implementacja dla konkretnego zwierzaka
  }
}
Kopiuj
public class Cat extends Animal{
   @Override
   public String kick(){
    return "Meow!!!"; 
  }
}
Kopiuj
Animal animal1 = new Dog();
Animal animal2 = new Cat();

System.out.println(animal1.kick()); //tutaj wiemy tylko tyle, że animal1 to zwierzę, nie wiemy jakie konkretnie (pies, 
//kot, czy inny sebix) ale wiadomo jaką metodę można na nim wywołać
System.out.println(animal2.kick());

output:

Kopiuj
Wof Wof
Meow!!!
edytowany 1x, ostatnio: piotrpo
Zobacz pozostałe 6 komentarzy
_13th_Dragon
Ale zawsze można: public class Fish extends Animal{ @Override public String kick() { return ""; } }
piotrpo
Jest już późno, dzieci śpią, return null;
_13th_Dragon
System.out.println(new Fish().kick()); i okazuje się że gada?
piotrpo
Nie na darmo jest to "trillion dollar bug".
AS
@WeronikaR: zapoznaj się z definicją "Polimorfizm".... Książka "Myślenie obiektowe w programowaniu. Wydanie V" (najlepiej czytać w oryginale) rozwiąże 90% Twoich problemów.
AS
  • Rejestracja:ponad 6 lat
  • Ostatnio:ponad rok
  • Postów:21
1

Niby różnica pomiędzy klasami abstrakcyjnymi a interfejsami w najnowszej Javie (tak od 8) jest symboliczna jak to określił @piotrpo ale jest jednak znacząca. Klasa realizuje problem z "jest" a interfejs z "zachowuje się jak", np. "Ferrari" jest "pojazdem" - bo jest jest konkretną instancją (implementacją) klasy "samochód", która dziedziczy po ogólnej klasie abstrakcyjnej "pojazd". Natomiast "szybowiec" zachowuje się jak "ptak" bo lata ale zarówno szybowiec -> statek powietrzny -> pojazd ani sokół -> ptak drapieżny -> ptak -> zwięrzę nie mają wspólnego drzewa dziedziczenia ale można nimi zarządzać wspólnie poprzez wspólny interfejs z metodą void fly();

Dodatkowo w Javie nie ma wielodziedziczenia klas:

Kopiuj
public abstract class Glider{
  public void printInfo() {
      System.out.println("I am a glider");
  }
}

public abstract class Plane{
  public void printInfo() {
      System.out.println("I am a plane");
  }
}

// To nie zadziała, bo nie może:
public class MotorGlider extends Glider, Plane{
  @Override
  public void printInfo() {
      System.out.println("I am a motor glider");
  }
}

Natomiast ze względu na możliwość tworzenia od Javy 8 metod "default" mamy w Javie "problem diamentu":

Kopiuj
public interface Glider{
  default public void printInfo() {
      System.out.println("Glider");
  }
}

public interface Plane{
  default public void printInfo() {
      System.out.println("Plane");
  }
}

// To zadziała:
public class MotorGlider implements Glider, Plane{
  @Ovveride
  public void printInfo() {
      System.out.println("I am a motor glider");
      System.out.printf("I am a kind of like a %s and a kind of like a %s%n", Glider.super.printInfo(), Plane.super.printInfo());
  }
}
edytowany 4x, ostatnio: Ashapola
piotrpo
  • Rejestracja:ponad 7 lat
  • Ostatnio:16 dni
  • Postów:3277
0

@Ashapola: Oczywiście masz rację, zwyczajnie to co napisała OP, skłoniło mnie do nie wnikania na tym etapie w te rozważania. Wydaje mi się, że sporą przeszkodą w ogarnięciu o co biega w programowaniu obiektowym, jest próba tłumaczenia początkującym jednych nie zrozumiałych pojęć, przy pomocy innych niezrozumiałych pojęć.

@WeronikaR: Wydaje mi się, że bardzo wiele wyjaśniają słowa kluczowe extends i implements. W każdym razie mi pozwoliły zrozumieć o co chodzi z tymi całymi dziedziczeniami, polimorfizmami itd.
Interface definiuje jakiś zbiór akcji, które można wywoływać na implementujących go klasach. Żeby zostawić już w spokoju te zwierzaki, masz w Javie interface collection. Jeżeli jakiś obiekt go implementuje, to można na nim wywołać każdą z metod i spodziewać się, że zwróci c.a. identyczny rezultat, bez zastanawiania się jak to zrobi. Masz pod spodem zbiory, listy (również interface'y 'Set', 'List'), które dokładają po parę metod w zależności od tego, czy porządek jest istotny i czy chcemy mieć możliwość przechowywania duplikatów. Oba interface'y mają ileś tam różnych implementacji, w zależności od tego, czy akurat zależy nam na wydajności podczas wrzucania czegoś do listy na jej końcu, czy wolimy użyć mniej pamięci itp.

Teraz o klasie abstrakcyjnej.
Słówko kluczowe abstract może występować przy nazwie klasy i przy metodzie.
Jeżeli jest ono przy nazwie klasy, to dzieją się 2 rzeczy:

  • Nie da się utworzyć instancji takiej klasy, bez względu na to co jest w reszcie kodu.

  • Da się napisać abstract przy nazwie metody.

    Jeżeli abstract jest napisany przy metodzie, to metoda nie może być zaimplementowana w rodzicu, ale musi być zaimplementowana w klasach rozszerzających (chyba, że też będą abstrakcyjne).

    I teraz odpowiedź na pytanie czym właściwie różni się klasa abstrakcyjna od interface'u (to co wyżej masz napisane to oczywiście prawda).
    Interface z natury może mieć tylko metody publiczne.
    Natomiast klasa abstrakcyjna, znowu wracam do zwierzaków, może mieć np. tak:

Kopiuj
public abstract class Animal{
protected int attitude = 0;
  public final String kick(){
    makeNoise();
    changeAttitude()
  }
  public abstract String makeNoise();
  public abstract void changeAttitude();
}

i implementacja:

Kopiuj
public class Dog extends Animal{
  @Override
  public String makeNoise(){
    return "Wof Wof";
  }

  @Override
  public void changeAttitude(){
    attitude -=100; //odwołanie do chronionego pola klasy nadrzędnej
  }
}

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.