Prednáška č. 3
- Opakovanie: triedy a objekty
- Dedenie
- Prekrývanie metód a polymorfizmus
- Abstraktné triedy a metódy
- Hierarchia tried a trieda Object
- Rozhrania
- Prehľad niektorých modifikátorov tried, premenných a metód
- Aritmetický strom s využitím dedenia
- Odkazy
Opakovanie: triedy a objekty
- Objekt je predovšetkým súborom rôznych dát a metód na manipuláciu s nimi. Na objekty sa odkazuje pomocou ich identifikátorov, ktoré sú referenciami na ich „pamäťové adresy”.
- Každý objekt je inštanciou nejakej triedy (angl. class). Triedu možno chápať ako „vzor”, podľa ktorého sa vytvárajú objekty. Trieda tiež reprezentuje typ jej objektov.
- Trieda sa teda podobá na
structz jazykov C a C++ v tom, že môže združovať niekoľko hodnôt rôznych typov. Ide však o omnoho bohatší koncept – môže obsahovať metódy (funkcie) na prácu s dátami uloženými v inštancii danej triedy, umožňuje nastaviť viditeľnosť jednotlivých premenných a metód pomocou modifikátorov, atď. - Konštruktory sú špeciálne kusy kódu slúžiace na vytvorenie objektu (inštancie triedy).
- Dôležitým metodickým princípom objektovo orientovaného programovania
je zapuzdrenie (angl. encapsulation): spojenie dát a súvisiaceho
kódu do koherentného logického celku.
- Trieda väčšinou navonok ukazuje iba vhodne zvolenú časť metód.
- Premenné a pomocné metódy sú skryté – prípadné zmeny vnútornej implementácie triedy sa tak nemusia prejaviť v kóde pracujúcom s touto triedu.
Dedenie
Trieda môže byť podtriedou inej triedy. Napríklad trieda Pes môže byť
podtriedou všeobecnejšej triedy Zviera: každý objekt, ktorý je
inštanciou triedy Pes je potom súčasne aj inštanciou triedy Zviera.
Tento vzťah medzi triedami vyjadrujeme kľúčovým slovom extends v
definícii triedy.
class Pes extends Zviera {
// ...
}
Hovoríme tiež, že trieda Pes dedí od triedy Zviera, alebo že
trieda Pes triedu Zviera rozširuje. V prípade vhodne zvolených
prístupových modifikátorov (detaily neskôr) totiž inštancia triedy
Pes zdedí metódy a premenné (nie konštruktory) definované v triede
Zviera a tie sa potom správajú tak, ako keby boli priamo definované aj
v triede Pes.
Dedenie umožňuje vyhnúť sa nutnosti písať podobný kód viackrát. Namiesto implementácie podobných metód v niekoľkých triedach možno vytvoriť nadtriedu týchto tried a spoločné časti kódu presunúť tam.
Príklad
Uvažujme triedy reprezentujúce rôzne geometrické útvary v rovine a
poskytujúce metódu move realizujúcu posunutie.
public class Rectangle {
private double x, y; // Suradnice laveho horneho rohu
private double width, height; // Vyska a sirka obdlznika
public double getX() {
return x;
}
public void setX(double x) {
this.x = x;
}
public double getY() {
return y;
}
public void setY(double y) {
this.y = y;
}
public double getWidth() {
return width;
}
public void setWidth(double width) {
this.width = width;
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
public void move(double deltaX, double deltaY) {
x += deltaX;
y += deltaY;
}
}
public class Circle {
private double x, y; // Suradnice stredu
private double radius; // Polomer kruznice
public double getX() {
return x;
}
public void setX(double x) {
this.x = x;
}
public double getY() {
return y;
}
public void setY(double y) {
this.y = y;
}
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
public void move(double deltaX, double deltaY) {
x += deltaX;
y += deltaY;
}
}
Vidíme, že obidve triedy obsahujú pomerne veľa spoločného kódu, ktorý
nie je nijak špecifický pre obdĺžniky alebo kruhy, ale naopak môže byť v
nezmenenej podobe použitý aj pri ďalších rovinných geometrických
útvaroch. Využijeme teda mechanizmus dedenia – spoločné premenné a
metódy tried Rectangle a Circle presunieme do ich spoločnej
nadtriedy Shape.
public class Shape {
private double x, y; // Suradnice nejakeho vyznacneho bodu geometrickeho utvaru
public double getX() {
return x;
}
public void setX(double x) {
this.x = x;
}
public double getY() {
return y;
}
public void setY(double y) {
this.y = y;
}
public void move(double deltaX, double deltaY) {
x += deltaX;
y += deltaY;
}
}
public class Rectangle extends Shape {
private double width, height;
public double getWidth() {
return width;
}
public void setWidth(double width) {
this.width = width;
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
// Pripadne dalsie metody pre obdlznik
}
public class Circle extends Shape {
private double radius;
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
// Pripadne dalsie metody pre kruznicu
}
V rámci triedy možno používať aj verejné metódy a premenné nadtriedy,
ako keby boli jej vlastné – a často aj metódy a premenné s inými
modifikátormi rôznymi od private (o tom neskôr). V metódach a
konštruktoroch tried Rectangle a Circle tak napríklad môžeme
používať metódy getX, setX, getY, setY a move.
public class Rectangle extends Shape {
// ...
public Rectangle(double x, double y, double width, double height) {
setX(x);
setY(y);
setWidth(width);
setHeight(height);
}
// ...
}
public class Circle extends Shape {
// ...
public Circle(double x, double y, double radius) {
setX(x);
setY(y);
setRadius(radius);
}
@Override // Vyznam tejto znacky si vysvetlime o chvilu
public String toString() {
return "Stred: [" + getX() + "," + getY() + "]; Polomer: " + getRadius() + ".";
}
// ...
}
- Inštanciu
ctriedyCircleteraz môžeme nielen vypísať na konzolu cezSystem.out.println(c)(použije sa metódatoString), ale môžeme pre ňu zavolať aj ľubovoľnú metódu triedyShape, napríkladc.move(1, 1)aleboc.setX(2).
Dedenie a typy
- Typom objektu je trieda určená konštruktorom, ktorým bol objekt
vytvorený – napríklad volanie konštruktora triedy
Circlemá za následok vytvorenie objektu typuCircle. - Premenná, ktorej typom je trieda
Tvšak môže obsahovať aj referenciu na objekt, ktorého typom je podtrieda triedyT. Napríklad premenná typuShapetak môže obsahovať referenciu na objekt triedyShapealebo jej ľubovoľnej podtriedy. Vždy teda treba rozlišovať medzi typom referencie (premennej obsahujúcej referenciu na objekt) a samotným typom objektu.
Circle circle = new Circle(0, 0, 5);
Shape shape = circle; // toto je korektne priradenie
// circle = shape; // toto neskompiluje, kedze shape nemusi byt kruznica
circle = (Circle) shape; // po pretypovani to uz skompilovat pojde; ak shape nie je instanciou Circle alebo null, vyhodi sa vynimka
- Istejší prístup je pri priraďovaní premennej typu
Shapedo premennej typuCirclenajprv overiť, či premenná typuShapeobsahuje referenciu na inštanciu triedyCircle. Na to slúži operátorinstanceof. Platí pritom, že ak je objekt inštanciou nejakej triedy, je súčasne aj inštanciou ľubovoľnej jej nadtriedy (samotný typ objektu je však daný iba najnižšou triedou v tomto usporiadaní). Napríklad podmienkashape instanceof Shapeje splnená kedykoľvek je splnená podmienkashape instanceof Circle. Pre ľubovoľnú trieduTriedamá výraznull instanceof Triedavždy hodnotufalse(a rovnako pre premennú obsahujúcunull).
if (shape instanceof Circle) {
circle = (Circle) shape;
}
- Keďže teda môžeme inštancie tried
RectanglealeboCirclepovažovať aj za inštancie ich spoločnej nadtriedyShape, môžeme rôzne typy útvarov spracúvať tým istým kódom. Napríklad nasledujúca metóda dostane pole útvarov a posunie každý z nich o daný vektor (deltaX,deltaY).
public static void moveAll(Shape[] shapes, double deltaX, double deltaY) {
for (Shape shape : shapes) {
shape.move(deltaX, deltaY);
}
}
- Cvičenie: čo vypíše nasledujúci kód?
Shape[] shapes = new Shape[2];
shapes[0] = new Rectangle(0, 0, 1, 2);
shapes[1] = new Circle(0, 0, 1);
moveAll(shapes, 2, 2);
for (Shape x : shapes) {
if (x instanceof Circle) {
System.out.println("Je to kruh.");
Circle c = (Circle) x; // O chvilu uvidime, ze toto pretypovanie kvoli nasledujucemu riadku nie je nutne
System.out.println(c);
}
if (x instanceof Shape) {
System.out.println("Je to utvar.");
}
}
Dedenie a konštruktory
- Typickou úlohou konštruktora je správne nainicializovať objekt.
- Pri dedení si väčšinou každá trieda inicializuje „svoje” premenné.
- Napríklad krajší spôsob realizácie konštruktorov pre geometrické
útvary je nasledovný:
Shapeinicializujexay, pričom napríkladCirclenechá inicializáciuxaynaShapea inicializuje už lenradius. - Prvý príkaz konštruktora môže pozostávať z volania konštruktora
predka pomocou kľúčového slova
super(z angl. superclass, t. j. nadtrieda).
public class Shape {
// ...
public Shape(double x, double y) {
this.x = x; // alebo setX(x);
this.y = y; // alebo setY(y);
}
// ...
}
public class Rectangle extends Shape {
// ...
public Rectangle(double x, double y, double width, double height) {
super(x, y);
this.width = width; // alebo setWidth(width);
this.height = height; // alebo setHeight(height);
}
// ...
}
public class Circle extends Shape {
// ...
public Circle(double x, double y, double radius) {
super(x, y);
this.radius = radius; // alebo setRadius(radius);
}
// ...
}
- Ak nezavoláme konštruktor nadtriedy ručne, automaticky sa zavolá
konštruktor bez parametrov, t. j.
super(). To môže pri kompilovaní vyústiť v chybu v prípade, keď nadtrieda nemá definovaný konštruktor bez parametrov (či už explicitne jeho implementáciou, alebo implicitne tým, že sa neuvedie implementácia žiadneho konštruktora nadtriedy). Napríklad v horeuvedenom príklade je teda volanie konštruktora nadtriedy nutnou podmienkou úspešného skompilovania programu. - Výnimkou je prípad, keď sa v rámci prvého príkazu volá iný
konštruktor tej istej triedy pomocou
this(...)– vtedy sa volanie konštruktora nadtriedy nechá na práve zavolaný konštruktor.
Prekrývanie metód a polymorfizmus
Podtrieda môže prekryť (angl. override) niektoré zdedené metódy, aby sa chovali inak ako v predkovi.
Uvažujme napríklad útvar Segment (úsečka), ktorý je zadaný dvoma
koncovými bodmi a v metóde move treba posunúť oba. V triede Segment
je teda potrebné metódu move prekryť. Metódu move z nadtriedy
Shape pritom možno zavolať ako super.move, ale nemusí to byť v rámci
prvého príkazu prekrývajúcej metódy move a metóda nadtriedy sa tam
prípadne ani nemusí zavolať vôbec (volanie metódy super.move možno
použiť aj v iných metódach a konštruktoroch triedy Segment).
public class Segment extends Shape {
private double x2, y2;
// ...
public Segment(double x, double y, double x2, double y2) {
super(x, y);
this.x2 = x2;
this.y2 = y2;
}
@Override
public void move(double deltaX, double deltaY) {
super.move(deltaX, deltaY);
x2 += deltaX;
y2 += deltaY;
}
}
O prekrytie metódy z nadtriedy ide samozrejme iba v prípade, že má prekrývajúca metóda rovnaký názov a rovnakú postupnosť typov parametrov (t. j. rovnakú signatúru) ako metóda nadtriedy. V prípade rovnakého názvu metódy, ale rozdielnych typov parametrov ide len o preťaženie metódy, ako ho poznáme z minulej prednášky.
Návratový typ prekrývajúcej metódy sa musí buď zhodovať s návratovým typom prekrývanej metódy, alebo musí byť jeho „špecializáciou” (napr. môže ísť o podtriedu triedy, ktorá slúži ako návratový typ prekrývanej metódy). Modifikátor prístupu prekrývajúcej metódy musí byť nastavený tak, aby bola táto metóda prístupná kedykoľvek je prístupná prekrývaná metóda (ak je teda napr. prekrývaná metóda verejná, musí byť verejná aj prekrývajúca metóda). Pokiaľ tieto vlastnosti prekrývajúcej metódy nie sú splnené, program neskompiluje.
Anotácia @Override je pri prekrývaní metód nepovinná, ale odporúčaná.
Ide o informáciu pre kompilátor, ktorou sa vyjadruje snaha o prekrytie
zdedenej metódy. Ak sa v predkovi nenachádza metóda s rovnakou
signatúrou, kompilátor vyhlási chybu. Tým sa dá predísť obzvlášť
nepríjemným chybám, pri ktorých napríklad namiesto prekrytia metódy
túto metódu neúmyselne preťažíme alebo napíšeme metódu s úplne iným
názvom (napr. hashcode namiesto hashCode).
S prekrývaním metód súvisí polymorfizmus, pod ktorým sa v programovaní (hlavne pri OOP) rozumie schopnosť metód chovať sa rôzne:
- S určitou formou polymorfizmu sme sa už stretli, keď sme mali viacero metód s rovnakým menom, avšak s rôznymi typmi parametrov (tzv. preťažovanie metód, angl. overloading).
- Pri dedení sa navyše môže metóda chovať rôzne v závislosti od triedy, ku ktorej táto metóda patrí.
- To, ktorá verzia metódy sa zavolá, záleží od toho, akého typu je objekt, nie akého typu je referencia naň.
- Takto to ale funguje iba pri nestatických metódach (keďže statické
metódy príslušia samotným triedam, rozdiel medzi typom referencie a
typom inštancie tam nemožno využiť). Pri statických metódach preto
nehovoríme o ich prekrývaní, ale o ich skrývaní; nemožno vtedy ani
použiť anotáciu
@Override.
Shape s = new Segment(0, 0, 1, -5);
s.move(1, 1); // zavola prekrytu metodu z triedy Segment
s = new Circle(0, 0, 1);
s.move(1, 1); // zavola metodu z triedy Shape, lebo v Circle nie je prekryta
Shape[] shapes = new Shape[3];
//...
for(Shape x : shapes) {
x.move(deltaX, deltaY); // kazdy prvok pola sa posuva svojou metodou move, ak ju ma
}
Vo všeobecnosti sa pri volaní o.f(par1,...,parn) pre objekt o typu
T aplikuje nasledujúci princíp:
- Ak má trieda
Tsvoju implementáciu metódyfs vhodnými parametrami, vykoná sa táto verzia metódy. - V opačnom prípade sa vhodná verzia metódy
fhľadá v nadtriede triedyT, v prípade neúspechu v nadtriede nadtriedyT, atď.
Polymorfizus môže byť schovaný aj hlbšie – neprekrytá metóda z predka môže vo svojom tele volať prekryté metódy, čím sa jej správanie mení v závislosti od typu objektu.
public class SuperClass {
void f() {
System.out.println("Metoda f nadtriedy.");
}
void g() {
f();
f();
}
}
public class SubClass extends SuperClass {
@Override
void f() {
System.out.println("Metoda f podtriedy.");
}
}
SuperClass a = new SubClass();
a.g(); // vypise sa dvakrat "Metoda f podtriedy."
Zmysluplnejším príkladom takéhoto správania bude metóda
approximateArea v príklade nižšie.
Abstraktné triedy a metódy
Aby sa metóda chovala v určitej skupine tried polymorfne, musí byť definovaná v ich spoločnej nadtriede. V tejto nadtriede však nemusí existovať jej zmysluplná implementácia.
- Uvažujme napríklad metódu
area(), ktorá zráta plochu geometrického útvaru. - Pre triedy
Rectangle,Circle, resp.Segmentje implementácia takejto metódy zrejmá. Zmysluplná implementácia v ich spoločnej nadtriedeShapeby však bola prinajmenšom problematická.
Vzniknutú situáciu možno riešiť nasledovne:
- Metódu
area()v triedeShape, ako aj trieduShapesamotnú, označíme za abstraktnú modifikátoromabstract. - Abstraktná metóda pozostáva iba z hlavičky bez samotnej implementácie a je určená na prekrytie v podtriedach (musí ísť o nestatickú metódu).
- Abstraktná trieda je trieda, ktorá môže obsahovať abstraktné metódy.
Zo zrejmých dôvodov z nej nemožno priamo tvoriť inštancie –
napríklad v našom príklade by tieto inštancie „nevedeli, čo robiť”
pri volaní metódy
area()– ale za účelom volania z podtried môže obsahovať definície konštruktorov. Abstraktná trieda slúži iba na dedenie a ako taká nemôže byť typom žiadneho objektu. Stále však môže byť typom referencie na objekt. - Podtriedy abstraktnej triedy, ktoré nie sú abstraktné, musia implementovať všetky abstraktné metódy svojho predka.
Príklad:
public abstract class Shape {
// ...
public abstract double area();
public long approximateArea() {
return Math.round(area());
}
}
public class Rectangle extends Shape {
// ...
@Override
public double area() {
return width * height;
}
}
public class Circle extends Shape {
// ...
@Override
public double area() {
return Math.PI * radius * radius;
}
}
public class Segment extends Shape {
// ...
@Override
public double area() {
return 0;
}
}
Napríklad program
public static void main(String[] args) {
Shape[] shapes = new Shape[3];
shapes[0] = new Rectangle(0, 0, 1, 2);
shapes[1] = new Circle(0, 0, 1);
shapes[2] = new Segment(1, 1, 2, 2);
for (Shape x : shapes) {
System.out.println(x.area() + " " + x.approximateArea());
}
}
potom vypíše nasledujúci výstup:
2.0 2
3.141592653589793 3
0.0 0
Hierarchia tried a trieda Object
- V Jave môže každá trieda dediť iba od jednej triedy (na rozdiel od niektorých iných jazykov, kde je možné dedenie od viacerých tried).
- Dedenie je však možné „viacúrovňovo”:
class Pes extends Zviera {
}
class Civava extends Pes { // Hierarchia tried nemusi verne zodpovedat realite
}
- Všetky triedy sú automaticky potomkami triedy
Object; tá sa tiež považuje za priamu nadtriedu tried, ktoré explicitne nerozširujú žiadnu triedu. - Trieda
Objectobsahuje metódy – napríkladtoStringaleboequals– ktoré je často užitočné prekrývať. - To vysvetľuje, prečo sme pri metóde
toStringtriedyCirclepoužili anotáciuOverride: prekryli sme totiž jej definíciu z triedyObject. - Vypisovanie kružnice
Circle circlecezSystem.out.println(circle)je zas ukážkou polymorfizmu. Ide tu o použitie verzie metódyprintln, ktorá ako argument očakáva inštanciu triedyObjecta na výpis používa metódutoStringtejto inštancie. V prípade, že metóduprintlnzavoláme pre inštanciu podtriedy triedyObject, použije sa pri výpise prekrytá verzia metódytoString. - Veľmi špeciálnym druhom objektov v Jave sú polia, pričom polia typu
T(kdeTje trieda alebo primitívny typ) sa považujú za inštancie triedyT[], ktorá je podtriedou triedyObject. Aj na polia teda v princípe možno aplikovať metódutoStringtriedyObject, avšak od jej použitia nemožno očakávať žiadne rozumné správanie, keďže nebola zmysluplným spôsobom prekrytá.
Rozhrania
Rozhranie (angl. interface) je podobným konceptom ako abstraktná trieda. Existuje však medzi nimi niekoľko rozdielov, z ktorých najpodstatnejšie sú tieto:
- Rozhranie slúži predovšetkým ako zoznam abstraktných metód –
kľúčové slovo
abstracttu netreba uvádzať. Pri triedach implementujúcich rozhranie je garantované, že na prácu s nimi bude možné použiť metódy deklarované v rozhraní (odtiaľ aj termín „rozhranie”). Napríklad rozhranie pre zásobníky by mohlo deklarovať metódy akopush,popaisEmptya triedy pre zásobníky implementované pomocou polí resp. spájaných zoznamov by toto rozhranie mohli implementovať. - Naopak implementované metódy musia byť v rozhraní označené kľúčovým
slovom
default(čo sa však typicky využíva iba vo veľmi špeciálnych situáciách), prípadne musia byť statické. - Rozhranie nemôže definovať konštruktory, ani iné ako finálne premenné (t. j. konštanty).
- Kým od tried sa dedí pomocou kľúčového slova
extends, rozhrania sa implementujú pomocou kľúčového slovaimplements. Rozdiel je predovšetkým v tom, že implementovať možno aj viacero rozhraní. Jedno rozhranie môže navyše rozširovať iné (dopĺňať ho o ďalšie požadované metódy): v takom prípade používame kľúčové slovoextends. - Všetky položky v rozhraní sa chápu ako verejné (modifikátor
publicteda nie je potrebné explicitne uvádzať). - Podobne ako abstraktná trieda, môže byť aj rozhranie typom referencie.
- Hoci nejde o prekrývanie v pravom slova zmysle, aj pri
implementovaní metód z rozhraní používame anotáciu
@Override.
Príklad použitia:
public interface Movable {
void move(double deltaX, double deltaY);
}
public interface Measurable {
double area();
long approximateArea();
}
public abstract class Shape implements Movable, Measurable {
// ...
@Override
public void move(double deltaX, double deltaY) {
x += deltaX;
y += deltaY;
}
@Override
public abstract double area(); // Deklaracia tejto abstraktnej metody sa stava nepotrebnou
@Override
public long approximateArea() {
return Math.round(area());
}
// ...
}
public static void main(String[] args) {
Measurable[] elements = new Shape[3]; // Podobne ako abstraktne triedy mozu byt aj rozhrania typmi referencie
elements[0] = new Rectangle(0, 0, 1, 2);
elements[1] = new Circle(0, 0, 1);
elements[2] = new Segment(1, 1, 2, 2);
for (Measurable element : elements) {
System.out.println(element.area() + " " + element.approximateArea());
}
}
Prehľad niektorých modifikátorov tried, premenných a metód
Modifikátory prístupu:
public: triedy, rozhrania a ich súčasti prístupné odvšadiaľ.- (žiaden modifikátor): viditeľnosť len v rámci balíka (
package). protected: viditeľnosť v triede, jej podtriedach a v rámci balíka (len pre premenné, metódy a konštruktory; nedá sa aplikovať na triedu samotnú).private: viditeľnosť len v danej triede (len pre premenné, metódy a konštruktory; nedá sa aplikovať na triedu samotnú).
Iné modifikátory:
abstract: neimplementovaná metóda alebo trieda s neimplementovanými metódami.final:- Ak je trieda
final, nedá sa z nej ďalej dediť. - Ak je metóda
final, nedá sa v podtriede prekryť. - Ak je premenná alebo parameter
final, ide o „konštantu”, ktorú nemožno meniť (možno ju ale inicializovať aj za behu).
- Ak je trieda
static:- Statické premenné a metódy príslušia triede samotnej, nie jej inštanciám.
- Statické triedy vo vnútri inej triedy nie sú viazané na jej konkrétnu inštanciu (viac neskôr).
Aritmetický strom s využitím dedenia
V minulom semestri ste boli upozornení na návrhový nedostatok pri realizácii aritmetického stromu: niektoré položky uložené v štruktúrach reprezentujúcich uzly takýchto stromov sa využívali len pre určité druhy uzlov (hodnoty iba v listoch a operátory iba vo vnútorných uzloch). Tomuto sa vieme vyhnúť pomocou dedenia.
- Jednotlivé typy uzlov budú podtriedy abstraktnej triedy
Node. - Namiesto použitia príkazu
switchna typ uzla tu prekryjeme potrebné metódy, napríkladevaluate.
abstract class Node {
public abstract int evaluate();
}
abstract class NullaryNode extends Node {
}
abstract class UnaryNode extends Node {
private Node child;
public Node getChild() {
return child;
}
public UnaryNode(Node child) {
this.child = child;
}
}
abstract class BinaryNode extends Node {
private Node left;
private Node right;
public Node getLeft() {
return left;
}
public Node getRight() {
return right;
}
public BinaryNode(Node left, Node right) {
this.left = left;
this.right = right;
}
}
class Constant extends NullaryNode {
private int value;
public Constant(int value) {
this.value = value;
}
@Override
public int evaluate() {
return value;
}
@Override
public String toString() {
return Integer.toString(value);
}
}
class UnaryMinus extends UnaryNode {
public UnaryMinus(Node child){
super(child);
}
@Override
public int evaluate() {
return -getChild().evaluate();
}
@Override
public String toString() {
return "(-" + getChild().toString() + ")";
}
}
class Plus extends BinaryNode {
public Plus(Node left, Node right) {
super(left, right);
}
@Override
public int evaluate() {
return getLeft().evaluate() + getRight().evaluate();
}
@Override
public String toString() {
return "(" + getLeft().toString() + "+" + getRight().toString() + ")";
}
}
public class Expressions {
public static void main(String[] args) {
Node expr = new Plus(new UnaryMinus(new Constant(2)),
new Constant(3));
System.out.println(expr);
System.out.println(expr.evaluate());
}
}