Arithmetische Ausdrücke

JavaIsTheBest

Bekanntes Mitglied
Hallo,
ich habe folgende Aufgabe im Anhang. Das UML Diagramm ist mit dabei.
Ein paar Fragen hätte ich zu der Aufgabe.

1. Was macht combine()?
2. Warum ist die Methode compute() in Bin und nicht in den Subklassen von Bin?

Java:
// Allgemeiner arithmetischer Ausdruck.
abstract class Expr {
    // Wert des Ausdrucks berechnen.
    public abstract double compute();
}
Java:
// Konstanter Ausdruck.
class Const extends Expr {
    // Wert des Ausdrucks.
    private double value;

    // Konstanten Ausdruck mit Wert v erzeugen.
    public Const(double v) {
        value = v;
    }

    // Wert des konstanten Ausdrucks berechnen.
    public double compute() {
        return value;
    }

    // Zeichenkettendarstellung des konstanten Ausdrucks liefern.
    public String toString() {
        return "" + value;
    }

    // Konstanten Ausdruck mit Objekt other vergleichen.
    public boolean equals(Object other) {
        if (!(other instanceof Const))
            return false;
        Const that = (Const) other;
        return this.value == that.value;
    }
}
Java:
// Binärer Ausdruck.
abstract class Bin extends Expr {
    // Linker und rechter Teilausdruck.
    private Expr left, right;

    // Binären Ausdruck mit Teilausdrücken l und r erzeugen.
    protected Bin(Expr l, Expr r) {
        left = l;
        right = r;
    }

    // Werte l und r mit dem Operator des binären Ausdrucks verknüpfen.
    protected abstract double combine(double l, double r);

    // Operator des binären Ausdrucks liefern.
    protected abstract String oper();

    // Wert des binären Ausdrucks berechnen.
    public double compute() {
        return combine(left.compute(), right.compute());
    }

    // Zeichenkettendarstellung des binären Ausdrucks liefern.
    public String toString() {
        return "(" + left + oper() + right + ")";
    }

    // Binären Ausdruck mit Objekt other vergleichen.
    public boolean equals(Object other) {
        if (!(other instanceof Bin))
            return false;
        Bin that = (Bin) other;
        return this.oper().equals(that.oper()) && this.left.equals(that.left) && this.right.equals(that.right);
    }
}
Java:
// Addition.
class Add extends Bin {
    // Addition mit Teilausdrücken l und r erzeugen.
    public Add(Expr l, Expr r) {
        super(l, r);
    }

    // Werte l und r durch Addition verknüpfen.
    public double combine(double l, double r) {
        return l + r;
    }

    // Operator der Addition liefern.
    protected String oper() {
        return "+";
    }
}

Über Eure Hilfe, bedanke ich mich im Voraus.
 

Anhänge

  • 1.png
    1.png
    46,3 KB · Aufrufe: 44
  • 2.png
    2.png
    60,5 KB · Aufrufe: 25

Meniskusschaden

Top Contributor
1. Was macht combine()?
combine() ist dazu gedacht, zwei Ausdrücke miteinander zu verknüpfen. Weil das jede der genannten Rechenoperation können muss, aber jede es auf unterschiedliche Weise tun muss, wird combine() in der Klasse Bin als abstrakte Methode definiert. Dadurch wird sicher gestellt, dass jede Subklasse so eine Methode hat und zwar eine individuell ausgeprägte, da Bin keine Implementierung enthält, die vererbt werden könnte.
2. Warum ist die Methode compute() in Bin und nicht in den Subklassen von Bin?
Die compute()-Methode soll für alle Bin-Subklassen dasselbe tun, nämlich die Verknüpfung für den linken und rechten Ausdruck aufrufen. Deshalb können die Unterklassen die Implementierung der Methode compute() von Bin erben, denn für alle soll dasselbe passieren: die jeweilige combine()-Methode der Subklasse aufrufen.

Die Individualität der Subklassen wird also nicht in der Methode compute() realisiert, sondern in der Methode combine(). Durch diesen Entwurf kann man immer weitere binäre Operationen einführen, indem man entsprechende Subklassen hinzufügt, etwa für das Potenzieren. Die anderen Klassen müssen dafür nicht mehr angepasst werden.
 

Baldur

Aktives Mitglied
Hm, recht interessantes Beispiel für Klassenvererbung. Mal was anderes als die üblichen Fahrzeug/Auto/Fahrrad-Beispiele ;)

Das compute/combine was du ansprichst ist tatsächlich ein ganz interessanter Aspekt an dem Beispiel (und auch durchaus realistisch)

