Prof: Are you finding some of this interface stuff a bit familiar? Does it remind you of something we were working with all the way back at the beginning?
Student: Yeah… when I was writing something like
isPng.and(isBig) it felt very similar to writing tree.beside(sun). The Pict library we have been using, that uses interfaces?Prof: It sure does! You’re right that it is similar, here is an example of using
Pict like you just mentioned.Student: I see, so when I use
dot.beside(square), that is using a default method inside of the Pict interface?Prof: Yep.
Student: Ohhh, and there must be a record called
Beside that implements Pict. It’s all coming together now.Prof: Here, I’ll write out a section of the Pict library so we can talk through it. I trimmed it down a little; the whole thing is in
Pict.java.interface Pict {
int width();
int height();
default Pict beside(Pict other) { return new Beside(this, other); }
default Pict above(Pict other) { return new Above(this, other); }
// ... more default methods ...
void draw(Graphics2D g, int x, int y);
}
record Circle(int radius, Color color) implements Pict {
// ... plus constructors that accept a color name like "red" ...
public int width() { return 2 * this.radius; }
public int height() { return 2 * this.radius; }
public void draw(Graphics2D g, int x, int y) {
g.setColor(this.color);
g.fillOval(x, y, 2 * this.radius, 2 * this.radius);
}
}
record Beside(Pict left, Pict right) implements Pict {
public int width() { return this.left.width() + this.right.width(); }
public int height() { return Math.max(this.left.height(), this.right.height()); }
public void draw(Graphics2D g, int x, int y) {
int centerY = this.height() / 2;
this.left.draw(g, x, y + centerY - this.left.height() / 2);
this.right.draw(g, x + this.left.width(), y + centerY - this.right.height() / 2);
}
}Student: Ok, so
Pict is an interface. And you gave it a width and height method. So all cases where we implement Pict will have a width and height as well.Prof: Correct, what else do you notice?
Student: You used
default just like we did with ImageQuery, but instead of using it for And, Not, Or, it’s being used for beside and above. Although I am a bit confused about the Graphics2D part? What is a Graphics2D? What are all these lines that say g mean?Prof: For now let’s just treat
Graphics2D as our way of actually drawing pixels on the screen. Pict describes what we want to draw, and Graphics2D is part of a library that handles the details of actually drawing it.Student: So you are hiding things from me again?
Prof: Funnily enough, the creators of
Graphics2D actually hid all this from me! When I wrote Pict, I didn’t have to think too much about how Graphics2D works. It abstracted away those details so I could focus on implementing all the shapes and relationships that I wanted Pict to have. Programmers rely on the work of others, especially when working on very complicated projects. We have also been using assertEquals() the same way, as code someone else wrote that we trust to do its job.Student: This is a lot to keep track of.
Prof: Let’s just focus on
Pict a bit more, and just treat the Graphics2D g section of the draw method as a canvas with a paint brush: when you hand it something, it draws it for you. We only use it inside draw, and only the simple records like Circle call Graphics2D methods directly. The records are what we are focusing on. They hold the data saying “a circle with a given radius and color” or “a circle, then a square beside it”. Pict is the type that lets all of them be used interchangeably. Notice that Beside holds two Picts, in the same way And held two ImageQuerys. The drawing part is just what allows us to see and verify the result visually, but all the data is defined by us.Practice
Look at the
draw method of Circle and the draw method of Beside above. Which statement is true?Beside.drawpaints pixels by callingg.fillOvalandg.fillRectdirectly.Besidenever calls aGraphics2Dmethod itself. It only callsdrawonleftandright.Circle.drawcallsGraphics2Dmethods to paint itself, whileBeside.drawhands the samegto thedrawof its two parts, at different positions.- A
Circlestores its own pixels, sodrawjust copies them ontog.Circleholds only a radius and a color. The record is data, anddrawis where it gets painted. Besidedoes not need adrawmethod, because thePictinterface supplies one.Every record that implementsPictmust supplydraw, because the interface declares it with no default.
Above is the vertical version of Beside. This is the draw method of Above from the Pict library, with one piece missing. The top picture is drawn at the top, so the bottom picture must start just below it. Which expression belongs in place of /* ??? */, the y argument of the second draw call?
record Above(Pict top, Pict bottom) implements Pict {
public int width() { return Math.max(this.top.width(), this.bottom.width()); }
public int height() { return this.top.height() + this.bottom.height(); }
public void draw(Graphics2D g, int x, int y) {
int centerX = this.width() / 2;
this.top.draw(g, x + centerX - this.top.width() / 2, y);
this.bottom.draw(g, x + centerX - this.bottom.width() / 2, /* ??? */);
}
}yThat is the sameyas the top picture, so the bottom picture would be drawn on top of it.this.top.height()That ignoresy, so the pictures would only line up when theAboveis drawn at the very top of the canvas.y + this.top.height()The bottom picture starts where the top one ends: the startingy, plus the height of the top picture.y + this.bottom.height()That moves down by the height of the bottom picture itself, which leaves a gap the size of the bottom picture, and the top picture’s height never counts.