2.02 Datenkapselung – public vs. private

Schützen von Attributen


Wiedergabe stellt eine Verbindung zu YouTube her.

Bisher haben wir die Methode aenderePreisUm(int pAenderung) noch gar nicht verwendet. Diese Methode wurde mit einer if-Abfrage so programmiert, dass der Preis eines Autos durch eine Änderung nicht negativ werden kann. Dies sehen wir, wenn wir die Methode in Autotest verwenden:

public class Autotest {

  public static void main(String[] args) {
    
    // Ein Auto wird erstellt:
    Auto meinErstesAuto = new Auto(1985, "DeLorean", 54000, false, 60000);

    meinErstesAuto.aenderePreisUm(-50000);
    System.out.println(meinErstesAuto.preis);
    // Ausgabe: 10000
    
    meinErstesAuto.aenderePreisUm(-20000);
    // Ausgabe: Die Änderung ist nicht zulässig!

  }

}

Vielleicht wäre es geschickt, die Methode macheProbefahrt(int pKilometer) ebenfalls etwas sicherer zu gestalten. Momentan können wir nämlich einfach eine negative Zahl angeben und so den Kilometerstand zurückdrehen:

public class Autotest {

  public static void main(String[] args) {
    
    // Ein Auto wird erstellt:
    Auto meinErstesAuto = new Auto(1985, "DeLorean", 54000, false, 60000);

    meinErstesAuto.macheProbefahrt(-100);
    System.out.println(meinErstesAuto.kilometerstand);
    // Ausgabe: 53900
    
  }

}

Auch hier können wir eine if-Abfrage verwenden:

public class Auto {

  // Attribute
  int baujahr;
  String marke;
  int kilometerstand;
  boolean automatik;	
  int preis;

  // Konstruktor
  public Auto(int pBaujahr, String pMarke, int pKilometerstand, boolean pAutomatik, int pPreis){
    baujahr = pBaujahr;
    marke = pMarke;
    kilometerstand = pKilometerstand;
    automatik = pAutomatik;
    preis = pPreis;
  }

  // weiterer Kontruktor für Neuwagen
  public Auto(String pMarke, boolean pAutomatik, int pPreis){
    baujahr = 2016;
    kilometerstand = 0;

    marke = pMarke;
    automatik = pAutomatik;
    preis = pPreis;
  }

  // Methoden
  public void macheProbefahrt(int pKilometer){
    if(pKilometer >= 0){
      kilometerstand = kilometerstand + pKilometer;
    }else{
      System.out.println("Betrugsversuch!");
    }
  }

  public void aenderePreisUm(int pAenderung){
    if (preis+pAenderung>0){
      preis = preis+pAenderung;
    }else{
      System.out.println("Die Änderung ist nicht zulässig!");
    }
  }

}

Wollen wir nun den Kilometerstand zurückdrehen, erscheint eine eindeutige Meldung:

public class Autotest {

  public static void main(String[] args) {
    
    // Ein Auto wird erstellt:
    Auto meinErstesAuto = new Auto(1985, "DeLorean", 54000, false, 60000);

    meinErstesAuto.macheProbefahrt(-100);
    // Ausgabe: Betrugsversuch!
    
  }

}

Es scheint, als sei der Kilometerstand damit vor Manipulationen geschützt. Das ist er jedoch nicht. Anstatt nämlich mithilfe der Methode macheProbefahrt(int pKilometer) den Kilometerstand zu verändern, können wir einfach direkt auf das Attribut kilometerstand zugreifen und seinen Wert verändern:

public class Autotest {

  public static void main(String[] args) {
    
    // Ein Auto wird erstellt:
    Auto meinErstesAuto = new Auto(1985, "DeLorean", 54000, false, 60000);

    meinErstesAuto.macheProbefahrt(50);
    System.out.println(meinErstesAuto.kilometerstand);
    // Ausgabe: 54050
    
    meinErstesAuto.kilometerstand = 50000;
    System.out.println(meinErstesAuto.kilometerstand);
    // Ausgabe: 50000
    
  }

}

Hier wird zunächst eine Probefahrt von 50 km gemacht, so dass der Kilometerstand auf 54050 km ansteigt. Danach wird er aber einfach auf 50000 km zurückgedreht. Die anderen Attribute sind ebenso frei zugänglich, so dass man auch bei diesen die Werte unkontrolliert verändern könnte.

Um einen solch willkürlichen Zugriff auf die Attribute einer Klasse zu unterbinden, können wir diese schützen, indem man sie als private deklarieren:

public class Auto {

  // Attribute
  private int baujahr;
  private String marke;
  private int kilometerstand;
  private boolean automatik;	
  private int preis;

  // Konstruktor
  public Auto(int pBaujahr, String pMarke, int pKilometerstand, boolean pAutomatik, int pPreis){
    baujahr = pBaujahr;
    marke = pMarke;
    kilometerstand = pKilometerstand;
    automatik = pAutomatik;
    preis = pPreis;
  }

  // weiterer Kontruktor für Neuwagen
  public Auto(String pMarke, boolean pAutomatik, int pPreis){
    baujahr = 2016;
    kilometerstand = 0;

    marke = pMarke;
    automatik = pAutomatik;
    preis = pPreis;
  }

  // Methoden
  public void macheProbefahrt(int pKilometer){
    if(pKilometer >= 0){
      kilometerstand = kilometerstand + pKilometer;
    }else{
      System.out.println("Betrugsversuch!");
    }
  }