Das ganze Beispiel soll ja eine OOP-Darstellung eines beliebigen mathematischen Terms sein. Ich denke mal soweit ist das ja klar. (Ganz am Rande bemerkt: wenn ein Compiler ein Programm aus dem Quelltext einliest, erstellt er im Prinzip auch sowas, nennt sich dann "Abstract Syntax Tree")

Also warum steht compute in "Bin" und nicht in "Add"? Ich denke mal das wird klarer, wenn du auch die Klassen für die anderen Rechenoperationen hast. Alle vier Klassen haben ja eine Gemeinsamkeit: Sie verrechnen zwei Werte miteinander, mit ihrem jeweiligen Operator. Also hätte man, wenn man es vollständig schreibt, in jeder der vier Klassen sowas wie left.compute() * right.compute(), nur jeweils mit einem anderen Operator.
Also geht man her, und erstellt sich eine Oberklasse für alle Rechenoperationen mit zwei Operanden -> Bin(aryOperation) und führt die abstrakte Funktion "combine" ein, die die konkrete Rechnung dann auf die zwei Operanden anwenden soll.
Wichtige Regel als Programmierer: Immer möglichst faul sein ;) Doppelter Code ist unnötiger Code, daher lagert man alles was redundant wäre in eine eigene Klasse aus.
Hier gehts zwar "nur" um zwei Aufrufe von "compute", in einem komplexeren Beispiel würde hier vielleicht noch irgendeine Validierung durchgeführt o.ä., so daß es sich mehr lohnen würde, den Code auszulagern.
 

JavaIsTheBest

Bekanntes Mitglied
Ich habs immer noch nicht ganz verstanden.
Sagen wir mal, ich hätte diesen arithmetischen Ausdruck: 3*(2+3)/(7-2)

1. Was macht combine() mit diesem Ausdruck?
2. Was macht compute() mit diesem Ausdruck?
3. Welcher Teilausdruck ist left vom Typ Expr und, welcher ist right vom Typ Expr und warum?
4. Könnte mir bitte jemand an einem Beispiel, die Methode compute() erklären?
Java:
public double compute () {
return combine(left.compute(), right.compute());
}
5. In der Methode compute() wird die Methode combine() aufgerufen. Das dürfte doch gar nicht möglich sein, da die Oberklasse nichts über die Unterklasse weiß.
 

Meniskusschaden

Top Contributor
Man kann dein Beispiel so berechnen:
Java:
public class Main {
    public static void main(String[] args) {
        Expr zwei = new Const(2);
        Expr drei = new Const(3);
        Expr sieben = new Const(7);
        Bin subExpression = new Sub(sieben, zwei);
        Bin addExpression = new Add(zwei, drei);
        Bin divExpression = new Div(addExpression, subExpression);
        Bin mulExpression = new Mul(drei, divExpression);

        System.out.println(mulExpression.compute());
    }
}
Ich habe auf der linken Seite des Gleichheitszeichens jeweils die allgemeinste mögliche Klasse für die Variablendeklaration benutzt und auf der rechten Seite die konkrete Unterklasse erzeugt. So versteht man es etwas besser und es ist auch der bessere Programmierstil.

Ein Objekt vom Typ Const ist gleichzeitig auch vom Typ Expr. Ein Objekt vom Typ Add, Sub, Div oder Mul ist gleichzeitig auch vom Typ Bin und vom Typ Expr. Das bedeutet, dass für diese Objekte nicht nur die Methoden aufgerufen werden können, die in der eigenen Klasse definiert wurden, sondern auch die Methoden der übergeordneten Klassen. Deshalb kann beispielsweise in der letzten ZeilemulExpression.compute()aufgerufen werden, obwohl diese Methode in der Klasse Mul überhaupt nicht programmiert wurde. Die Methode wurde aus der Klasse Bin geerbt.

Es wird also für das Objekt mulExpression vom Typ Mul die Methode compute() der Klasse Bin ausgeführt. In dieser Methode wird dann (wieder für das Objekt mulExpression) die Methode combine() aufgerufen.

Diese combine()-Methode stammt jedoch nicht aus der Oberklasse, denn für die Unterklasse Mul existiert ja eine eigene combine()-Methode. Die combine-Methode ruft dann für die beiden im Objekt mulExpression gespeicherten Ausdrücke left und right die compute()-Methode auf und multipliziert die beiden Ergebnisse.

