#Niezbędnik Juniora. Konstruktory i klasa nadrzędna

By 12 października 2015Niezbędnik Juniora

Kon­struk­to­ry to bard­zo ważny ele­ment języ­ka Java — świadomie lub nie, korzys­tałaś z nich już od początku Two­jej przy­gody z pro­gramowaniem. Dzisi­aj zajmiemy się nimi trochę bliżej, kładąc nacisk na kon­tekst dziedz­iczenia.

Czym są konstruktory

Żeby lep­iej zrozu­mieć, o czym mówimy, wyjaśni­jmy najpierw czym są kon­struk­to­ry. Należą do spec­jal­nych ‘metod’, uruchami­anych w momen­cie tworzenia nowego obiek­tu, za pomocą słówka new. Od nor­mal­nych metod odróż­nia je to, że nic nie zwraca­ją i nie deklaru­ją typu zwracanego. Pon­ad­to ich nazwa musi być iden­ty­cz­na z nazwą klasy. Nie moż­na ich także wywołać po utworze­niu obiek­tu. Czym zatem nie są? Nie są meto­da­mi typu void, ponieważ rządzą się szczegól­ny­mi prawa­mi. Nie są też fab­ryka­mi — nie zwraca­ją obiek­tów.

Po tej może nie do koń­ca jas­nej definicji, spójrzmy na przykład­owy kod:

StringBuilder builder = new StringBuilder();

Ta lin­ij­ka tworzy nowy obiekt typu String­Builder, wywołu­jąc przy tym kon­struk­tor (bezar­gu­men­towy). Za każdym razem, kiedy tworzymy obiekt za pomocą new, wywoły­wany jest jeden z kon­struk­torów.

Do czego służą kon­struk­to­ry? Pozwala­ją wymusić podanie zestawu infor­ma­cji, których najczęś­ciej uży­wamy do uzu­pełnienia pól pry­wat­nych klasy. Cza­sa­mi służą do doko­na­nia prostych kon­wer­sji czy translacji obiek­tów, choć jest to wąt­pli­wą prak­tyką pro­gramowa­nia obiek­towego. Zde­cy­dowanym anty­w­zorcem jest umieszczanie w tym miejs­cu logi­ki biz­ne­sowej. Nigdy nie powin­na ona być wstaw­iana w kon­struk­torze obiek­tu.

Konstruktor domyslny

Pewnie zas­tanaw­iasz się w tym momen­cie nad fak­tem, że w więk­szoś­ci obiek­tów nie pisałaś kon­struk­torów! Otóż Java wykonu­je trochę pra­cy za Ciebie.  Jeśli nie zadeklaru­jesz żad­nego kon­struk­to­ra, to pro­gram ‘stworzy’ go za Ciebie. Będzie to bezar­gu­men­towy kon­struk­tor, który nic nie robi. Dzię­ki temu kod:

MojO­biekt obiekt = new MojO­biekt();

zadzi­ała jak tylko utworzysz klasę MojO­biekt.

Ale uwa­ga: zadeklarowanie dowol­nego kon­struk­to­ra (bezar­gu­men­towego lub nie) spowodu­je, że domyśl­ny kon­struk­tor nie zostanie utwor­zony.

Definiowanie konstruktorów

Kon­struk­to­ry defini­u­je­my podob­nie jak zwykłe metody — może­my określić dowolne argu­men­ty oraz dowol­ną logikę w ciele metody. Może­my także stworzyć kil­ka kon­struk­torów w jed­nej klasie:

public class KlasaZKonstruktorami {

private int jakasLiczba;

public KlasaZKonstruktorami() {
    this(10);
}

public KlasaZKonstruktorami(int jakasLiczba) {
    this.jakasLiczba = jakasLiczba;
}

}

Powyższy kod defini­u­je kon­struk­tor bezar­gu­men­towy oraz kon­struk­tor z jed­nym argu­mentem typu int. Pokazu­je także inny kon­cept — korzys­tanie z innych kon­struk­torów w tej samej klasie. Służy do tego słówko kluc­zowe ‘this’ użyte tak, jak­by samo było metodą.

