Przeciążanie

Przeciążanie metod polega na ponownym użyciu nazwy metody w klasie, ale z innymi argumentami (opcjonalnie i innym typem zwracanym) .

Zasady są proste:

 

  • Metoda przeciążona musi zmienić listę argumentów.
  • Metoda przeciążona może zmienić typ zwracany.
  • Metoda przeciążona może zmienić modyfikator dostępu.
  • Metoda  przeciążona może zadeklarować nowy albo szerszy sprawdzony wyjątek.
  • Metoda może być przeciążona w tej samej klasie bądź podklasie. Czyli jeśli klasa X definiuje metodę print(int a) to znaczy ,że podklasa Y  mogła być, zdefiniować print (string a) bez nadpisywania metody print. Dwie metody o tej samej nazwie, ale w innych klasach mogą być uznane za przeciążone, jeżeli podklasa dziedziczy jedną wersje tej metody ,a później deklaruje inną przeciążoną wersję.

Untitled5

W poprzednim wpisie opisałem metody nadpisane i zwróciłem uwagę na pewną różnicę pomiędzy przeciążaniem metody ,a jej nadpisaniem. Zakładając ,że egzamin lubi stosować chwyty poniżej pasa wyobraź sobie takie pytanie związane z poprawnością kodu.

public class PrzykladowaKlasa 
{
    public void print(int a,String b){}
    public void diffrentStuff(){}
}

class InnaKlasa extends PrzykladowaKlasa
{
    public void print(int a,double b) throws IOException{}
}

Faktycznie tutaj są łamane zasady nadpisywania metody ,ale z drugiej strony jeśli chodziło nam o przeciążone metody print kod jest OK.

image

Warto też zwrócić uwagę na to ,że wyjątek IOException stworzyłby problem, gdyby to było nadpisanie metody print() (ilustracja powyżej), ponieważ metoda ta nie deklaruje wyjątków. Nie jest to jednak nadpisywanie metod (patrz na kod powyżej) podklasa InnaKlasa ją przeciąża . A zamiana listy argumentów jest OK w przeciążaniu metody.

Poprawne przeciążenia

Oto przykład metody, która  ma być przeciążona.

public void ChangeWidth(int size,String name,float pattern){}

Następujące przeciążenia są poprawne.

public void ChangeWidth(int size,String name){}
public int ChangeWidth(int size,float pattern){return 1;}
public void ChangeWidth(String name,float pattern)throws IOException{}

Nie jest to trudne trzeba tylko trzymać się zasad które napisałem na początku wpisu.

Wywoływanie przeciążonych metod

Ten proces jest bardziej skomplikowany niż się wydaje. Pakowanie argumentów ma wielki udział  w działaniu przeciążonych metod, więc nie jest to miejsce na objaśnienie tego mechanizmu. Jak kompilator wie, którą wersję metody ma użyć? No cóż o to akurat nie musisz się martwić na egzaminie.

Kiedy metoda jest wywołana jedna bądź więcej metod o tej samej nazwie  może istnieć wewnątrz obiektu, w którym wywołanie następuje. Na przykład klasa Tygrys może mieć trzy metody o tej samej nazwie ,ale o innej liście argumentów,  co oznacza ,że metoda jest przeciążona.

Decyzja kompilatora na temat użycia danej wersji metody jest zależna od zadeklarowanych argumentów w metodzie. Czyli jeśli wywołałeś metodę z parametrem int to znaczy ,że wywołujesz wersje metody, która potrzebuje tylko ten parametr. Jeżeli wywołasz tę samą metodę ,ale  z parametrem string, wtedy uruchomi się przeciążona wersja metody, która pobiera string.

Oczywiście, jeśli umieścisz w parametrach obiekt Tygrys ,a metoda ta nie ma takiej wersji przeciążonej wtedy kompilator zgłosi błąd o  nie dopasowaniu ilości bądź typu argumentów  w metodzie.

Oto przykład użycia metody przeciążonej.

class Matematyka
{
    public int Dodawanie(int x,int y){
        return x + y;
    }
    public double Dodawanie(double x,double y){
        return x + y;
    }
}

public class PrzeciazenieTest 
{
    public static void main(String[] args) 
    {
        Matematyka matematyka = new Matematyka();
        int i1 = 41;
        int i2 = 121;
        double d1 = 3.14;
        double d2 = 0.000000001;
        
        int rez = matematyka.Dodawanie(i1, i2);
        double rez2 = matematyka.Dodawanie(d1, d2);
        System.out.println(rez);
        System.out.println(rez2);
    }
}

W kodzie powyżej zostaje dwa razy wywołana  metoda przeciążona. Raz wywołana jest wersja, w której dodawane są liczby całkowite int ,a za drugim razem wywołana jest wersja, która dodaje liczby double.

A jak działa przeciążanie metod z typami referencyjnymi, które dziedziczą po sobie,

Powiedzmy ,że jedna metoda przeciążona pobiera Vehicle ,a w innej Car (podklasa Vehicle). Jeżeli umieścisz obiekt Car do metody, to wtedy wywołasz wersje metody z obiektem Car. Sprawdźmy.

class Vehicle{}
class Car extends Vehicle{}

public class UseCarsVehicle 
{
    
    void drive(Vehicle v)
    {
        System.out.println("Vehicle verison dude!");
    }
    