left und right sind in diesem Beispiel die Objekte drei und divExpression, die beim Erzeugen von mulExpression angegeben wurden:Bin mulExpression = new Mul(drei, divExpression);Hier wird zunächst der Konstruktor der Klasse Mul aufgerufen, der seinerseits mittelssuper(l, r);den Konstruktor von Bin aufruft und die beiden Ausdrücke in den Attributen left und right speichert. Hier ist wichtig zu verstehen, dass auch der Konstruktor von Bin für das mulExpression-Objekt aufgerufen wird. Es gibt nicht etwa ein zweites Bin-Objekt.
5. In der Methode compute() wird die Methode combine() aufgerufen. Das dürfte doch gar nicht möglich sein, da die Oberklasse nichts über die Unterklasse weiß.
Doch die Oberklasse Bin weiß etwas über alle ihre Nachfahren:
Java:
protected abstract double combine(double l, double r);
Durch das Schlüsselwort abstract werden zwei Dinge erreicht. Zum Einen ist es nicht möglich, konkrete Bin-Objekte zu erzeugen, sondern nur Objekte aus Unterklassen davon. Zum anderen verpflichtet die Klasse Bin alle ihre Nachfahren dazu, eine combine()-Methode bereit zu stellen. Wenn man also eine VariableBin binhat, weiß man zwar nicht unbedingt, ob sie vom Typ Sub, Add, Mul oder Div oder sogar von einem ganz anderen Typ ist, man weiß aber, dass sie ein Nachfahre vom Typ Bin ist und somit auf jeden Fall eine combine()-Methode hat. Deshalb kann die combine()-Methode aufgerufen werden.
 

JavaIsTheBest

Bekanntes Mitglied
Vielen Dank, dass du dir Zeit nimmst, mir deine Fragen zu beantworten. :)

1. Also, weil in der Bin Klasse die Methode combine abstrakt ist, weiß die Klasse Bin, dass es in der Unterklasse eine Methode combine gibt, die nicht abstrakt ist?
2. Woher weiß die Methode compute, welche combine() Methode aufgerufen werden soll? Es gibt immerhin vier.
3. Warum ist die toString() Methode nicht in der Klasse Expr abstrakt?
4. Warum hat die Methode equals() als Parameter Object und nicht Expr? Das wäre doch geschickter, weil dann müsste ich nicht mehr überprüfen,ob der Parameter eine Instanz vom Typ Expr ist.
 

Baldur

Aktives Mitglied
1. Also, weil in der Bin Klasse die Methode combine abstrakt ist, weiß die Klasse Bin, dass es in der Unterklasse eine Methode combine gibt, die nicht abstrakt ist?
Genaugenommen wird durch das "abstract" der Entwickler dazu verpflichtet, in jeder Unterklasse die er schreibt, alle abstrakten Methoden zu implementieren (oder seine Klasse ebenfalls als abstrakt zu markieren). Also kannst du davon ausgehen, daß jedes Objekt vom Typ "Bin" eine Methode "combine" besitzt. Das gehört so zu den grundlegenden OOP-Mechanismen, daß man festlegt: Alle Objekte vom Typ X sollen diese und jene Funktionalität bereitstellen. WIE diese Funktionalität implementiert ist, ist aber dann die Aufgabe der jeweiligen Klasse.

2. Woher weiß die Methode compute, welche combine() Methode aufgerufen werden soll? Es gibt immerhin vier.
Es wird das "combine" des aktuellen Objekts aufgerufen. Die Funktion, die das aufruft, muss.. darf gar keine Kenntnis darüber haben, ob das aktuelle "Bin" jetzt ein Add ist oder ein Sub oder sonstwas. Der aufrufende Code muss nur wissen: Das "Bin"-Objekt hat die Funktion combine, die will zwei Zahlen und gibt mir eine dritte Zahl zurück.
Durch diese Abstraktion hast du die Möglichkeit, jederzeit neue Rechenoperationen hinzuzufügen, ohne irgendwas am bestehenden Code ändern zu müssen.

3. Warum ist die toString() Methode nicht in der Klasse Expr abstrakt?
Sowohl toString als auch equals stammen aus der "Object" Klasse. Diese Klasse ist die oberste aller Klassen in Java, und jede andere Klasse erbt entweder direkt oder indirekt von Object (auch wenn man nicht direkt class Expr extends Object schreibt, erbt sie trotzdem davon)
Theoretisch könnte toString auch abstrakt sein, allerdings würde man dann wirklich jede Klasse dazu verpflichten, toString zu implementieren, was aber nicht bei allen Klassen Sinn macht.