Zwróć uwagę, że pomi­mo wyglą­du metody, syg­natu­ra, poza nazwą, nie zaw­iera typu zwracanego. To ostate­czny wyróżnik pomiędzy metodą, a kon­struk­torem.

Konstruktory prywatne

Byś może zas­tanaw­iasz się również, dlaczego przed kon­struk­torem staw­iamy klasy­fika­tor dostępu i czy może to być coś innego, niż pub­lic. Otóż jak najbardziej. Kon­struk­to­ry mogą być pro­tect­ed lub pri­vate. Rządzą nimi te same zasady, co w przy­pad­ku metod, jeśli chodzi o wywoły­wanie. Kon­struk­tor pry­wat­ny może być uży­ty w kon­strukcji new … tylko wewnątrz tej klasy. Doty­czy to także metod staty­cznych.

Bard­zo ważną kwest­ią jest to, że o ile kon­struk­to­ry mogą rzu­cać wyjąt­ki, nie jest to szczegól­nie czytelne. Kon­struk­to­ry pry­watne częs­to są uży­wane w połącze­niu ze wzorcem fac­to­ry method. Pozwala to na ‘kon­trolowanie’ tworzenia nowych obiek­tów i np. ponownego uży­wa­nia ist­nieją­cych (nie może­my tego zro­bić w kon­struk­torze, ponieważ jest on uruchami­any dla utwor­zonego, ale nie zainicjowanego obiek­tu).

Uwaga pułapka!

To może Wam się przy­trafić zarówno w prak­tyce, w kiep­sko zor­ga­ni­zowanym kodzie, jak i pod­czas rekru­tacji i testów. Zerkni­jmy na poniższą klasę (w przykładzie pominięte ciało metody i kon­struk­to­ra, ale do zobra­zowa­nia wystar­czą nam same syg­natu­ry):

public class Typ {
    public Typ Typ() {
        //to jest metoda
    }

    public Typ() {
        //to jest konstruktor
    }
}

Zestaw­ia­jąc obie obok siebie, łat­wo zauważyć różnice. W przy­pad­ku wyłącznie takiej deklaracji metody w kodzie oraz wys­tępowa­nia wielu innych ‘rozprasza­czy’, łat­wo ją przeoczyć.

Warto także nad­mienić, że z punk­tu widzenia Javy taka meto­da jest popraw­na (przy­na­jm­niej od strony kodu) Kod jak najbardziej się skom­pilu­je, a meto­da będzie dostęp­na. Nie jest to zale­cana prak­ty­ka z uwa­gi na czytel­ność kodu. Nieste­ty, cza­sem moż­na się natknąć na podob­ne ‘kwiat­ki’.

Konstruktory, a dziedziczenie

Jak wobec tego wyglą­da­ją kon­struk­to­ry, kiedy dziedz­iczymy po danej klasie? Otóż kon­struk­to­ry nie są dziedz­ic­zone. Ma to sens, biorąc pod uwagę, że ich założe­niem jest inicjować dany obiekt. Dziedz­icze­nie mogło­by prowadz­ić do niepełnej inic­jal­iza­cji obiek­tów oraz prob­lemów z imple­men­tacją. Stanowi to powód, dla którego nie wol­no umieszczać żad­nej logi­ki w kon­struk­torach! Teo­re­ty­cznie kod się skom­pilu­je, ale proszę, nie rób tego innym pro­gramis­tom.

Jeszcze nie wszys­tko powiedzieliśmy sobie w kwestii kon­struk­torów. Każdy kon­struk­tor musi w pier­wszej oper­acji wywołać kon­struk­tor klasy, po której dziedz­iczy. Java ponown­ie robi to za nas. Jeśli nie wywołamy kon­struk­to­ra klasy nadrzęd­nej jako pier­wszej oper­acji, Java domyśl­nie wywoła kon­struk­tor bezar­gu­men­towy za nas. Najczęś­ciej nie rodzi to prob­lemów. Jed­nak w przy­pad­ku, kiedy dziedz­iczymy po klasie deklaru­jącej własne kon­struk­to­ry, może ona nie mieć kon­struk­to­ra bezar­gu­men­towego. Wtedy nasza klasa się nie skom­pilu­je, o ile jawnie nie wywołamy jed­nego z kon­struk­torów klasy nadrzęd­nej.

