Aufgabe 5.1 (Laboraufgabe, Nocheinmal ein wenig Graphik) Lösung:

Werbung
Übungen zu „Objektorientierte Programmierung in Java“
PD Dr. Wolfgang Goerigk
Sommersemester 2009
Musterlösungen
Serie 5
Aufgabe 5.1 (Laboraufgabe, Nocheinmal ein wenig Graphik)
Die Klasse java.awt.Container (und damit auch die Klasse java.applet.Applet) enthält eine
Methode void paint (Graphics g), die vom System immer dann aufgerufen wird, wenn Teile oder
die ganze Grafik des Containers neu gezeichnet werden müssen (z.B. nach einem Verdecken des Fensters).
Ändern Sie Ihr Programm aus Aufgabe 4.2 derart, dass Sie die Methode paint überschreiben und Ihre
Grafikausgaben dort programmieren.
Beobachten Sie (etwa indem Sie das Applet-Fenster verdecken und wieder in den Vordergrund bringen)
den Unterschied zu Ihrem Programm aus Aufgabe 4.2.
import java.applet.Applet;
import java.awt.*;
public class Aufgabe_5_1 extends Applet {
public void init () {
setSize(400, 400); setVisible(true);
}
public void paint (Graphics g) {
/**
* Hier folgt Ihr Programmcode
*/
}
}
Lösung:
Statt in der Methode start(), die jeweils nur einmal beim Starten des Applets ausgeführt wird,
schreiben wir den Code für die Graphicausgabe nun in die Methode paint(Graphics g), die immer
dann ausgeführt wird, wenn das System das Fenster neu zeichnet. Der Parameter g (die eigentliche GrafikFläche) wird dieser Methode bereits vom System übergeben:
import java.applet.Applet;
import java.awt.*;
public class Aufgabe_5_1 extends Applet {
public void init () {
this.setSize(400, 400);
this.setVisible(true);
}
public void paint (Graphics g) {
GraphicsCircle gc = new GraphicsCircle(100, 100, 80);
gc.draw(g);
int side = (int)(gc.umfang()/4);
g.setColor(Color.red);
g.drawRect(gc.x-side/2, gc.y-side/2, side, side);
}
}
Aufgabe 5.2 (Laboraufgabe, Ein kleiner Grafikeditor)
Definieren Sie Klassen GraphicsCircle und GraphicsRectangle mit jeweils (int x, int y)
als Mittelpunkt und Radius (int r) für Kreise bzw. Breite und Höhe (int width, int height)
für Rechtecke, ähnlich wie in der Vorlesung. Die Klassen sollen jeweils eine Methode void draw
(Graphics g) enthalten, die die Instanz auf die Graphik g zeichnet, und eine gemeinsame Superklasse
GrElement haben (ohne Attribute und mit einer Dummy-Methode void draw (Graphics g) {}
mit leerem Rumpf).
Das Package java.util enthält eine Klasse Vector (Listen mit wahlfreiem Zugriff und unbeschränkter
Länge). Die Deklaration
public Vector <GrElement> elements = new Vector <GrElement>();
deklariert eine public Variable elements vom Typ Vector <GrElement> und initialisiert sie mit
einem neuen leeren Vektor. Vector <GraphicsElement> bezeichnet den Typ der VectorInstanzen, die nur Elemente vom Typ GraphicsElement enthalten dürfen (also auch
GraphicsCircle, ...).
Definieren Sie in Ihrem Applet eine Methode void paint (Graphics g), die alle Elemente dieses
Vektors zeichnet, in dem sie ihnen die Nachricht draw(this.getGraphics()) sendet:
import java.applet.Applet;
import java.awt.*;
import java.util.*;
public class Aufgabe_5_2 extends Applet {
public Vector <GrElement> elements = new Vector <GrElement>();
public void init () {
setSize(400, 400); setVisible(true);
mymain();
}
public void paint (Graphics g) {
/**
* Hier der Code, der alle Elemente aus
* elements zeichnet
*/
}
public mymain () {
/**
* Hier Ihr Code, der Instanzen erzeugt
* und elements hinzufügt
*/
repaint();
}
}
Hinzu kommen die Klassen für die graphischen Elemente. Instanziieren Sie nun einige graphische Elemente
e (Kreise, Rechtecke) und tragen Sie diese durch elements.add(e) in den Vektor ein. Nach jeder
Änderung des Vektors sollte ein repaint() ausgeführt werden, um die dargestellte Grafik zu
aktualisieren. Wenn Sie nun Ihr Applet-Fenster verdecken, wieder sichtbar machen u.ä., werden die
Elemente in elements immer wieder neu gezeichnet.
Hinweise:
•
•
•
•
Diese Aufgabe ist ein wenig aufwändiger und Sie können sich die Labore in dieser und der
kommenden Woche (KW 22) dafür Zeit nehmen.
In mymain() können Sie z.B. einen kleinen Dialog programmieren, der etwa Kommandos der
Form
c 100 100 50
(Kreis, Mittelpunkt = (100,100), Radius = 50)
r 200 200 100 50 (Rechteck, Mittelpunkt = (200,200), Breite = 100, Höhe = 50)
von der Standardeingabe (java.util.Scanner(System.in)) liest und die entsprechenden
Elemente instanziiert und dem Vektor elements hinzufügt.
Wer Lust hat, kann versuchen, Elemente aus Elements wieder zu löschen, mit Rand- und Füllfarben
spielen u.ä..
Es kann sein, dass Sie in Ihrem Eclipse-Projekt (rechte Maustaste auf das Projekt, Properties, Java
Compiler) den Compiler Compliance Level auf 5.0 (Java 1.5.0) umstellen müssen, wenn Sie
syntaktische Probleme mit Vektoren haben.
Lösung:
Wir definieren zunächst die Klassen für die graphischen Elemente und eine gemeinsame abstrakte
Superklasse GrElement, die die gemeinsamen Attribute x,y (Mittelpunkt) und c,f (Farbe und
Füllfarbe) sowie eine abstrakte Methode void draw (Graphics g) deklarieren; als konkrete
graphische Elemente definieren wir Kreise (GrCircle), Ellipsen (GrEllipse) und Rechtecke
(GrRectangle) jeweils als Subklassen von GrElement:
import java.awt.*;
public abstract class GrElement {
int x,y;
Color c,f;
public abstract void draw (Graphics g);
}
import java.awt.*;
public class GrCircle extends GrElement {
int r; // Radius
public GrCircle (int x, int y, int r, Color c, Color f) {
this.x = x; this.y = y; this.r = r; this.c = c; this.f = f;
}
public double umfang() {
return 2 * Math.PI * r;
}
public void draw (Graphics g) {
g.setColor(f); g.fillOval(x-r, y-r, 2*r, 2*r);
g.setColor(c); g.drawOval(x-r, y-r, 2*r, 2*r);
}
}
import java.awt.*;
class GrEllipse extends GrElement {
int r1,r2; // Horizontaler (r1) und vertikaler (r2) Radius
public GrEllipse (int x, int y, int r1, int r2, Color c, Color f) {
this.x = x; this.y = y; this.r1 = r1; this.r2 = r2;
this.c = c; this.f = f;
}
public void draw (Graphics g) {
g.setColor(f);
g.fillOval(x-r1, y-r2, 2*r1, 2*r2);
g.setColor(c);
g.drawOval(x-r1, y-r2, 2*r1, 2*r2);
}
}
import java.awt.*;
class GrRectangle extends GrElement {
int w, h; // Breite (w) und Höhe (h)
public GrRectangle (int x, int y, int w, int h, Color c, Color f) {
this.x = x; this.y = y; this.w = w; this.h = h;
this.c = c; this.f = f;
}
public void draw (Graphics g) {
g.setColor(f);
g.fillRect(x-w/2, y-h/2, w, h);
g.setColor(c);
g.drawRect(x-w/2, y-h/2, w, h);
}
}
Die Applet-Klasse: Die Methode void applet-main() ist die Hauptmethode, die nach der Initialisierung aus
der Methode init() aufgerufen wird. Sie implementiert eine kleine Schleife, die Kommandos einliest und
entsprechende Instanzen grafischer Elemente in den Vektor elements einträgt. Syntax der
Kommandosprache: sie Methode void usage().
Die Methode GrElement find(int x, int y) sucht in elements nach dem letzten Vorkommen eines Elements
an der Mittelpunkt-Koordinate (x,y), damit diese aus elements gelöscht werden kann.
import
import
import
import
java.awt.*;
java.applet.Applet;
java.awt.*;
java.util.*;
public class Aufgabe_5_2 extends Applet {
public Vector<GrElement> elements = new Vector<GrElement>();
Color c, f;
public void init() {
setSize(400, 400); setVisible(true);
applet_main();
}
public void paint(Graphics g) {
for (int i = 0; i < elements.size(); i++) {
elements.get(i).draw(g);
}
}
/**
* Suche das letzte GrElement in elements mit Koordinate (x,y)
*/
public GrElement find(int x, int y) {
GrElement result = null;
for (int i = 0; i < elements.size(); i++) {
GrElement g = elements.get(i);
if (g.x == x && g.y == y)
result = g;
}
return result;
}
/**
* Schreibe usage-Information
*/
public void usage() {
System.out.println("***
System.out.println("***
System.out.println("***
System.out.println("***
System.out.println("***
System.out.println("***
System.out.println("***
System.out.println("***
}
auf die Konsole
c x y
r x y
e x y
d x y
col r
fil r
quit
u
r
: Kreis mit Radius r");
w h
: Rechteck wxh");
r1 r2 : Ellipse mit Radien r1, r2");
: Lösche letztes Elt. an x,y");
g b
: Zeichenfarbe");
g b
: Füllfarbe");
: Beenden");
: Diese Usage-Information");
/**
* Haupteingabeschleife
*/
public void applet_main() {
Scanner sc = new Scanner(System.in);
String s;
System.out.print("Bitte Eingabe (u: Usage): ");
while (!(s = sc.next()).equals("quit")) {
if (s.equals("u"))
usage();
else {
int x = sc.nextInt(); int y = sc.nextInt();
if (s.equals("c")) {
int r = sc.nextInt();
elements.add(new GrCircle(x, y, r, c, f));
} else if (s.equals("r")) {
int width = sc.nextInt();
int height = sc.nextInt();
elements.add(
new GrRectangle(x, y, width, height, c, f));
} else if (s.equals("e")) {
int r1 = sc.nextInt();
int r2 = sc.nextInt();
elements.add(new GrEllipse(x, y, r1, r2, c, f));
} else if (s.equals("d")) {
elements.remove(find(x, y));
} else if (s.equals("col")) {
int b = sc.nextInt();
c = new Color(x, y, b);
} else if (s.equals("fil")) {
int b = sc.nextInt();
f = new Color(x, y, b);
} else if (s.equals("m")) {
int nx = sc.nextInt();
int ny = sc.nextInt();
GrElement g = find(x, y);
g.x = nx; g.y = ny;
} else
System.out.println
("Unknown element \'" + s + "\'");
}
repaint();
sc.nextLine();
System.out.print("Bitte Eingabe (u: Usage): ");
}
System.out.println("Dialog beendet. Applet wird beendet!");
stop();
destroy();
System.exit(0);
}
}
Aufgabe 5.3 (Finalisieren von Klassen)
1. Gegeben sei das folgende Java-Programm:
class A {
int x = 1, y = 1;
int m () {
return f();
}
int f() {
return x;
}
}
class B extends A {
int x = 2;
int f () {
return x;
}
}
class C extends B {
int x = 3;
double z = 1.0;
C c = new C();
int f () {
return x;
}
static void main (String[] args) {
System.out.println( c.m() );
}
}
class D extends B {
int x = 4;
}
1. Zeichnen Sie den Vererbungsgraphen des Programms, etwa in Form eines UML-Klassendiagramms,
wie wir es in der Vorlesung verwendet haben.
2. Finalisieren Sie die Klassen: Welche Attribute haben die Klassen A, B, C und D? Welche Methoden
haben die Klassen A, B, C und D. Geben Sie die vier finalisierten Klassendefinitionen an (die alle ihre
Attribute und Methoden lokal deklarieren).
3. Welcher Wert wird von der main-Methode der Klasse C ausgegeben?
Hinweise:
Attribute (Instanzvariablen) werden statisch gebunden; ein return x liefert also den Wert derjenigen
Instanzvariablen x der Klasse, in der es syntaktisch auftritt. Machen Sie sich also insbesondere klar, welche
der Instanzvariablen x von der jeweiligen return x –Anweisung referenziert wird.
Lösung:
1.
Abbildung 1: Vererbungsgraph (originales Programm)
Abbildung 2: Vererbungsgraph des finalisierten Programms
2. Vererbungsgraph des finalisierten Programms siehe Abbildung 2. Das modifierte (finalisierte)
Programm hat dann die Form
class A {
int x = 1;
int y = 1;
int m () {
return f();
}
int f() {
return x;
}
}
class B {
int x
int y
int x
int m
= 1;
= 1;
= 2;
() {
return f();
}
int f () {
return x;
}
class C {
int x = 1;
int y = 1;
int x = 2;
int x = 3;
double z = 1.0;
C c = new C();
int m () {
return f();
}
int f () {
return x;
}
static void main (String[] args) {
System.out.println( c.m() );
}
}
class D {
int x
int y
int x
int x
int m
= 1;
= 1;
= 2;
= 4;
() {
return f();
}
int f () {
return x;
}
}
3. Die main-Methode aus C sendet die Nachricht m() an eine Instanz der Klasse C. Die Methode m()
in C sendet die Nachricht f() an dieselbe Instanz der Klasse C; es wird also die Methode f() der
Klasse C ausgeführt. Diese ist lokal in C definiert und liefert das ebenfalls lokal in C definierte x,
also den Wert 3.
Beachten Sie, dass die Instanzvariablen und Methoden farblich gekennzeichnet sind entsprechend der
Klassen, aus denen sie ursprünglich stammen. Auch die angewandten Vorkommen der Instanzvariablen x in
den Methodenrümpfen der Methoden f() können ebenfalls eindeutig zugeordnet werden (statische
Bindung für Instanzvariablen).
Für die Aufrufe der Methoden f() und m() geht dies nicht, denn die Zuordnung geschieht zur Laufzeit in
Abhängigkeit von der Klasse des Objektes, das die Nachricht empfängt (late binding, dynamische Bindung
für Methoden).
Herunterladen