4. Warum hat die Methode equals() als Parameter Object und nicht Expr? Das wäre doch geschickter, weil dann müsste ich nicht mehr überprüfen,ob der Parameter eine Instanz vom Typ Expr ist.
Könnte man theoretisch tun. Dann hätte man aber nur die Möglichkeit, ein "Expr" mit einer anderen "Expr" zu vergleichen. Man muss also vorher schon etwas über ein Objekt wissen, um es mit einem anderen vergleichen zu können. In Java will man aber auch, wenn man nur eine Liste von Objekten hat, von denen man überhaupt nicht weiß, zu welcher Klasse die überhaupt gehören, diese miteinander vergleichen können. Der Aufwand, den man beim Implementieren von "equals" hat, spart man sich dafür dann beim Aufrufen von equals.
 

Meniskusschaden

Top Contributor
1. Also, weil in der Bin Klasse die Methode combine abstrakt ist, weiß die Klasse Bin, dass es in der Unterklasse eine Methode combine gibt, die nicht abstrakt ist?
Ja, das kann man so sagen. Es könnten zwar noch Unterklassen von Bin existieren, die keine combine-Methode haben, das wären dann aber ebenfalls abstrakte Klassen, so dass man dafür keine Objekte erzeugen könnte.
2. Woher weiß die Methode compute, welche combine() Methode aufgerufen werden soll? Es gibt immerhin vier.
Sehen wir uns einmal folgendes Beispiel an:
Java:
Bin mulExpression = new Mul(drei, divExpression);
mulExpression.compute();
In der ersten Zeile wird einer Bin-Variablen ein Mul-Objekt zugewiesen. In der zweiten Zeile wird die Bin-Variable benutzt. Der Compiler kann zu diesem Zeitpunkt nicht wissen, von welchem Typ dass über die Bin-Variable referenzierte Objekt ist (Da das Beispiel so trivial ist, könnte man hier irrtümlich annehmen, dass er es doch weiß. Man kann sich aber komplexere Fälle vorstellen, wo die Bin-Variable abhängig von Benutzereingaben oder Zufallsereignissen gefüllt wird.). Der Compiler weiß nur, daß es sich um einen Bin-Nachfahren handelt, der somit über eine combine-Methode verfügt und dass demzufolge nichts gegen eine Compilierung spricht. Zum Zeitpunkt der Programmausführung, weiß die JVM aber sehr wohl, dass es sich um ein Mul-Objekt handelt. Man muß sich bewußt machen, dass die Objekte zum Compilierungszeitpunkt noch nicht existieren. Der Compiler kann sich nur auf die deklarierten Typen stützen (hier Bin). Zum Ausführungszeitpunkt existiert das Objekt jedoch und enthält Informationen über seinen eigenen Typen, so daß die JVM weiß, um welchen konkreten Typen es sich handelt (hiier die Klasse Mul). Deshalb kann die JVM entscheiden, dass die Methode aus der Klasse Mul richtig ist.
3. Warum ist die toString() Methode nicht in der Klasse Expr abstrakt?
Die oberste Klasse der gesamten Klassenhierarchie ist die Klasse Object. Alle anderen Klassen sind (evtl. über mehrere Ebenen) Unterklassen von Object. Das gilt auch dann, wenn sie nicht ausdrücklich von Object abgeleitet werden. Die Klassendeklarationpublic class MeineKlasse {...}ist also dasselbe wiepublic class MeineKlasse extends Object{...}. Nun gibt es in der Klasse Object bereits ein paar Methoden. Da Object Oberklasse aller anderen Klassen ist, werden diese Methoden auch an alle Klassen vererbt. toString() ist eine solche Methode. Dadurch ist sicher gestellt, dass für sämtliche Objekte die toString-Methode existiert und aufgerufen werden kann.
4. Warum hat die Methode equals() als Parameter Object und nicht Expr? Das wäre doch geschickter, weil dann müsste ich nicht mehr überprüfen,ob der Parameter eine Instanz vom Typ Expr ist.
wie bei Punkt 3 ist auch equals eine Methode, die bereits in der Klasse Object definiert wurde und zwar mit einem Parameter, der ebenfalls vom Typ Object ist. Das ist ganz praktisch, weil man dadurch beliebige Objekte miteinander vergleichen kann:
Java:
public class EqualsTest {
    public static void main(String[] args) {
        Object object1 = new String("Bin ich etwa Pi?");
        Object object2 = new Double(3.1415);
        System.out.println(object1.equals(object2));
    }
}
Wenn man eine eigene Klasse programmiert muß man entscheiden, ob der aus Object geerbte Vergleich sinnvolle Ergebnisse liefert. Falls nicht, muß man die equals-Methode überschreiben und mit eigener Logik versehen.
 