super

Aby odwołać się do klasy po której dziedz­iczmy, może­my użyć słówka ‘super’. Dzi­ała ono ana­log­icznie jak this, pozwala odwoły­wać się do kon­struk­torów, metod oraz pól klasy, po której dziedz­iczymy. Dla przykładu poniższy kod:

public class Klasa {
    public Klasa() {
        //nic nie robimy
    }
}

Jest równoważny poniższe­mu:

public class Klasa {
    public Klasa() {
        super();
        //nic nie robimy
    }
}

Co ważne, metod nie doty­czy taka sama zasa­da, co kon­struk­torów. Metody dziedz­iczące nie muszą wywoły­wać tych, po których dziedz­iczą, nie musi to być również pier­wsza instrukc­ja. Weźmy np poniższy kod:

public KlasaBazowa {
    public int iloczyn(int a, int b) {
        return a*b;
    }
}

public KlasaZUlepszeniami extends KlasaBazowa {
    public int iloczyn(int a, int b) {
        if (a==0 || b==0) {return 0;}
        return super.iloczyn(a, b);
    }
}

Pomi­ja­jąc fakt, że nie jest to wzorowy przykład dobrze zor­ga­ni­zowanego kodu, jest on skład­niowo jak najbardziej poprawny.

Podsumowanie

Kon­struk­to­ry to bard­zo waż­na część języ­ka Java, choć mogłaś do tej pory nie korzys­tać z nich wprost. Niem­niej świado­mość, jak dzi­ała­ją i czym są jest jed­ną z pod­sta­wowych umiejęt­noś­ci, które powinien opanować każdy pro­gramista.

  •  
  •  
  •  
  • 4
  •  
  • Tom­cio

    Wita­j­cie! Pro­ponu­ję przyjżeć się jeszcze raz ostat­niemu przykład­owi. Nazwa klasy “KlasaBazowa” powin­na być bez naw­iasów (). To samo doty­czy KlasaZulep­szeni­a­mi. Taki kod jest niepoprawny i się nie kom­pilu­je. Do tego po nazwie drugiej klasy powin­no wys­tąpić jeszcze “extends KlasaBazowa”. Dzie­ki temu będzie moż­na wywołać metodę iloczyn, na klasie nadrzęd­nej :) Poz­draw­iam :)

    • Cześć, oczy­wiś­cie masz rac­je, już popraw­ione. Dzię­ki!

  • Dami­an

    Wita­j­cie!
    Myśle, że w akapicie doty­czą­cym pułap­ki mają Państ­wo błąd ponieważ meto­da:
    pub­lic Typ Typ() {
    //to jest meto­da!
    }
    musi zaw­ier­ać return state­ment co oznacza, że kod się nie skom­pilu­je.

    • Tak, jasne, nato­mi­ast z tek­stu wyni­ka, że pokazu­je­my w tym kodzie różnice pomiędzy syg­naturą (źle nazwanej) metody a kon­struk­to­ra — meto­da jak słusznie zauważyłeś, będzie miała typ zwracany, lub void jeśli nic nie zwraca, a kon­struk­tor nie.

      • Dami­an

        Hej,
        chodz­iło mi dokład­nie o Wasz komen­tarz do tej pułap­ki, w którym pisze­cie, że “kod jak najbardziej się skom­pilu­je” :)
        A dokład­nie:
        “Warto także nad­mienić, że z punk­tu widzenia Javy taka meto­da jest popraw­na (przy­na­jm­niej od strony kodu) Kod jak najbardziej się skom­pilu­je, a meto­da będzie dostęp­na. Nie jest to zale­cana prak­ty­ka z uwa­gi na czytel­ność kodu. Nieste­ty, cza­sem moż­na się natknąć na podob­ne ‚kwiat­ki’”

        Chy­ba, że odbier­am go w jak­iś zły sposób, w takim razie prosiłbym o wytłu­macze­nie :)

        • Dami­an

          Nieste­ty nie zauważyłem wcześniej Waszego zda­nia, że:
          (w przykładzie pominięte ciało metody i kon­struk­to­ra, ale do zobra­zowa­nia wystar­czą nam same syg­natu­ry).
          Więc “wątek” moż­na zakończyć :)