PrzeciążanieCzęść NR.19

Witaj w przedostatnim  wpisie z tego cyklu. Poza wielkim podsumowaniem czego w Javie nie ma, a jest w C# wypadałoby jeszcze coś powiedzieć o przeciążaniu metod w C# i w Javie, ponieważ różnice są.

Zacznijmy od C#.

Elementy klasy pochodnej mogą redefiniować metodę klasy bazowej. W klasie pochodnej tworzymy metodę o tej samej nazwie i liście parametrów, co metoda w klasie pochodnej.

Metoda ta będzie miała nowe zachowanie. Inne niż metoda z klasy bazowej.

class Rectangle
{
    public int x = 1, y = 10;
    public int GetArea() { return x * y; }
}
class Square : Rectangle
{
    public int GetArea() { return 2 * x; }
}

Istnieje mała, ale poważna różnica pomiędzy ich ukrywaniem , a ich przeciążaniem. Obecnie nadpisaliśmy metodę w C#.

Kompilator nawet podpowie, że zapewne chodziło nam o ukrycie metody.

image

By to zaznaczyć w C# korzystam ze słowa kluczowego new.

class Rectangle
{
    public int x = 1, y = 10;
    public int GetArea() { return x * y; }
}

class Square : Rectangle
{
    public new int  GetArea() { return 2 * x; }
}

Statyczne metod zachowują się podobnie.

class Rectangle
{
    public int x = 1, y = 10;
    public static int GetArea(int x,int y) 
    { return x * y; }
}
class Square : Rectangle
{
    public static new int  GetArea(int x, int y) 
    { return x * y; }
}

 

A co jeśli chcemy przeciążać metodę. Najpierw metoda w klasie bazowej musi być oznaczona jako wirtualna przy pomocy słowa kluczowego virtual

image

Statyczne metody nie mogą być wirtualne.

class Rectangle
{
    public int x = 1, y = 10;
    public virtual int GetArea() { return x * y; }
}

W klasie pochodnej przeciążenie jest określone słowem override.

class Square : Rectangle
{
    public override int GetArea() { return 2 * x; }
}

Jaka jest w końcu ta różnica pomiędzy przeciążeniem a nadpisaniem metody poza słowami kluczowymi?

Różnice widać, gdy definicja kwadratu jest umieszczona w klasie bazowej określającej prostokąty. Jeżeli metoda jest nadpisana ze słowem kluczowym new, wtedy wykona się metoda z Prostokątów. Nowa definicja metody z kwadratu będzie ukryta w takim przypadku.

class Program
{
    public static void Main(string[] args)
    {
        Rectangle s1 = new Square();
    
        Square  s2 = new Square();
    
        Console.WriteLine(s1.GetArea());
        Console.WriteLine(s2.GetArea());
    
    }
}

image

Jeżeli jednak przeciążyliśmy metodę wirtualną, wtedy w zmiennej Prostokąta przechowującej kwadrat wykona się metoda z kwadratu.

Bazowo kod zawsze wykona się tak samo bez względu na to, jaka referencja obiektu jest przetrzymywana.

image

W Javie wszystkie metody w klasie są wirtualne

W Javie wszystkie metody są wirtualne oznacza to, że nie trzeba ich definiować jako takich.

Domyślnie metody niestatyczne w klasie są więc przeciążane.

class Rectangle
{
    public int w = 10, h = 10;
    public int getArea() { return w * h; }
}
class Triangle extends Rectangle
{
    public int getArea() { return w * h / 2; }
}

Tworzy to jednak pewien problem. Od Javy 5 została dodana specjalna adnotacja, która upewnia kompilator, że jest to zachowanie, które chcemy mieć.

Tak chcę przeciążyć tę metodę.

class Triangle extends Rectangle
{
    @Override public int getArea()
    {
        return w * h / 2;
    }
}

Bez względu na to, czy dodasz adnotację kod przeciąży metody i zachowa się tak samo. Wykona się kod trójkąta.

public static void main(String[] args)
{
    Rectangle r = new Triangle();
    Triangle t = new Triangle();
    
    System.out.print(r.newArea());
    System.out.print(t.newArea());
}

Te twierdzenia w Javie są prawidłowe dla metod w instancji klasy, a co z metodami statycznymi.

A co z metodami statycznymi w Javie

Ukrywanie metod w Javie wystąpi w przypadku metod statycznych.

W końcu metody statyczne nie mogą być wirtualne.

W Javie jednak nie musimy używać słowa kluczowego new by określać, że tak o takie zachowanie nam chodziło.

class Rectangle
{
    public int w = 10, h = 10;
    public static int newArea(int a, int b) {
        return a * b;
    }
}

class Triangle extends Rectangle
{
        public static int newArea(int a, int b) {
        return a * b / 2;
    }
}

Adnotacja Override nie może zostać użyta, ponieważ metoda statyczna nie może być przeciążana i adnotacja tego nie zmieni.

image

Podsumowując w Javie metody instancji klasy zawszę będą przeciążane. Nie można  zmienić tego zachowania  jak w C#.

Metody statyczne klasy zawszę będą ukrywane i  w C# i w Javie.

Jak przeciw działać nadpisywaniu i ukrywaniu

W Javie, by zapobiec nadpisywaniu metody korzystam ze słowa kluczowego final w klasie bazowej.

public final int getArea() { return w * h; }

W C# mamy do tego celu słowo kluczowe sealed.

class MyClass
{
    public sealed override int NonOverridable() {}
}

Uzyskanie dostępu do bazowych definicji metod

Aby uzyskać do działania metody z klasy bazowej w Javie używamy słowa kluczowego super.

@Override public int getArea()
{
    return super.getArea() / 2;
}

W C# w tym celu używamy słowa kluczowego base.

class Triangle : Rectangle
{
    public override GetArea() { return base.GetArea()/2; }
}

Jeśli metoda bazowa przyjmuje parametry to podajemy je w nawiasach.

public Triangle(int a, int b) { super(a,b); }

W podobny sposób możemy skorzystać z bazowego konstruktora wewnątrz konstruktora klasy pochodnej.

public Triangle() { super(); }

Wywołanie konstruktora bazowego musi być zawsze wywołane jako pierwsze.

image

W C# zachowanie jest podobne. Jedyna różnica polega na słowie kluczowym base oraz na fakcie, że wywołanie kodu bazowego konstruktora odbywa się w sygnaturze konstruktora .

class Rectangle
{
    public int x = 1, y = 10;
    public Rectangle(int a, int b) { x = a; y = b; }
}
class Square : Rectangle
{
    public Square(int a) : base(a,a) {}
}

Kompilator C# i Javy automatycznie dodaje wywołanie bazowego konstruktora, jeśli jest on bezparametrowy.

class Square : Rectangle
{
    public Square(int a) {} // : base() implicitly added
}

Zapewnia nas to, że elementy klasy bazowej zostaną utworzone poprawnie dla klasy pochodnej.