JAVA PAGRINDAI DETALIAU

Java metodų perrašymas

Metodo perrašymas (angl. method overriding) vyksta tada, kai poklasis pateikia savo iš tėvinės klasės paveldėto metodo įgyvendinimą. Metodo paskirtis išlieka, tačiau konkretaus objekto elgesys gali skirtis.

Pirmasis perrašyto metodo pavyzdys

class Pranesimas {
    void siusti() {
        System.out.println("Siunčiamas bendras pranešimas");
    }
}

class ElPastas extends Pranesimas {
    @Override
    void siusti() {
        System.out.println("Siunčiamas el. laiškas");
    }
}

ElPastas laiskas = new ElPastas();
laiskas.siusti();
Konsolės rezultatas

Siunčiamas el. laiškas

  1. Pranesimas aprašo bendrą metodą siusti().
  2. ElPastas extends Pranesimas reiškia, kad ElPastas paveldi šį metodą.
  3. Poklasis parašo tokį patį metodo parašą ir pateikia jam konkretesnį veiksmą.
  4. Kadangi objektas yra ElPastas, vykdomas jo perrašytas metodas.

Kodėl verta rašyti @Override?

Anotacija @Override aiškiai pasako kompiliatoriui: „šiuo metodu ketinu perrašyti paveldėtą metodą“. Ji nėra vien papuošimas – kompiliatorius patikrina, ar tėvinėje klasėje tikrai yra tinkamas metodas.

Praktinė nauda

Jei netyčia parašysi siust() vietoje siusti() arba pakeisi parametrus, gausi kompiliavimo klaidą, o ne tyliai sukursi visai kitą metodą.

Java žiūri į tikrąjį objektą

class Pranesimas {
    void siusti() {
        System.out.println("Bendras pranešimas");
    }
}

class ElPastas extends Pranesimas {
    @Override
    void siusti() {
        System.out.println("El. laiškas");
    }
}

class Sms extends Pranesimas {
    @Override
    void siusti() {
        System.out.println("SMS žinutė");
    }
}

Pranesimas pirmas = new ElPastas();
Pranesimas antras = new Sms();

pirmas.siusti();
antras.siusti();
Konsolės rezultatas

El. laiškas
SMS žinutė

  1. Abiejų kintamųjų tipas yra bendras Pranesimas.
  2. Pirmasis kintamasis iš tikrųjų rodo į ElPastas objektą, antrasis – į Sms.
  3. Vykdymo metu Java pasirenka metodą pagal tikrąjį objekto tipą.
  4. Tai vadinama dinaminiu metodo parinkimu arba vykdymo laiko polimorfizmu.

Perrašomo metodo parašas turi sutapti

class Dokumentas {
    void atidaryti() {
        System.out.println("Dokumentas atidarytas");
    }
}

class TekstinisDokumentas extends Dokumentas {
    @Override
    void atidaryti(String programa) { // Klaida
        System.out.println("Atidaryta su " + programa);
    }
}
Rezultatas

Kodas nesukompiliuojamas dėl @Override: tėvinė klasė neturi metodo atidaryti(String).

Norint perrašyti metodą, turi sutapti jo pavadinimas ir parametrų sąrašas. Pakeitus parametrus sukuriamas perkrautas, o ne perrašytas metodas.

Tėvinį metodą galima papildyti su super

class Kvitas {
    void spausdinti() {
        System.out.println("Parduotuvė: Miesto krautuvė");
    }
}

class PirkimoKvitas extends Kvitas {
    @Override
    void spausdinti() {
        super.spausdinti();
        System.out.println("Suma: 18.50 Eur");
    }
}

PirkimoKvitas kvitas = new PirkimoKvitas();
kvitas.spausdinti();
Konsolės rezultatas

Parduotuvė: Miesto krautuvė
Suma: 18.50 Eur

super.spausdinti() pirmiausia iškviečia tėvinės klasės variantą. Tada poklasis prideda savo informaciją. Taip nereikia kopijuoti bendros kvito antraštės.

Prieigos negalima susiaurinti

class Paskyra {
    public void rodyti() {
        System.out.println("Paskyra");
    }
}

class DarboPaskyra extends Paskyra {
    @Override
    private void rodyti() { // Klaida: siauresnė prieiga
        System.out.println("Darbo paskyra");
    }
}
Rezultatas

Kodas nesukompiliuojamas, nes public metodas poklasyje bandomas pakeisti į private.

Perrašytas metodas gali palikti tokią pačią arba suteikti platesnę prieigą, tačiau negali pažeisti pažado, kad tėvinės klasės metodas buvo viešai pasiekiamas.

Kurių metodų perrašyti negalima?

Specialūs metodų atvejai
MetodasKas nutinka?
finalPerrašyti draudžiama, nes tėvinė klasė užfiksavo galutinį elgesį.
privatePoklasis metodo nemato, todėl toks pat jo metodas būtų naujas, o ne perrašytas.
staticNeperrašomas. Toks pats poklasio metodas paslepia tėvinės klasės statinį metodą.
Egzemplioriaus metodasGali būti perrašomas, jei kitos taisyklės tai leidžia.
class Saskaita {
    final void rodytiNumeri() {
        System.out.println("LT123");
    }
}