JavaIsTheBest

Bekanntes Mitglied
Dann hätte ich noch weitere Fragen.

1. Wennn es in der abstrakten Oberklasse eine abstrakte Methode gibt, muss dann direkt in der Unterklasse die Methode implementiert werden oder kann die Implementierung der Methode erst zwei Unterklassen drunter gemacht werden?
2. Wäre es in diesem Beispiel sinnvoll der Methode equals vom Typ Expr als Parameter zu geben? So hätte ich es gemacht.
 

mrBrown

Super-Moderator
Mitarbeiter
1.
Kann in einer beliebigen Unterklasse gemacht werden. Die Klassen, in denen es nicht implementiert wird, müssen aber jeweils abstract sein und die Methoden auch.

2.
Nein, ginge auch nicht, da die Methodensignatur aus Object kommt, sie kennt also nur Object ;)
Weiter oben gibts dazu ne weitere Erklärung:
Könnte man theoretisch tun. Dann hätte man aber nur die Möglichkeit, ein "Expr" mit einer anderen "Expr" zu vergleichen. Man muss also vorher schon etwas über ein Objekt wissen, um es mit einem anderen vergleichen zu können. In Java will man aber auch, wenn man nur eine Liste von Objekten hat, von denen man überhaupt nicht weiß, zu welcher Klasse die überhaupt gehören, diese miteinander vergleichen können. Der Aufwand, den man beim Implementieren von "equals" hat, spart man sich dafür dann beim Aufrufen von equals.
 

JavaIsTheBest

Bekanntes Mitglied
Die Metode equals kann man doch überladen.
In unserem Beispiel will man doch nur wissen, ob der Parameter vom Typ Expr ist und nicht vom Typ Auto oder ähnliches.
Deswegen würde ich, bezogen auf die Aufgabe anstatt Object schon Expr nehmen.

Sorry, dass ich es nicht verstehe.
 
K

kneitzel

Gast
Einfach mal ein paar Beispiele:
Code:
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.nio.charset.Charset;
import java.util.Properties;
import java.util.Scanner;

public class Test  {
    private int x;

    public int getX() { return x; }
    public void setX(int x) { this.x = x; }

    @Override public boolean equals(Object other) {
        System.out.println("Object version!");
        return false;
    }

    public boolean equals(Test other) {
        System.out.println("Test version!");
        return false;
    }

    public static void main (String[] args) {
        Test t1 = new Test();
        Test t2 = new Test();
        Object o1 = new Test();
        Object o2 = new Test();

        t1.equals(t2); // Test version
        t1.equals(o2); // object version
        o1.equals(t2); // object version
        o1.equals(o2); // object version
    }
}

Code:
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.nio.charset.Charset;
import java.util.Properties;
import java.util.Scanner;

public class Test  {
    private int x;

    public int getX() { return x; }
    public void setX(int x) { this.x = x; }
    
    public boolean equals(Test other) {
        System.out.println("Test version!");
        return false;
    }

    public static void main (String[] args) {
        Test t1 = new Test();
        Test t2 = new Test();
        Object o1 = new Test();
        Object o2 = new Test();

        t1.equals(t2); // Test version
        t1.equals(o2); // Object.equals - no output!
        o1.equals(t2); // Object.equals - no output!
        o1.equals(o2); // Object.equals - no output!
    }
}

Code:
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.nio.charset.Charset;
import java.util.Properties;
import java.util.Scanner;

public class Test  {
    private int x;

    public int getX() { return x; }
    public void setX(int x) { this.x = x; }

    @Override public boolean equals(Object other) {
        System.out.println("correct call!");
        return false;
    }

    public static void main (String[] args) {
        Test t1 = new Test();
        Test t2 = new Test();
        Object o1 = new Test();
        Object o2 = new Test();

        t1.equals(t2); // correct call!
        t1.equals(o2); // correct call!
        o1.equals(t2); // correct call!
        o1.equals(o2); // correct call!
    }
}

Also was will ich mit diesen einfachen Beispielen zeigen?
Wenn Du eine spezifische Version schreibst, dann kann es passieren, dass diese eben nicht aufgerufen wird sondern du die original equals Funktion aus Object (oder eben die equals Funktion der Oberklasse) bekommst.

Wenn Du aber eine eigene equals Funktion schreibst, dann willst Du auch, dass diese genutzt wird. Daher die 3te und letzte Version, bei der bei allen Aufrufen die geschriebene equals Methode aufgerufen wurde.
 

Neue Themen


Oben