    void drive(Car c)
    {
        System.out.println("Car version man. Hight five!");
    }
    
    public static void main(String[] args) 
    {
        UseCarsVehicle ucv = new UseCarsVehicle();
        Vehicle vehicleObj = new Vehicle();
        Car carObj = new Car();
        
        ucv.drive(vehicleObj);
        ucv.drive(carObj);
    }
}

Wynik działania kodu:

Vehicle verison dude!
Car version man. Hight five!

Wszystko jest w porządku ,ale co się stanie, jeśli umieszczę obiekt Car do referencji Vehicle.


Vehicle vehicleRefToCar = new Car();
ucv.drive(vehicleRefToCar);

Która wersja zostanie wywołana? Możesz powiedzieć “Ta, która bierze samochód Car, ponieważ to on jest przechowywany w czasie programu i to właśnie on jest przesłany do metody”.

Program wydrukuje jednak coś innego w czasie działania.

Vehicle verison dude!

Pomimo iż aktualny obiekt w czasie działania programu to Car ,a nie Vehicle , to wybór w czasie wywołania padł na wersje z Vehicle. Dzieje się tak, ponieważ mechanizm ten nie działa dynamicznie w czasie działania programu,i to referencja ,a nie obiekt decyduje o wywołaniu przeciążonej metody,

Powrót polimorfizmu przeciążanie metod i ich nadpisywanie

Jak polimorfizm działa z przeciążonymi metodami.  Tak jak udowodniłem powyżej mechanizm polimorfizmu nie występu przy przeciążaniu metod. Wydaje się to proste ,ale co się stanie, jeśli metoda będzie i przeciążona i nadpisana równocześnie.

package carTest;

class Vehicle 
{
       public void enginesOn() 
       {
          System.out.println("Vehicle Engines are on");
       }
}

class Car extends Vehicle 
{
       public void enginesOn() {
           System.out.println("Car Engines on");
       }
       public void enginesOn(String s) {
          System.out.println("Car Engines on " + s);
       }
}

public class TestCar 
{
    public static void main(String[] args) 
    {
        Vehicle v = new Vehicle();
        v.enginesOn();
        
        Car c = new Car();
        c.enginesOn();
        
        Vehicle v1 = new Car();
        v1.enginesOn();
        
        Car c2 = new Car();
        c2.enginesOn("That is awesome!");
    }
}

Zauważ ,że klasa Car przeciąża i nadpisuje metodę “enginesOn”. Tabelka poniżej pokazuje która z tych 3 metod uruchomi się i kiedy.

Kod wywołania metodyRezultat
Vehicle v = new Vehicle();
v.enginesOn();
Vehicle Engines are on
Car c = new Car();
c.enginesOn();
Car Engines on
Vehicle v2 = new Car();
v2.enginesOn();
Car Engines on
Polimorfizm działa dla aktualnego obiektu Car nie dla referencji Vehicle .
Car c2 = new Car();
c2.enginesOn("That is awesome!");
Car Engines on That is awesome!
Przeciążona wersja enginesOn(String s) jest wywołana.
Vehicle v3 = new Vehicle();
v3.enginesOn("This works?");
Błąd kompilacji . Kompilator stwierdza ,że klasa Vehicle nie ma metody eginesOn() ,która przyjmuje napis string.
Vehicle v4 = new Car();
v4.enginesOn("Test this man.");
Błąd kompilacji . Kompilator stwierdza ,że klasa Vehicle nie ma metody eginesOn() ,która przyjmuje napis string.

Kompilatora nie obchodzi fakt ,że wewnątrz referencji Vehicle może znajdować się obiekt Car.


Trzeba też pamiętać.

Untitled5

Metoda wciąż  jest przeciążona, pomimo iż nie  jest nadpisana w klasie pochodnej. Mam na myśli taki kod.

public class Example 
{
    void doSomething(){};
}

class Another
{
    void doSomething(int a){}
}

Klasa Another ma dwie metody doSomething() – metoda bez argumentów jest dziedziczona od klasy Example i ją przeciąża. Kod z referencją do obiektu klasy Another może wywołać metodę doSomething() z argumentem int i bez.

Różnice pomiędzy przeciążaniem metody ,a jej nadpisaniem

CoPrzeciążenie metodyNadpisanie metody
ArgumentMuszą się zmienićNie mogą się zmienić
Typ zwracanyMoże się zmienićNie może ulec zmianie, chyba że zwraca pochodny typ. (kowariancja)
WyjątkiMożna zmienić.Można zredukować bądź wyeliminować wyjątek. Nie można wyrzucać nowych wyjątków czy ich szerszych sprawdzonych wersji.
DostępnośćMożna zmienićNie można zmienić.
WywołanieTyp referencyjny determinuje o wersji metody przeciążonej, jak i liczba argumentów. Wywołana metoda wciąż jest wirtualną metodą w czasie działania programu ,ale kompilator zna już sygnatury metod, które będą wywoływane więc w czasie działania programu decyzja o ustaleniu wersji metod została już podjęta.Typ obiektu, czyli prawdziwa instancja na stercie determinuje, która metoda zostanie wywołana. Ten mechanizm działa dynamicznie w czasie wykonywania programu.


Na koniec prosta ilustracja podsumowująca nadpisywanie i przeciążanie metod.

public class-18

Co dalej:

Rzutowanie zmiennych referencyjnych.