  public void aenderePreisUm(int pAenderung){
    if (preis+pAenderung>0){
      preis = preis+pAenderung;
    }else{
      System.out.println("Die Änderung ist nicht zulässig!");
    }
  }

}

Die Attribute sind nun von außen nicht mehr sichtbar. In Autotest erhalten wir daher auch sofort Fehlermeldungen:

public class Autotest {

  public static void main(String[] args) {
    
    // Ein Auto wird erstellt:
    Auto meinErstesAuto = new Auto(1985, "DeLorean", 54000, false, 60000);

    meinErstesAuto.macheProbefahrt(50);
    System.out.println(meinErstesAuto.kilometerstand); // FEHLER!
    
    meinErstesAuto.kilometerstand = 50000; // FEHLER!
    System.out.println(meinErstesAuto.kilometerstand); // FEHLER!
    
  }

}

Der einzige Weg, den Preis nun zu ändern ist über die Methode aenderePreisUm(int pAenderung). Den Kilometerstand können wir nur noch mit der Methode macheProbefahrt(int pKilometer) erhöhen.

Getter

Leider ergibt sich damit aber wieder ein neues Problem. Die Attribute sind nun so gut geschützt, dass wir uns noch nicht einmal mehr deren aktuelle Werte anzeigen lassen können:

public class Autotest {

  public static void main(String[] args) {
    
    // Ein Auto wird erstellt:
    Auto meinErstesAuto = new Auto(1985, "DeLorean", 54000, false, 60000);

    System.out.println(meinErstesAuto.kilometerstand); // FEHLER!

  }

}

Um das Auslesen (aber nicht das Verändern) des Wertes eines Attributes zu ermöglichen, müssen wir jetzt noch die sogenannten Getter (oder auch get-Methoden) ergänzen:

public class Auto {

  // Attribute
  private int baujahr;
  private String marke;
  private int kilometerstand;
  private boolean automatik;	
  private int preis;

  // Konstruktor
  public Auto(int pBaujahr, String pMarke, int pKilometerstand, boolean pAutomatik, int pPreis){
    baujahr = pBaujahr;
    marke = pMarke;
    kilometerstand = pKilometerstand;
    automatik = pAutomatik;
    preis = pPreis;
  }

  // weiterer Kontruktor für Neuwagen
  public Auto(String pMarke, boolean pAutomatik, int pPreis){
    baujahr = 2016;
    kilometerstand = 0;

    marke = pMarke;
    automatik = pAutomatik;
    preis = pPreis;
  }

  // Methoden
  public void macheProbefahrt(int pKilometer){
    if(pKilometer >= 0){
      kilometerstand = kilometerstand + pKilometer;
    }else{
      System.out.println("Betrugsversuch!");
    }
  }

  public void aenderePreisUm(int pAenderung){
    if (preis+pAenderung>0){
      preis = preis+pAenderung;
    }else{
      System.out.println("Die Änderung ist nicht zulässig!");
    }
  }

  // Getter
  public int getBaujahr() {
    return baujahr;
  }

  public String getMarke() {
    return marke;
  }

  public int getKilometerstand() {
    return kilometerstand;
  }

  public boolean isAutomatik() {
    return automatik;
  }

  public int getPreis() {
    return preis;
  }

}

Die Getter sind immer nach dem gleichen Muster aufgebaut. Zunächst wird mit public festgelegt, dass wir sie von anderen Klassen (wie z.B. Autotest) aus aufrufen können. Man sagt, dass sie öffentliche Methoden sind. Danach wird der Datentyp des betreffenden Attributs angegeben – also zum Beispiel int beim Attribut baujahr. Die leeren runden Klammern geben an, dass die Methode keinen Parameter benötigt. Die einzige Anweisung, die schließlich ausgeführt wird, ist das Zurückgeben des Attributwertes mit return. Solltest Du damit nicht vertraut sein, solltest Du Dir die Lektion zu Methoden mit und ohne Rückgabewert ansehen!

Nun können wir die Werte der Attribute von außen auslesen. Die Getter bilden dabei eine Einbahnstraße: Auslesen ist erlaubt, Verändern nicht. Hier ein Beispiel zur Verwendung eines Getters:

public class Autotest {

  public static void main(String[] args) {
    
    // Ein Auto wird erstellt:
    Auto meinErstesAuto = new Auto(1985, "DeLorean", 54000, false, 60000);
    
    meinErstesAuto.macheProbefahrt(100);
    System.out.println(meinErstesAuto.getKilometerstand());
    // Ausgabe: 54100

  }

}

Im Klassendiagramm ergänzen wir nun noch durch und +, dass die Attribute alle private sind und die Methoden hingegen public:

Mit Minus werden private Attribute und Methoden markiert. Mit Plus öffenliche.
Das fertige Klassendiagramm

Hier sollte Dir auch noch auffallen, dass bei den get-Methoden der Datentyp des Rückgabewertes angegeben wird. Später werden wir noch weitere Methoden mit Rückgabewerten verwenden – nicht immer nur die Getter – bei denen dann ebenfalls der Datentyp angegeben werden muss.

Setter

Neben den get-Methoden können oft auch set-Methoden nützlich sein. Diese sind – ähnlich wie aenderePreisUm – dazu da, den Wert einen Attributs kontrolliert zu verändern. Allerdings gibt man dort dann immer direkt den neuen gewünschten Wert an. Hier ein Beispiel für unsere Klasse Auto:

public void setPreis(int pPreis){
  if (pPreis > 0){
    preis = pPreis;
  }
}