Abstrakt klasse vs. grensesnitt i Java: Komplett guide

Abstrakte klasser og grensesnitt i Java

I Java benyttes abstrakte klasser og grensesnitt for å oppnå abstraksjon. Abstraksjon innen objektorientert programmering innebærer å skjule de interne detaljene av en implementasjon fra sluttbrukeren.

Dette tillater oss å vite hvilke funksjoner som er tilgjengelige, uten å måtte forstå nøyaktig hvordan de er konstruert.

La oss utforske disse konseptene og se hvorfor de er så viktige.

Abstrakt klasse

En abstrakt klasse i Java er en klasse som ikke kan opprettes som et objekt, og den kan inneholde abstrakte metoder. En abstrakt metode er en metode som er deklarert uten en spesifikk implementasjon.

Et eksempel på en abstrakt klasse er `GraphicObject` fra Oracle.

For å definere en abstrakt klasse, må vi bruke nøkkelordet `abstract` før klassenavnet.

abstract class AbstraktKlasse {
    void utfør() {
        System.out.println("utført");
    }
}

En abstrakt klasse kan være utgangspunkt for andre klasser, altså den kan arves. Dette gjør det mulig å gjenbruke kode og skape et hierarki av klasser.

abstract class AbstraktKlasse {
    void utfør() {
        System.out.println("utført");
    }
}

class UtvidetAbstraktKlasse extends AbstraktKlasse {
    void nyMetode() {
        System.out.println("ny");
    }

    @Override
    void utfør() {
        System.out.println("overstyrt");
    }
}

Abstrakte klasser er nyttige for å implementere felles metoder som deles av flere klasser som arver den abstrakte klassen. De er også ideelle for klasser med metoder som ligner på hverandre, men har ulike implementeringer.

La oss se på et eksempel med biler. Biler har felles funksjoner som å starte, stoppe og rygge.

Men hva med selvkjørende funksjoner? Implementeringen av disse kan variere betydelig mellom ulike typer biler. La oss se hvordan vi kan modellere dette i et objektorientert program.

Først oppretter vi en `Bil`-klasse som vil fungere som grunnlag for ulike biltyper.

abstract class Bil {
    void start() {
        System.out.println("starter bilen");
    }

    void stopp() {
        System.out.println("motoren stopper");
    }

    void rygg() {
        System.out.println("ryggemodus aktivert");
    }

    abstract void selvkjør();
}

Metodene `start()`, `stopp()` og `rygg()` er felles for alle biler, og deres implementering er gitt i `Bil`-klassen. Selvkjøring, derimot, kan ha ulike implementasjoner, og `selvkjør()` defineres derfor som en abstrakt metode. Dette gjør at hver biltype kan implementere denne funksjonen på sin egen måte.

class BilTypeA extends Bil {
    @Override
    void start() {
        super.start();
    }

    @Override
    void stopp() {
        super.stopp();
    }

    @Override
    void rygg() {
        super.rygg();
    }

    void selvkjør() {
        System.out.println("Type A selvkjøring aktivert");
    }
}
class BilTypeB extends Bil {
    void selvkjør() {
        System.out.println("Type B selvkjøring aktivert");
    }
}

Det er viktig å merke seg at hvis en underklasse ikke implementerer alle abstrakte metoder i en abstrakt klasse, må underklassen selv deklareres som abstrakt.

Grensesnitt

Et grensesnitt spesifiserer hvilke metoder en klasse må implementere. For eksempel, en bil har grunnleggende funksjoner som å starte, bevege seg og stoppe. Hvis vi implementerer et grensesnitt for en bil, må alle disse metodene implementeres for at bilen skal fungere korrekt.

På samme måte som abstrakte klasser, kan vi ikke opprette objekter fra et grensesnitt. Det kan betraktes som en fullstendig abstrakt klasse da den kun inneholder abstrakte metoder, det vil si metoder uten implementasjon.

Vi bruker nøkkelordet `interface` for å definere et grensesnitt.

interface BIL {
    void start();
    void stopp();
    void kjør();
}

Når vi definerer en klasse som implementerer et grensesnitt, må vi bruke nøkkelordet `implements`.

class BilTypeB implements BIL {
    public void start() {
        System.out.println("startet");
    }

    public void stopp() {
        System.out.println("stoppet");
    }

    public void kjør() {
        System.out.println("kjører");
    }
}

Likheter

Den eneste fellesnevneren mellom abstrakte klasser og grensesnitt er at ingen av dem kan instansieres som objekter.

Forskjeller

Abstrakt klasse Grensesnitt
Arv og implementasjon En klasse kan kun arve én abstrakt klasse. En klasse kan implementere flere grensesnitt.
Variable typer Kan ha `final`, ikke-`final`, `static` og ikke-`static` variabler. Kan kun ha `static` og `final` variabler.
Metodetyper Kan inneholde både abstrakte og ikke-abstrakte metoder. Kan kun inneholde abstrakte metoder (med unntak av statiske metoder).
Tilgangsmodifikatorer En abstrakt klasse kan ha tilgangsmodifikatorer. Metodesignaturene i grensesnitt er offentlige (`public`) som standard. Et grensesnitt har ikke en tilgangsmodifikator.
Konstruktører og destruktører Kan deklarere konstruktører og destruktører. Kan ikke deklarere konstruktører eller destruktører.
Hastighet Raskere Langsommere

Når skal man bruke abstrakt klasse og grensesnitt?

Bruk abstrakte klasser når:

  • Du trenger å dele felles metoder og felt mellom flere klasser.
  • Du må erklære ikke-statiske og ikke-final felt for å endre tilstanden til objektet de er relatert til.

Bruk grensesnitt når:

  • Du ønsker å definere oppførselen til en klasse uten å bekymre deg om hvordan den implementeres.
  • Du vil forsikre deg om at en klasse implementerer alle nødvendige metoder for å fungere korrekt.

Avsluttende tanker

Grensesnitt er sentrale for å skape APIer, fordi de gir en struktur for funksjonalitet uten å kreve en konkret implementasjon.

Abstrakte klasser er ideelle for å dele felles abstrakte og ikke-abstrakte metoder mellom klasser som arver dem, noe som øker gjenbrukbarheten av koden.

Utforsk mer om Java med disse online kursene. Forbereder du deg til et Java-intervju? Her er noen intervjuspørsmål om objektorientert programmering.