class VersloSaskaita extends Saskaita {
    // Klaida: final metodo perrašyti negalima
    void rodytiNumeri() { }
}
Rezultatas

Kodas nesukompiliuojamas, nes rodytiNumeri() tėvinėje klasėje pažymėtas final.

static metodo paslėpimas nėra perrašymas

class Tevas {
    static void rodytiTipa() {
        System.out.println("Tėvo statinis metodas");
    }

    void rodytiBusena() {
        System.out.println("Tėvo egzemplioriaus metodas");
    }
}

class Vaikas extends Tevas {
    static void rodytiTipa() {
        System.out.println("Vaiko statinis metodas");
    }

    @Override
    void rodytiBusena() {
        System.out.println("Vaiko egzemplioriaus metodas");
    }
}

Tevas objektas = new Vaikas();

objektas.rodytiTipa(); // Leidžiama, bet nerekomenduojama
Tevas.rodytiTipa();    // Rekomenduojamas tiesioginis iškvietimas
Vaikas.rodytiTipa();   // Kviečiamas Vaikas klasės statinis metodas

objektas.rodytiBusena();
Konsolės rezultatas

Tėvo statinis metodas
Tėvo statinis metodas
Vaiko statinis metodas
Vaiko egzemplioriaus metodas

  1. static metodas priklauso klasei, todėl aiškiausia jį kviesti tiesiogiai per klasės pavadinimą: Tevas.rodytiTipa() arba Vaikas.rodytiTipa().
  2. Java taip pat leidžia parašyti objektas.rodytiTipa(), nors objektas yra nuorodos kintamasis. Toks užrašymas veikia, bet yra klaidinantis ir nerekomenduojamas.
  3. Kviečiant statinį metodą per nuorodą Java nežiūri į tikrąjį objektą. Ji žiūri į deklaruotą nuorodos tipą Tevas, todėl vykdo Tevas.rodytiTipa().
  4. Vaikas.rodytiTipa() aiškiai pasirenka Vaikas klasėje aprašytą statinį metodą.
  5. rodytiBusena() yra egzemplioriaus metodas, todėl Java žiūri į tikrąjį objektą Vaikas ir vykdo perrašytą jo variantą.
Gera taisyklė

Statinį metodą kviesk per klasę, kuriai jis priklauso. Taip iš kodo iš karto matyti, kad metodui nereikia konkretaus objekto.

Perrašytas metodas gali grąžinti poklasio tipą

class Dokumentas {
    Dokumentas kopijuoti() {
        return new Dokumentas();
    }
}

class Saskaita extends Dokumentas {
    @Override
    Saskaita kopijuoti() {
        return new Saskaita();
    }
}

Saskaita originalas = new Saskaita();
Saskaita kopija = originalas.kopijuoti();

System.out.println(kopija.getClass().getSimpleName());
Konsolės rezultatas

Saskaita

Tėvinis metodas grąžina Dokumentas, o perrašytas metodas – konkretesnį jo poklasį Saskaita. Tai vadinama kovariantiniu grąžinamuoju tipu ir Java tai leidžia.

Patikrintų išimčių taisyklė

Perrašytas metodas negali paskelbti naujos arba platesnės patikrintos (checked) išimties nei tėvinis metodas. Jis gali paskelbti tokią pačią, siauresnę arba išvis jos nepaskelbti. Neprivalomoms RuntimeException tipo išimtims ši riba netaikoma.

Kodėl?

Kodas, dirbantis su tėvinės klasės tipu, jau žino, kokias patikrintas išimtis privalo apdoroti. Poklasis negali netikėtai pareikalauti apdoroti platesnio klaidų rinkinio.

Perkrovimas ir perrašymas nėra tas pats

Metodų perkrovimo ir perrašymo skirtumai
SavybėPerkrovimasPerrašymas
Angliškaioverloadingoverriding
Kur vyksta?Dažniausiai toje pačioje klasėjeTarp tėvinės klasės ir poklasio
ParametraiPrivalo skirtisPrivalo sutapti
Pasirinkimo laikasKompiliavimo metuVykdymo metu pagal tikrąjį objektą
PaskirtisTas pats veiksmas skirtingiems argumentamsKonkretesnis paveldėto elgesio įgyvendinimas

Plačiau apie Java metodų perkrovimą

Ką verta prisiminti?

  • Perrašymas leidžia poklasiui pakeisti paveldėto egzemplioriaus metodo elgesį.
  • Metodo pavadinimas ir parametrų sąrašas turi sutapti.
  • Visada naudok @Override, kad kompiliatorius patikrintų tavo ketinimą.
  • Perrašytas metodas parenkamas vykdymo metu pagal tikrąjį objekto tipą.
  • Prieigos negalima susiaurinti.
  • final, private ir static metodai nėra perrašomi.
  • Su super.metodas() galima panaudoti ir papildyti tėvinį elgesį.