Objektorientierte Programmierung: Unterschied zwischen den Versionen

Aus CCWiki
Zur Navigation springen Zur Suche springen
Zeile 306: Zeile 306:
}
}
</syntaxhighlight>
</syntaxhighlight>
Folgendes ist wichtig bei der Vererbung zu erwähnen. Die '''Subklasse''' muss den '''Konstruktor''' der '''Superklasse''' aufrufen. Wenn es nur den '''Standard Konstruktor''' (keine Parameter), muss auch nicht '''super()''' aufgerufen werden.


== Implementierung ==
== Implementierung ==

Version vom 26. Januar 2021, 13:59 Uhr

Objektorientierte Programmierung

Die objektorientierte Programmierung (kurz OOP) ist ein auf dem Konzept der Objektorientierung basierendes Programmierparadigma. Die Grundidee besteht darin, die Architektur einer Software an den Grundstrukturen desjenigen Bereichs der Wirklichkeit auszurichten, der die gegebene Anwendung betrifft. Ein Modell dieser Strukturen wird in der Entwurfsphase aufgestellt. Es enthält Informationen über die auftretenden Objekte und deren Abstraktionen, ihre Typen. Die Umsetzung dieser Denkweise erfordert die Einführung verschiedener Konzepte, insbesondere Klassen, Vererbung, Polymorphie und spätes Binden (dynamisches Binden).[1]

Java

Java ist eine Objektorientierte Programmiersprache. In einigen Punkten nimmt Sie die Objektorientierung nicht so streng wie andere Sprachen. Ein Beispiel hierfür wären die primitiven Datentypen. Somit ist in Java nicht alles ein Objekt.

Primitive Datentypen

In Java gibt es eine vielzahl von primitiven Datentypen. Sie unterscheiden sich darin, dass Sie kein Object sind. Sie haben keine Methoden und auch keine Attribute. Wird eine Variable mit einem primitiven Datentyp erstellt und dieser Variable ein Wert zugewiesen, so enthält diese Variable den wirklichen Wert und nicht nur eine Referenz auf das eigentliche Objekt:

//Variable a enthält den Wert 10
int a = 10;
//Variable b enthält nur den Verweis auf das Integer Objekt mit dem Wert 10
Integer b = Integer.valueOf(10);

Zu jedem primitiven Datentyp, gibt es einen entsprechende Klasse:

Entsprechende Klasse für primitive Datentypen
primitiver Datentyp Klasse
int Integer
float Float
double Double
char Character
boolean Boolean

Primitive Datentypen können automatisch in ihr entsprechendes Klassenäquivalent umgewandelt werden und natürlich umgekehrt. Dieser Prozess nennt sich Autoboxing. Dies ist vorallem von Vorteil, wenn mit Collections (Listen,...) gearbeitet wird, da diese nur mit Klassen funktionieren.

//Umwandlung der Zahl 10 in ein Integer Object
Integer b = 10;
//Integer Object wird in primitive Zahl 10 umgewandelt
int c = b;

Klasse

Eine Klasse kann man sich wie eine Vorlage vorstellen. Aus dieser Vorlage können Instanzen mittels des Schlüsselworts new erstellt werden.

Konstruktor

Jede Klasse hat einen Konstruktor, ist dieser leer, d.h. er nimmt keine Parameter, muss dieser nicht geschrieben werden. Der Konstruktor heißt immer gleich wie die Klasse selbst.

public class Animal {
  private String name;

  //Standardkonstruktor wird verwendet.
}
...
//Instanz von Animal erstellen
Animal a = new Animal();

Wollen wir nur beispielsweise garantieren, dass ein Animal bei der Instanzierung einen Namen hat, so könnten wir den Konstruktor verändern.

public class Animal {
  private String name;

  public Animal(String name) {
    this.name = name;
  }
}
...
//Instanz von Animal erstellen
Animal a = new Animal("Alfons");

