Dziedziczenie Java - problem poczatkujacego

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

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()};
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...

4

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

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

public abstract class Animal{
  public abstract String kick();
}
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
  }
}
public class Cat extends Animal{
   @Override
   public String kick(){
    return "Meow!!!"; 
  }
}
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:

Wof Wof
Meow!!!
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:

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":

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());
  }
}
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:

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

i implementacja:

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
  }
}

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