Clase abstracte (abstract) în Java

O clasă abstract nu poate fi instanțiată direct; poate avea metode abstracte, pe care subclasele concrete sunt obligate să le scrie.

Sintaxă
abstract class Angajat {
    final String nume;
    Angajat(String nume) { this.nume = nume; }

    abstract double salariu();      // fără corp: o scriu subclasele

    String fluturas() {             // metodă normală
        return nume + ": " + salariu();
    }
}

Ce face Clase abstracte (abstract)

Cuvântul cheie abstract pus în fața unei clase spune că ea este doar o bază: new Angajat(...) nu compilează. Pus în fața unei metode, spune că metoda nu are corp (se termină cu ;) și că fiecare subclasă concretă trebuie să o implementeze.

Spre deosebire de o interfață, o clasă abstractă poate avea câmpuri cu stare, constructori și metode cu orice nivel de acces. Este potrivită când mai multe clase împart cod comun și diferă doar în câteva metode. O clasă poate extinde o singură clasă abstractă, dar poate implementa multe interfețe.

Un tipar des întâlnit este „metoda șablon”: clasa abstractă scrie pașii generali ai unui algoritm într-o metodă obișnuită și lasă detaliile pe seama metodelor abstracte.

Exemple

Exemplul 1: Bază comună, detalii în subclase

fluturas() este scrisă o singură dată în clasa abstractă și apelează salariu(), pe care fiecare subclasă o implementează altfel.

abstract class Angajat {
    final String nume;

    Angajat(String nume) {
        this.nume = nume;
    }

    abstract double salariu();

    String fluturas() {
        return String.format("%-8s %-16s %9.2f lei", nume, getClass().getSimpleName(), salariu());
    }
}

class AngajatLunar extends Angajat {
    final double lunar;
    AngajatLunar(String nume, double lunar) { super(nume); this.lunar = lunar; }

    @Override
    double salariu() { return lunar; }
}

class AngajatOrar extends Angajat {
    final double tarif;
    final int ore;
    AngajatOrar(String nume, double tarif, int ore) { super(nume); this.tarif = tarif; this.ore = ore; }

    @Override
    double salariu() { return tarif * ore; }
}

public class Main {
    public static void main(String[] args) {
        Angajat[] statPlata = {
            new AngajatLunar("Elena", 7800),
            new AngajatOrar("George", 45.5, 120),
        };
        for (Angajat a : statPlata) {
            System.out.println(a.fluturas());
        }
    }
}

Rezultat

Elena    AngajatLunar       7800.00 lei
George   AngajatOrar        5460.00 lei

Exemplul 2: Metoda șablon

Raport.genereaza() fixează ordinea pașilor. Subclasele aleg doar titlul și liniile. Metoda șablon este final, ca subclasele să nu-i schimbe ordinea.

import java.util.List;

abstract class Raport {
    final void genereaza() {
        System.out.println("=== " + titlu() + " ===");
        for (String linie : linii()) {
            System.out.println("  " + linie);
        }
        System.out.println("(" + linii().size() + " rânduri)");
    }

    abstract String titlu();
    abstract List<String> linii();
}

class RaportVanzari extends Raport {
    @Override String titlu() { return "Vânzări octombrie"; }
    @Override List<String> linii() { return List.of("Iași: 12.400 lei", "Cluj: 15.150 lei"); }
}

class RaportStoc extends Raport {
    @Override String titlu() { return "Stoc depozit"; }
    @Override List<String> linii() { return List.of("făină: 40 saci", "zahăr: 25 saci", "ulei: 60 l"); }
}

public class Main {
    public static void main(String[] args) {
        new RaportVanzari().genereaza();
        new RaportStoc().genereaza();
    }
}

Rezultat

=== Vânzări octombrie ===
  Iași: 12.400 lei
  Cluj: 15.150 lei
(2 rânduri)
=== Stoc depozit ===
  făină: 40 saci
  zahăr: 25 saci
  ulei: 60 l
(3 rânduri)

Greșeli frecvente

  • Încerci să creezi obiectul direct: new Angajat("Ion") dă Angajat is abstract; cannot be instantiated.
  • O subclasă uită o metodă abstractă: AngajatOrar is not abstract and does not override abstract method salariu() in Angajat.
  • Scrii corp pentru o metodă abstractă (abstract double salariu() { ... }): abstract methods cannot have a body.
  • Combini abstract cu final sau private pe aceeași metodă: nu are sens, pentru că metoda trebuie rescrisă, iar compilatorul refuză.

Comenzi înrudite

  • Interfețe (interface, implements): O interfață (interface) descrie ce metode trebuie să aibă o clasă; clasa o promite cu implements și poate implementa mai multe interfețe.
  • Moștenire (extends): Cu extends o clasă moștenește câmpurile și metodele altei clase și le poate extinde sau rescrie; Java permite o singură superclasă.
  • Polimorfism: Polimorfismul înseamnă că același apel (f.arie()) execută metoda clasei reale a obiectului, chiar dacă variabila are tipul superclasei.
  • final (și static final): final interzice schimbarea: o variabilă primește valoare o singură dată, o metodă nu poate fi rescrisă, o clasă nu poate fi extinsă.
  • sealed, permits și non-sealed: O clasă sau interfață sealed își alege explicit subtipurile (permits); fiecare subtip este final, sealed sau non-sealed.