Es können auch mehrere Konstruktoren existieren. Diese können sich auch gegenseitig aufrufen. Sobald ein Konstruktor definiert wird, so kann der Standardkonstruktor nicht mehr zur Instanzierung verwendet werden, dieser muss explizit hingeschrieben werden, wenn nötig.

public class Animal {
  private String name;

  public Animal() {
    this("John Doe")
  }

  public Animal(String name) {
    this.name = name;
  }
}
...
//Instanz von Animal erstellen
Animal a = new Animal(); //John Doe
//Weitere Instanz mit gewähltem Namen
Animal b = new Animal("Alfons"); //Alfons

Attribute

Wir unterscheiden hier zwei Varianten von Attributen (Eigenschaften).

Instanz Attribute

public class Animal {
  public String name;
}
...
//Korrekter Zugriff
Animal a = new Animal();
System.out.println(a.name);

Jede Instanz verfügt über eigene Instanzvariablen, diese werden nicht untereinander geteilt.

Klassen Attribute

public class Animal {
  public static double PI = 3.14;
}
//Korrekter Zugriff
System.out.println(Animal.PI);

Attribute die mit dem Schlüsselwort static gekennzeichnet werden, existieren nur einmal. Vereinfacht gesagt, diese Attribute sind an die Klasse und nicht an die Instanz gebunden.

Methoden

Methoden sind Prozeduren (Abläufe) die in Klassen Definiert werden. Durch Methoden können Objekte miteinander interagieren. Genauso wie bei den Attributen können Methoden für eine Instanz oder mit dem Schlüsselwort static für die Klasse definiert werden.

Methoden haben zumindest:

  • Methodennamen
  • Rückgabetype
  • 0 - * Parameter
  • Methodenrumpf (Code der Ausgeführt wird)

Instanzmethode

Folgendes Beispiel zeigt den klassischen getter und setter, Attribute sollten nach Möglichkeit nur über diese geholt/verändert werden und die Attribute sollten nach außen hin nicht sichtbar sein (private). Warum? Durch den setter ist es möglich, eine inkorrekte Modifikation des Attributs zu unterbinden.

public class Animal {
  private String name;

  public void setName(String name) {
    this.name = name;
  }

  public void getName(String name) {
    this.name = name;
  }
}
...
//Korrekter Aufruf
Animal a = new Animal();
a.setName("Alfons");
System.out.println(a.getName());

Klassenmethode

Eine klassische Helper Methode.

public class Animal {
  private float weight;

  public Animal(float weight) {
    this.weight = weight;
  }

  public static float calculateWeight(List<Animal> animals) {
    float weight = 0;
    for(Animal a : animal) {
      weight += a.weight;
    }
  }
}
...
//Korrekter Aufruf
List<Animal> animals = new ArrayList<>();
animals.add(new Animal(10));
animals.add(new Animal(20));
animals.add(new Animal(30));
System.out.println(Animal.calculateWeight(animals));

Abstrakte Klasse

Abstrakte Klassen sind unfertige Klassen. Sie können nicht direkt instanziert werden. Es können nur Subklassen (Klassen die davon erben) davoninstanziert werden. Zusätzlich besteht die Möglichkeit eine Instanz durch eine Anonyme Implementierung der abstrakten Klasse zu erstellen.
Sie einzusetzen ist sinnvoll, wenn sich eine Gruppe von Klassen Funktionalität teilt, doch gewisse Funktionalität von jeder einzelnen Klasse implementiert werden muss.
Die noch zu implementierenden Methoden und die Klasse selbst werden mit dem Schlüsselwort abstract gekennzeichnet, .

public abstract class Animal {
  private String name;
  private float weight;

  public Animal(String name, String weight) {
    this.name = name;
    this.weight = weight;
  }

  //Abstrakte Methode, muss implementiert werden
  public abstract void eat();
}

public class Cat extends Animal {
  public Animal(String name, String weight) {
    //Konstruktor der Superklasse muss aufgerufen werden
    super(name, weight);
  }

  @Override
  public void eat() {
    System.out.println("I eat mice!");
  }
}

public class Bird extends Animal {
  public Animal(String name, String weight) {
    //Konstruktor der Superklasse muss aufgerufen werden
    super(name, weight);
  }

  @Override
  public void eat() {
    System.out.println("I eat worms!");
  }
}
...
//Instanzierung
Bird b = new Bird('Tweety');
b.eat();
//Folgendes ist auch möglich weil, durch die Vererbung, ein Bird ist ein Animal
Animal b1 = new Bird('Tweety');
b1.eat();
//Anonyme Implementierung
Animal a = new Animal("Anonymous", 30) {
  public void eat() {
    System.out.println("I eat nothing.");
  }
};

Interface

Interfaces oder auch Schnittstellen bieten die Möglichkeit Methoden zu definieren welche von Klasse die diese Schnittstelle implementieren, implementiert werden müssen. Der Vorteil von Schnittstellen gegenüber der Vererbung ist, dass eine Klasse mehrere Interfaces implementieren kann. Genauso wie bei der Abstrakten Klasse gilt, Interfaces können nicht direkt instanziert werden, eine Anonyme Implementierung ist jedoch möglich.

public interface CanDrive {
  void drive();
}

public interface CanFly {
  void fly();
}

public class Car implements CanDrive {
  @Override
  public void drive() {
    System.out.println("Driving on the road");
  }
}

public class Plane implements CanFly {
  @Override
  public void fly() {
    System.out.println("Flying in the sky");
  }
}

public class KnightRider implements CanDrive, CanFly {
  @Override
  public void drive() {
    System.out.println("Driving on the road");
  }

  @Override
  public void fly() {
    System.out.println("Flying in the sky");
  }
}
...
//Neues Flugzeug wird instanziert
Plane plane = new Plane();
//Zuweisung ist möglich, da die Klasse Plane CanFly implementiert
CanFly canFly = plane;
//Neues Auto wird instanziert
Car car = new Car();
//Zuweisung ist möglich, da die Klasse Car CanDrive implementiert
CanDrive canDrive = car;
//Neuer KnightRider wird instanziert
KnightRider kit = new KnightRider();
//Zuweisung ist möglich, da die Klasse KnightRider CanDrive implementiert
canDrive = kit;
//Zuweisung ist möglich, da die Klasse KnightRider CanFly implementiert
canFly = kit;

Vererbung

Bei der Vererbung gibt es immer zwei Protagonisten, die Subklasse und die Superklasse. Die Subklasse erbt von der Superklasse. Das heißt Sie übernimmt alle Attribute und alle Methoden. Die Instanz Methoden können auch überschrieben werden, wenn diese nicht mit dem Schlüsselwort final markiert wurden. Vererbung geschieht mittels des Schlüsselworts extends, das heißt eine Klasse erweitert eine andere Klasse.

Die Superklasse kann entweder eine normale Klasse oder eine Abstrakte Klasse sein. Von einem Interface, wird nicht geerbt, dieses wird implementiert.
public class Animal {
  private String name;

  public Animal(String name) {
    this.name = name;
  }

  //Kann nicht überschrieben werden
  public final String getName() {
    return this.name
  }

  public void sayHello() {
    System.out.println("Hallo, ich bin " + name);
  }
}

public class Cat {
  public Cat(String name) {
    //Der Konstruktor der Superklasse muss aufgerufen werden
    super(name);
  }

  @Override
  public void sayHello() {
    System.out.println("Hallo mein Name ist " + getName() + " und ich bin eine Katze!");
  }
}

Folgendes ist wichtig bei der Vererbung zu erwähnen. Die Subklasse muss den Konstruktor der Superklasse aufrufen. Wenn es nur den Standard Konstruktor (keine Parameter), muss auch nicht super() aufgerufen werden.

Implementierung

Instanz vs Klasse

Sichtbarkeit

Multithreading