Polygon Objects, Color, and More Constructors

Generalization and specialization named at last, then two rounds of specialising a library class: a DrawablePolygon that adds colour to java.awt.Polygon, and a RegularPolygon that computes its own vertices with trigonometry. Plus constructor chaining with this(...) and throwing an exception to reject bad arguments. Follows Think Java 2e, Chapter 17 (Advanced Topics), Sections 17.1-17.4, pp. 277-283, cross-referenced against Java SE 21 API — java.awt.Polygon.

Subject: Java · 65 slides · code lesson

Open the interactive version of this deck

What this lesson covers

The lesson, slide by slide

1. Polygon Objects, Color, and More Constructors

Title

Think Java 2e · Chapter 17 · Advanced Topics

Sections 17.1-17.4 · pp. 277-283

2. What you will be able to do

Objectives

This lesson follows Think Java 2e, Chapter 17 (Advanced Topics), Sections 17.1-17.4, pp. 277-283. Everything on these slides can be checked against those pages.

1. Distinguish generalization from specialization and give an example of each from earlier chapters.

2. Extend a library class you did not write, adding an attribute and a method.

3. Compute the vertices of a regular polygon from a number of sides and a radius.

4. Chain constructors with this(...) to give parameters default values.

5. Validate constructor arguments and throw an exception when they are invalid.

6. Explain why validation belongs in the most general constructor.

3. Retrieve before you read

Warm-up

Four things, one from each of the last four chapters.

Discussion prompt

From Lesson 14a: what does super(...) do, and are constructors inherited? From Lesson 11a: what is overloading? From Lesson 16: what does protected mean? And from Lesson 4a: what units do Math.cos and Math.sin take?

Hint: Radians, not degrees.

Answer:

super(...) invokes the superclass constructor, and constructors are not inherited. Overloading is two methods with the same name and different parameter lists. protected means subclasses but not other classes. And the trig functions take radians.

All four are used in the next twenty lines. In this chapter, we'll explore the concept of inheritance more fully and present event-driven programming. We'll continue to develop graphical simulations as a running example, but this time in varying shapes and colors!

4. Two directions of inheritance

Concept

You have used inheritance twice for opposite reasons, and Chapter 17 opens by giving each one a name.

Figure (svg): Two panels contrasting generalization, which pulls shared code up, with specialization, which extends downward

Downey & Mayfield, Think Java, 2nd edition (Green Tea Press / O'Reilly, 2020) — Think Java 2e, Chapter 17 (Advanced Topics), Sections 17.1-17.4, pp. 277-283 — Chapter 17 opens on printed page 277.

5. Generalization and specialization

Section

Chapter opener

6. The same keyword, two purposes

Concept

When we first looked at inheritance in Chapter 14, our purpose was to avoid duplicating code. We noticed that decks of cards and hands of cards had common functionality, and we designed a CardCollection class to provide it. This technique is an example of generalization.

generalization — The process of extracting common code from two or more classes and moving it into a superclass.

specialization — Extending a class to add new attributes or methods, or to modify existing behavior.

generalizationspecialization
starts fromtwo or more existing classesone existing class
producesa new superclassa new subclass
you writethe parentthe child
the exampleCardCollection, in Chapter 14GridCanvas, in Chapter 15
also seen inAutomaton, in Chapter 16Conway and Langton, in Chapter 16

By generalizing the code, we were able to reuse it in the Deck and Hand classes. Chapter 16 did both in one chapter: generalising Conway and Langton into Automaton, and specialising Canvas into GridCanvas.

7. Extending a class you did not write

Notation

The Chapter 15 case is worth re-reading now, because it makes a claim about library design.

Annotate

  • You can extend a class whose source you have never seen. All you need is its public interface.
  • Canvas was designed for it. paint exists to be overridden — it is a hook, not a finished method.
  • Minimal additional code is the measure of a good specialization: GridCanvas added a 2D array and a paint override.
  • Not every class is designed to be extended, and extending one that is not tends to break when the library changes.
  • Polygon is another such class, and the next section extends it — the same move, on a class with public attributes rather than a hook method.

Generalization is something you do to your own code; specialization is often something you do to someone else's. That asymmetry is why the chapter names them separately.

8. Recognising which one you are doing

Worked example

Every use of extends in this course is one or the other. Sorting them is a good test of whether the distinction has landed.

class Hand extends CardCollection     // Ch.14
class GridCanvas extends Canvas       // Ch.15
class Conway extends Automaton        // Ch.16
class DrawablePolygon extends Polygon // Ch.17
classthe superclass existed first?which one
Handno — CardCollection was extracted for itgeneralization
GridCanvasyes — Canvas is a library classspecialization
Conwayno — Automaton was extracted from itgeneralization
DrawablePolygonyes — Polygon is a library classspecialization

Ask which class came first.

Why: If the superclass was written to hold shared code, it is generalization.

Ask who wrote the superclass.

Why: If it is someone else's, it is almost certainly specialization.

Notice both use extends.

Why: The language has one mechanism; the two names describe intent, not syntax.

Notice Conway is both.

Why: It was specialised from nothing and then generalised into Automaton — over two chapters.

Verify: Take Langton extends Automaton and decide which it is.

Why: Generalization — Automaton was created by extracting Conway and Langton's shared code, even though Langton was written first. The direction of the work is what matters, not the direction of the arrow, which points the same way in both cases.

9. Generalization or specialization?

Definition probe

Ask which class existed first, and who wrote it.

Sort into buckets

Sort each example.

generalization
extracting CardCollection from Deck and Pile; extracting Automaton from Conway and Langton
specialization
GridCanvas extends Canvas; DrawablePolygon extends Polygon
gen
Common code is extracted from two or more existing classes and moved up into a new superclass that you write.
spec
An existing class — usually one you did not write — is extended to add attributes or methods, or to modify its behaviour.

10. Why the distinction is worth having

Concept

Two names for one keyword only earn their keep if they lead to different decisions — and they do.

questiongeneralizationspecialization
what goes in the superclass?exactly what the subclasses sharednot your decision
can you change the superclass?yes, it is yoursno
what if it does not fit?restructure itwork around it, or do not extend
the riskover-generalising into a grab bagdepending on behaviour that changes

The third row is the practical one. When you generalise, a bad fit is a design problem you can fix; when you specialise a library class, a bad fit is a constraint — which is exactly the situation Section 17.7 runs into with DrawablePolygon extends Polygon.

11. Extending a class that was not built for it

Trap

The trap

Inheriting from a class just to get at its methods.

// suppose you want the ArrayList methods:
public class Inventory extends ArrayList<Item> {
    // now Inventory has add, remove, get, sort,
    // clear, subList, and 30 other methods
}
consequencedetail
Inventory IS-A ArrayListso it can be passed anywhere a list can
every list method is public on itincluding ones that break your invariants
you cannot remove theminheritance is all-or-nothing
is the sentence true?an inventory is a list? not really

This is Lesson 14a's trap in a library setting. extends is a claim about what your class is, and a claim you make to get at some methods is still a claim everyone else can rely on.

The fix

Specialise when the IS-A is true and the class invites it.

// Polygon IS the thing being drawn - the sentence holds,
// and Polygon's attributes are public by design
public class DrawablePolygon extends Polygon { ... }

// an inventory HAS items - composition, as in Lesson 13b
public class Inventory {
    private ArrayList<Item> items;
}
testDrawablePolygonInventory extends ArrayList
is the IS-A sentence true?yesno
does every inherited method make sense?yesno
was the parent designed for it?yesnot especially

Both tests have to pass. Chapter 17's DrawablePolygon passes them — a drawable polygon really is a polygon, and every Polygon method still makes sense on it.

12. Which came first?

Prediction

The direction of the work, not the arrow.

public class Conway extends Automaton { ... }
classwritten in
ConwayChapter 15
AutomatonChapter 16

Predict first

Is this generalization or specialization?

  • Generalization — Automaton was extracted from Conway and Langton's shared code
  • Specialization — Conway extends an existing class
  • Neither
  • Both equally

Correct: Generalization — Automaton was extracted from Conway and Langton's shared code

Why: Conway existed first; Automaton was created afterwards to hold the duplicated main and mainloop. The extends arrow points the same way in both kinds of inheritance — what distinguishes them is which class was written to accommodate the other.

13. Match the term to its definition

Matching

Chapter 17's vocabulary, part one.

Match the pairs

  • a. generalization
  • b. specialization
  • c. refactoring
  • d. abstract class
  • r1. extracting common code into a superclass
  • r2. extending a class to add or modify behaviour
  • r3. restructuring code without changing its behaviour
  • r4. a class that cannot be instantiated

Why: The first two are new; the last two are Chapter 16's. Note that generalization is usually a refactoring — it restructures code without changing behaviour — while specialization adds behaviour that was not there before.

14. Why can you extend a class you cannot see?

Explain it to yourself

You have never read Canvas's source.

Discussion prompt

GridCanvas extends Canvas and overrides paint, and you have no idea what is inside Canvas. Why is that enough?

Hint: What do you actually need to know?

Answer:

Because you only need its public interface — the method names, parameters and return types. Overriding paint requires knowing its signature, not its body.

And the library promises to call it. Canvas was designed so that a subclass's paint is invoked when the component needs drawing; that promise is the contract.

Encapsulation is what makes this possible. Lesson 11a said hiding the implementation buys the freedom to change it — and here you are on the other side of that deal, depending only on what was published.

15. Polygon objects

Section

Section 17.1

16. Many angles, from a list of points

Concept

The word polygon means many angles; the most basic polygons are triangles, rectangles, pentagons and so forth. Polygons are an important part of computer graphics because they are used to compose more complex images.

Polygon p = new Polygon();
p.addPoint(57, 110);
p.addPoint(100, 35);
p.addPoint(143, 110);
afternpointsshape
new Polygon()0empty
addPoint(57, 110)1a point
addPoint(100, 35)2a line
addPoint(143, 110)3a triangle

Java provides a Polygon class (in java.awt) that we can use to represent and draw polygons. A polygon is just an ordered list of vertices — the edges are implied, including the one from the last point back to the first.

17. Three attributes, two of them arrays

Picture it

Internally, Polygon objects have three attributes — and unusually, all three are public.

Figure (svg): Two parallel arrays of x and y coordinates with a count of how many points are in use

When a Polygon is created, npoints is 0 and the two arrays are initialized with length 4. So the array length and the number of points are two different things — which is exactly the distinction an ArrayList hides and this class does not.

18. How addPoint grows the arrays

Worked example

As points are added, npoints is incremented. If npoints exceeds the length of the arrays, larger arrays are created, and the previous values are copied over (similar to how ArrayList works).

public int npoints;      // total number of points
public int[] xpoints;    // array of X coordinates
public int[] ypoints;    // array of Y coordinates
points addednpointsarray lengthwhat happened
004initial
334room to spare
444full
558 or morenew arrays allocated, values copied

npoints counts the points in use.

Why: Not the array length — the two diverge immediately.

The arrays start at length 4.

Why: Enough for a quadrilateral without any growth.

Growing means allocating and copying.

Why: Larger arrays are created, and the previous values are copied over.

Which is what ArrayList does.

Why: Lesson 13b's growing collection, now visible from the inside.

Verify: Add five points and reason about how many array objects have existed: at least two pairs.

Why: This is the mechanism behind every growable collection. An array cannot change length, so growing means making a bigger one and copying — which is why ArrayList is not free, and why Lesson 10b's quadratic string concatenation had the same shape.

19. What is npoints after three addPoint calls?

Prediction

It starts at zero.

Polygon p = new Polygon();
p.addPoint(57, 110);
p.addPoint(100, 35);
p.addPoint(143, 110);
afternpoints
new Polygon()0
three addPoint calls?

Predict first

What is p.npoints?

  • 3
  • 4
  • 0
  • 6

Correct: 3

Why: As points are added, npoints is incremented, so three calls give three points — a triangle. Note that p.xpoints.length is still 4, because the arrays start at that length and have not yet needed to grow.

20. Public attributes, unusually

Concept

Lesson 11a argued for private instance variables. Polygon's are public, and it is worth asking why the library made that choice.

typical classPolygon
attributesprivatepublic
accessthrough gettersdirectly
can a client corrupt it?noyes — set npoints to 99
whyencapsulationspeed, and age

Polygon is old and performance-sensitive — graphics code touches these arrays in tight loops, and going through getters was once a real cost. It also means RegularPolygon can fill xpoints and ypoints directly, which is what the next section does. A design decision you would not repeat, being relied upon.

21. Confusing npoints with the array length

Trap

The trap

xpoints.length is not the number of vertices.

Polygon p = new Polygon();
p.addPoint(57, 110);
p.addPoint(100, 35);
p.addPoint(143, 110);

for (int i = 0; i < p.xpoints.length; i++) {   // 4, not 3
    System.out.println(p.xpoints[i]);          // prints a stray 0
}
expressionvalue
p.npoints3 — the vertices
p.xpoints.length4 — the capacity
p.xpoints[3]0 — never set

The extra 0 is not a vertex; it is unused capacity. A triangle would be drawn as a quadrilateral with a corner at the origin — a bug that looks like a graphics glitch.

The fix

Loop to npoints.

for (int i = 0; i < p.npoints; i++) {
    System.out.println(p.xpoints[i] + ", " + p.ypoints[i]);
}
bounditerationscorrect?
p.npoints3yes
p.xpoints.length4no

npoints is the count; the array length is the capacity. An ArrayList hides this distinction behind size(); Polygon exposes both, so you have to know which one you want.

22. Count or capacity?

Definition probe

Two numbers that are easy to confuse.

Sort into buckets

Sort each expression.

the count of points in use
p.npoints; how many vertices the shape has
the array's capacity
p.xpoints.length; how many slots are allocated
count
npoints tracks how many vertices have actually been added, which is what any loop over the shape should use.
cap
The array length is how much room exists — initially 4, and larger after the arrays grow.

23. Build a triangle

Fill the middle

Three vertices, added in order.

Fill in the blanks

Polygon p = new Polygon();
p.addPoint(57, 110);
p.addPoint(100, 35);
p.addPoint(143, 110);

Why: The constructor takes no arguments and produces an empty polygon; each addPoint appends one vertex and increments npoints. The edges are implied by the order, including the closing edge from the last point back to the first.

24. Why parallel arrays rather than an array of points?

Socratic

Point[] points would be more obvious.

Discussion prompt

Polygon stores int[] xpoints and int[] ypoints separately rather than one array of Point objects. What does that buy, and what does it cost?

Hint: How many objects is each design?

Answer:

Two arrays of primitives are two objects; an array of 360 Points is 361. For graphics code touching vertices in tight loops, that difference in allocation and indirection is real.

The cost is readability: xpoints[i] and ypoints[i] are one vertex split across two places, and nothing enforces that the arrays stay the same length.

Parallel arrays are a classic trade: faster and more compact, easier to get out of step. You saw the same shape in Lesson 7b's histogram — and the modern answer is usually the array of objects, unless measurement says otherwise.

25. Adding color

Section

Section 17.2

26. Specialization adds what the library left out

Concept

Specialization is useful for adding new features to an existing class, especially when you can't (or don't want to) change its design. Polygon knows its shape and nothing about colour or drawing itself.

public class DrawablePolygon extends Polygon {
    protected Color color;

    public DrawablePolygon() {
        super();
        color = Color.GRAY;
    }

    public void draw(Graphics g) {
        g.setColor(color);
        g.fillPolygon(this);
    }
}
addedwhat it is
protected Color colora new attribute
a constructornot inherited — must be written
draw(Graphics g)a new method
everything elseinherited from Polygon

We can extend the Polygon class by adding a draw method and a Color attribute. Ten lines, and the result has every Polygon method plus two things Polygon does not have — which is what minimal additional code looks like.

27. What the constructor does

Notation

Three lines, and two of them restate rules from Chapter 14.

Annotate

  • Constructors are not inherited — Lesson 14a's rule, restated because it is the one people forget.
  • If you don't define a constructor, the compiler will generate one that does nothing — and here, doing nothing would leave color as null.
  • super() initialises npoints, xpoints and ypoints, which is what makes addPoint work on a DrawablePolygon.
  • Then DrawablePolygon initializes the color attribute to GRAY — a sensible default rather than null.
  • color is protected, so subclasses can set it directly, which RegularPolygon will do.

A default value is a small kindness. Leaving color null would mean g.setColor(null) on any polygon whose colour was never set — an error a long way from its cause.

28. Using the specialized class

Worked example

DrawablePolygon has the same attributes and methods that Polygon has, so nothing you learned about Polygon stops working.

DrawablePolygon p = new DrawablePolygon();
p.addPoint(57, 110);
p.addPoint(100, 35);
p.addPoint(143, 110);
p.color = Color.GREEN;
linecomes from
new DrawablePolygon()the subclass's constructor
addPoint(57, 110)inherited from Polygon
p.color = Color.GREENthe subclass's new attribute
p.draw(g)the subclass's new method

Everything Polygon offered still works.

Why: You can use addPoint as before, or you can directly access npoints, xpoints, and ypoints (since they are public).

Including the methods not yet used.

Why: You can also use methods like contains, intersects, and translate.

Plus the new attribute.

Why: p.color = Color.GREEN; — legal here because the code is setting it from outside...

…which needs a caveat.

Why: color is protected, so this assignment works only from a subclass or the same package. The book's example is in the same package.

Verify: Check that draw uses fillPolygon(this) — passing the object itself to a method that takes a Polygon.

Why: g.fillPolygon(this) is substitution at work. fillPolygon expects a Polygon and receives a DrawablePolygon, which is legal because every DrawablePolygon is a Polygon — Lesson 14a's rule, in the library's own API.

29. What does super() do here?

Prediction

DrawablePolygon's constructor calls it first.

public DrawablePolygon() {
    super();
    color = Color.GRAY;
}
callinitialises
super()?
color = GRAYthe new attribute

Predict first

What does super() initialise?

  • npoints, xpoints and ypoints — Polygon's attributes
  • The color attribute
  • Nothing; it is optional here
  • The Graphics object

Correct: npoints, xpoints and ypoints — Polygon's attributes

Why: The constructor for DrawablePolygon uses super to invoke the constructor for Polygon, which initializes the attributes npoints, xpoints, and ypoints. Without those arrays, addPoint would have nowhere to put a vertex.

30. What draw actually does

Concept

Two lines, and both of them are the graphics idiom from Chapter 15.

public void draw(Graphics g) {
    g.setColor(color);
    g.fillPolygon(this);
}
calldoes
g.setColor(color)sets the pen colour for what follows
g.fillPolygon(this)fills the shape described by this object
the parametera Graphics — supplied by whoever is drawing

The same shape as Cell.draw in Lesson 15a: set a colour, draw something, take the Graphics as a parameter. Graphics is stateful — setColor affects every later call — which is why the colour is set immediately before use rather than once at the start.

31. Relying on the generated default constructor

Trap

The trap

No constructor means color is never set.

public class DrawablePolygon extends Polygon {
    protected Color color;
    // no constructor

    public void draw(Graphics g) {
        g.setColor(color);      // color is null
        g.fillPolygon(this);
    }
}
after new DrawablePolygon()value
npoints0 — Polygon's constructor still ran
xpoints, ypointsarrays of length 4
colornull

If you don't define a constructor, the compiler will generate one that does nothing — nothing beyond calling super(). So the inherited fields are fine and the new one is null, which fails later inside draw.

The fix

Write a constructor and give the new field a default.

public DrawablePolygon() {
    super();
    color = Color.GRAY;
}
lineinitialises
super()npoints, xpoints, ypoints
color = Color.GRAYthe new attribute

Every field a subclass adds is a field the superclass's constructor knows nothing about. Writing the constructor is how you take responsibility for them — and super() first, so the inherited state exists before you touch anything.

32. Inherited or new?

Definition probe

DrawablePolygon adds two things.

Sort into buckets

Sort each member.

inherited from Polygon
addPoint(x, y); translate(dx, dy)
added by DrawablePolygon
color; draw(Graphics g)
inh
It came with Polygon and works on a DrawablePolygon without a line of new code.
new
It is what the specialization adds — a colour and a way to draw, neither of which Polygon has.

33. Draw the polygon

Fill the middle

Set the colour, then fill.

Fill in the blanks

public void draw(Graphics g) setColor}(color);
g.fillPolygon(this);
}

Why: Graphics is stateful, so setColor applies to everything drawn after it — which is why it comes first. Passing this to fillPolygon works because the method takes a Polygon and every DrawablePolygon is one.

34. Could you add colour without inheritance?

Counterexample

A class holding a Polygon and a Color would work too.

Discussion prompt

class ColoredShape { Polygon p; Color c; } gives the same data. What does the inheritance version buy, and what does the composition version buy?

Hint: What can you pass to fillPolygon?

Answer:

Inheritance buys substitution. A DrawablePolygon can be passed straight to fillPolygon, contains, or anything else expecting a Polygon; the composition version must unwrap first.

Composition buys control. You expose only the methods you want, rather than inheriting all of Polygon's — including its public mutable arrays.

Here inheritance wins because the IS-A is genuine and the library API demands a Polygon. When the sentence is true and the parent's methods all make sense, extending is the lighter answer — that is the same test as Lesson 14a's.

35. Regular polygons

Section

Section 17.3

36. Specialising the specialization

Concept

In mathematics, a regular polygon has all sides the same length and all angles equal in measure. Regular polygons are a special case of polygons, so we will use specialization to define a class for them.

RegularPolygon rp = new RegularPolygon(6, 50, Color.BLUE);

// class RegularPolygon extends DrawablePolygon
//   which extends Polygon
//     which extends Object
classadds
Polygonnpoints, xpoints, ypoints, addPoint, translate
DrawablePolygoncolor, draw
RegularPolygona constructor that computes the vertices

We could extend the Polygon class, as we did in the previous section. But then we would not have the Color functionality we just added. So we will make RegularPolygon extend DrawablePolygon. Inheritance chains: each level keeps everything below it.

37. The trigonometry

Notation

The constructor uses trigonometry to find the coordinates of each vertex. Four steps, and the book gives all of them.

Annotate

  • A circle is 2π radians, so n equal steps around it are 2π/n apart.
  • Vertex i is at angle i × theta — vertex 0 at angle 0, vertex 1 at theta, and so on.
  • cos gives x and sin gives y, scaled by the radius — the definition of the trig functions, used directly.
  • Math.cos and Math.sin take radians, which is why theta is computed in radians rather than degrees. Lesson 4a's warning, finally load-bearing.
  • The results are doubles and the arrays are ints, so each one is rounded — (int) Math.round(x).

One formula, applied n times. The whole difference between a triangle and a 360-sided near-circle is the value of n — which is why the constructor takes it as a parameter rather than having three classes.

38. The RegularPolygon constructor

Worked example

Two blocks: initialise the inherited attributes, then compute the vertices.

public RegularPolygon(int nsides, int radius, Color color) {
    // initialize DrawablePolygon attributes
    this.npoints = nsides;
    this.xpoints = new int[nsides];
    this.ypoints = new int[nsides];
    this.color = color;

    // the amount to rotate for each vertex (in radians)
    double theta = 2.0 * Math.PI / nsides;

    // compute x and y coordinates, centered at the origin
    for (int i = 0; i < nsides; i++) {
        double x = radius * Math.cos(i * theta);
        double y = radius * Math.sin(i * theta);
        xpoints[i] = (int) Math.round(x);
        ypoints[i] = (int) Math.round(y);
    }
}
nsidesthetavertex 0vertex 1
4π/2 ≈ 1.571(50, 0)(0, 50)
6π/3 ≈ 1.047(50, 0)(25, 43)
360≈ 0.017(50, 0)(50, 1)

Fill in all four attributes directly.

Why: This constructor initializes all four DrawablePolygon attributes, so it doesn't have to invoke super().

Compute the step angle.

Why: double theta = 2.0 * Math.PI / nsides;

Loop over the vertices.

Why: Inside the for loop, it uses Math.sin and Math.cos to compute the coordinates of the vertices as floating-point numbers.

Round to integers.

Why: Then it rounds them off to integers and stores them in the arrays.

Verify: Check vertex 0 for any n: cos(0) is 1 and sin(0) is 0, so it is always at (radius, 0).

Why: Note 2.0 * Math.PI rather than 2 * Math.PI. Both work here since Math.PI is a double, but writing 2.0 makes the floating-point intent explicit — and Lesson 2b's integer-division trap is close enough that the habit is worth keeping.

39. What is theta for a hexagon?

Prediction

A circle divided into six.

double theta = 2.0 * Math.PI / nsides;   // nsides = 6
value
2 × πabout 6.283
divided by 6?

Predict first

What is theta, roughly?

  • About 1.047 radians — 60 degrees
  • 60 radians
  • About 0.017 radians
  • 6.283 radians

Correct: About 1.047 radians — 60 degrees

Why: A full circle is 2π radians, or about 6.283, and dividing by six gives about 1.047 — which is 60 degrees. Every vertex sits one theta further around, so vertex i is at angle i * theta.

40. Centred at the origin, then moved

Concept

When we construct a RegularPolygon, the vertices are centered at the point (0, 0).

RegularPolygon rp = new RegularPolygon(6, 50, Color.BLUE);
rp.translate(100, 100);
stepcentrevertex 0
after the constructor(0, 0)(50, 0)
after translate(100, 100)(100, 100)(150, 100)

If we want the center of the polygon to be somewhere else, we can use translate, which we inherit from Polygon. Building at the origin and moving afterwards keeps the trigonometry simple — and translate was free, which is a small return on choosing to extend Polygon.

41. Working in degrees

Trap

The trap

Math.cos takes radians, not degrees.

double theta = 360.0 / nsides;          // degrees
double x = radius * Math.cos(i * theta);  // wrong
nsidestheta in degreeswhat cos seesresult
66060 radiansnonsense
49090 radiansnonsense
does it crash?——no

60 radians is about nine and a half full turns, so the cosine is some arbitrary value between −1 and 1. The shape is drawn, and it is not a hexagon — a bug with no error message at all.

The fix

A full circle is 2π radians.

double theta = 2.0 * Math.PI / nsides;

// or, if you must start from degrees:
double theta = Math.toRadians(360.0 / nsides);
nsidestheta in radiansmeaning
32.094120 degrees
41.57190 degrees
61.04760 degrees

Lesson 4a introduced Math.toRadians for exactly this, and here the calculation starts in radians so it is not needed. Every trig function in Java's Math class takes radians — there are no degree versions, which is a deliberate simplification you have to remember.

42. Where is vertex 0?

Prediction

i is zero on the first iteration.

double x = radius * Math.cos(i * theta);
double y = radius * Math.sin(i * theta);
// i = 0, radius = 50
expressionvalue
Math.cos(0)1.0
Math.sin(0)0.0

Predict first

What are vertex 0's coordinates?

  • (50, 0)
  • (0, 50)
  • (0, 0)
  • (50, 50)

Correct: (50, 0)

Why: cos(0) is 1 and sin(0) is 0, so vertex 0 lands at (radius, 0) — directly right of the centre. That is true for every regular polygon regardless of the number of sides, which makes it a good value to check the constructor against.

43. Compute a vertex

Fill the middle

Cosine gives x; sine gives y.

Fill in the blanks

double theta = 2.0 * Math.PI / nsides;
double x = radius * Math.cos(i * theta);
double y = radius * Math.sin(i * theta);

Why: Math.PI is a class variable — Lesson 12a's static final — and Math.sin takes radians, which is why theta is computed from 2π rather than 360. By definition cos(θ) = x/r and sin(θ) = y/r, so multiplying each by the radius gives the coordinates.

44. What does a 360-sided polygon look like?

Edge cases

The book creates one.

Discussion prompt

new RegularPolygon(360, 50, Color.BLUE) computes 360 vertices on a circle of radius 50. What does it look like, and what does that say about curves in computer graphics?

Hint: How far apart are adjacent vertices?

Answer:

A polygon with 360 sides is a pretty good approximation of a circle. At radius 50 the vertices are less than a pixel apart, so no straight edge is visible.

Which is how curves are drawn generally: the screen has no curves, only pixels, so every circle you have ever seen on a screen is an approximation.

The interesting part is choosing n. Too few and the shape looks faceted; too many and you compute vertices finer than a pixel for nothing. Real graphics code picks n from the radius — which is a decision this constructor leaves to its caller.

45. More constructors

Section

Section 17.4

46. Chaining constructors with this(...)

Concept

Classes in the Java library often have more than one constructor for convenience. We can do the same with RegularPolygon.

public RegularPolygon(int nsides, int radius) {
    this(nsides, radius, Color.GRAY);
}

public RegularPolygon(int nsides) {
    this(nsides, 50);
}
callchains tofinal arguments
new RegularPolygon(6)this(6, 50)—
→ this(6, 50)this(6, 50, GRAY)—
→ this(6, 50, GRAY)the real constructor6, 50, GRAY

The keyword this, when used in a constructor, invokes another constructor in the same class. It has a similar syntax as the keyword super, which invokes a constructor in the superclass. Two keywords, one idea, aimed at different classes.

47. Three constructors, one implementation

Picture it

Because we provide only one integer argument, Java calls the third constructor, which calls the second one, which calls the first one.

Figure (svg): A pipeline showing a one-argument constructor call chaining through two and three argument versions

The result is a RegularPolygon with the specified value of nsides, 6, the default value of radius, 50, and the default color, GRAY. One implementation, three ways in — and no duplicated logic.

48. Validating the arguments

Worked example

When writing constructors, it's a good idea to validate the values you get as arguments. Doing so prevents run-time errors later in the program, which makes the code easier to debug.

public RegularPolygon(int nsides, int radius, Color color) {
    // validate the arguments
    if (nsides < 3) {
        throw new IllegalArgumentException("invalid nsides");
    }
    if (radius <= 0) {
        throw new IllegalArgumentException("invalid radius");
    }
    if (color == null) {
        throw new NullPointerException("invalid color");
    }
    // the rest of the method is omitted
}
argumentmust beexception if not
nsidesat least 3IllegalArgumentException
radiusgreater than zeroIllegalArgumentException
colornot nullNullPointerException

Decide what valid means.

Why: The number of sides should be at least three, the radius should be greater than zero, and the color should not be null.

Reject anything else immediately.

Why: We throw an exception to indicate that one of the arguments is invalid.

Know what that does.

Why: By default, these exceptions terminate the program and display an error message along with the stack trace.

Put it in one place.

Why: Because we added this code to the most general constructor, we don't have to add it to the others.

Verify: Call new RegularPolygon(2) and check that the error names nsides rather than failing somewhere in the drawing code.

Why: That is the entire argument for validating early. Two sides would produce a degenerate shape that draws as a line, and you would be debugging the graphics rather than the call — the failure would be a long way from its cause.

49. What does new RegularPolygon(6) produce?

Prediction

Three constructors, chained.

public RegularPolygon(int nsides) {
    this(nsides, 50);
}
public RegularPolygon(int nsides, int radius) {
    this(nsides, radius, Color.GRAY);
}
parametervalue
nsides6
radius?
color?

Predict first

What are the resulting values?

  • 6 sides, radius 50, colour GRAY
  • 6 sides, radius 6, colour GRAY
  • 6 sides, radius 0, colour null
  • A compile error — too few arguments

Correct: 6 sides, radius 50, colour GRAY

Why: Java calls the third constructor, which calls the second one, which calls the first one. Each link in the chain supplies one default, so a single argument produces a fully initialised object — and none of the defaults is written more than once.

50. Throwing an exception on purpose

Concept

Lesson 15b caught exceptions. This is the other side: creating and throwing one.

throw new IllegalArgumentException("invalid nsides");
partmeans
throwthe statement that raises it
new IllegalArgumentException(...)an ordinary object, created with new
the stringthe message shown to whoever sees it
the effectthe constructor stops immediately

An exception is an object, created with new like any other, and throw is the statement that sends it up the call chain. The exception type is chosen to describe the problem: IllegalArgumentException for a bad value, NullPointerException for a missing object.

51. Duplicating validation in every constructor

Trap

The trap

Checking the same things three times.

public RegularPolygon(int nsides) {
    if (nsides < 3) { throw new IllegalArgumentException(...); }
    this(nsides, 50);          // also checks nsides
}

public RegularPolygon(int nsides, int radius) {
    if (nsides < 3) { throw new IllegalArgumentException(...); }
    if (radius <= 0) { throw new IllegalArgumentException(...); }
    this(nsides, radius, Color.GRAY);   // checks again
}
problemconsequence
the same check in three placesthree places to update
they can drift apartone gets a fix, others do not
and it does not even compilethis(...) must be the first statement

The last row is decisive: a this(...) call must be the first statement in a constructor, so the check cannot precede it anyway. Java's rule pushes you toward the right design.

The fix

Validate once, in the most general constructor.

public RegularPolygon(int nsides) {
    this(nsides, 50);                   // delegates
}

public RegularPolygon(int nsides, int radius, Color color) {
    if (nsides < 3) { throw new IllegalArgumentException(...); }
    // ... the checks live only here
}
constructorvalidates?why
RegularPolygon(int)noit delegates
RegularPolygon(int, int)noit delegates
RegularPolygon(int, int, Color)yesevery path ends here

Because we added this code to the most general constructor, we don't have to add it to the others. Every chain terminates there, so one copy of the checks covers all three entry points — which is why chaining is worth more than three independent constructors.

52. this or super?

Definition probe

Two keywords that both invoke a constructor.

Sort into buckets

Sort each purpose.

this(...)
invoke another constructor in the same class; RegularPolygon(6) reaching RegularPolygon(6, 50)
super(...)
invoke the superclass's constructor; DrawablePolygon reaching Polygon's constructor
this
It delegates sideways, to another constructor of the same class — the mechanism behind default parameter values.
super
It delegates upward, to the superclass, initialising the attributes the subclass inherited.

53. Chain and validate

Fill the middle

Default the colour; reject too few sides.

Fill in the blanks

public RegularPolygon(int nsides, int radius) this}(nsides, radius, Color.GRAY);
}

// in the three-argument constructor:
if (nsides < 3) throw} new IllegalArgumentException("invalid nsides");
}

Why: this(...) delegates to another constructor in the same class and must be the first statement — which is why the validation lives in the constructor at the end of every chain rather than being repeated. throw raises the exception object, stopping the constructor immediately.

54. Why validate at all if it just crashes?

Real world

An invalid argument would probably cause a crash eventually anyway.

Discussion prompt

new RegularPolygon(2) without validation would produce a degenerate shape and maybe fail later. Why is failing immediately better?

Hint: Where would you look for the bug?

Answer:

Because the failure names the actual mistake. invalid nsides points at the call; a graphics glitch three screens later points nowhere.

Doing so prevents run-time errors later in the program, which makes the code easier to debug — and the further apart the cause and the symptom, the more expensive the debugging.

Fail fast, at the boundary. A constructor is where invalid data enters the object, so it is the cheapest place to stop it — and the same reasoning as Lesson 5b's input validation, one level up.

55. this against super

Comparison

Fill the blanks.

Comparison matrix

this(...)super(...)
invokes a constructor inthe same classthe superclass
used fordefault parameter valuesinitialising inherited attributes
must bethe first statementthe first statement
example in this lessonRegularPolygon(6) → (6, 50)DrawablePolygon → Polygon
this also meansa reference to the current object— (super has no such use)

The last row is the one that trips people up. this is overloaded: a reference to the current object almost everywhere, and a constructor call in exactly one position — the first statement of a constructor.

56. The pattern to carry away

Pattern

Constructor chaining with validation at the end of the chain.

// the most general constructor does the work and the checking
public RegularPolygon(int nsides, int radius, Color color) {
    if (nsides < 3) {
        throw new IllegalArgumentException("invalid nsides");
    }
    ...compute the vertices...
}

// the others supply defaults and delegate
public RegularPolygon(int nsides, int radius) {
    this(nsides, radius, Color.GRAY);
}

public RegularPolygon(int nsides) {
    this(nsides, 50);
}
rulereason
one constructor does the workno duplicated logic
the others delegate with this(...)each supplies one default
validate in the most general oneevery chain ends there
this(...) must come firstJava enforces it
throw for an invalid argumentfail where the mistake is

57. Check: chaining

Check

Work it out before you click.

public RegularPolygon(int nsides) {
    this(nsides, 50);
}
public RegularPolygon(int nsides, int radius) {
    this(nsides, radius, Color.GRAY);
}
RegularPolygon rp = new RegularPolygon(8);
parametervalue
nsides8
radius?

Check your understanding

What object is created?

  • A. An 8-sided polygon of radius 50, coloured GRAY (correct)
  • B. An 8-sided polygon of radius 8
  • C. A compile error — the one-argument constructor is incomplete
  • D. An 8-sided polygon with a null colour

Answer: A

Why: Each constructor supplies one default and delegates: 8 becomes (8, 50), which becomes (8, 50, GRAY). Because we provide only one integer argument, Java calls the third constructor, which calls the second one, which calls the first one — and only the last has a body, so there is one implementation and no duplicated logic.

Why B tempts people
The radius default is 50, written once in the one-argument constructor.
Why C tempts people
Delegating with this(...) is a complete constructor body; nothing further is required.
Why D tempts people
GRAY is supplied by the two-argument constructor, precisely so the colour is never null.

58. Check: radians

Check

Work it out before you click.

double theta = 2.0 * Math.PI / nsides;
double x = radius * Math.cos(i * theta);
unit
Math.cos expects?

Check your understanding

Why 2π rather than 360?

  • A. Math.cos and Math.sin take radians, and a full circle is 2π radians (correct)
  • B. 360 would overflow an int
  • C. Because the polygon is centred at the origin
  • D. Because Math.PI is more accurate than 360

Answer: A

Why: Every trigonometric function in java.lang.Math works in radians — there are no degree versions. Using 360 would pass an angle of, say, 60 radians instead of 60 degrees, producing an arbitrary value between −1 and 1 and a shape that is not a polygon. Nothing would crash, which is what makes it a nasty bug.

Why B tempts people
360 is a perfectly ordinary int; overflow is not the issue.
Why C tempts people
The centre is set by translate afterwards and has nothing to do with the units.
Why D tempts people
Accuracy is not the point — the two numbers measure different things.

59. Check: validation

Check

Work it out before you click.

public RegularPolygon(int nsides, int radius, Color color) {
    if (nsides < 3) {
        throw new IllegalArgumentException("invalid nsides");
    }
    ...
}
// the one- and two-argument constructors have no checks
callreaches the three-arg constructor?
new RegularPolygon(2)yes, via the chain

Check your understanding

Is new RegularPolygon(2) caught?

  • A. Yes — it chains to the three-argument constructor, where the check lives (correct)
  • B. No — the one-argument constructor has no check
  • C. No — validation only applies to direct calls
  • D. It compiles but produces a two-sided polygon

Answer: A

Why: Because we added this code to the most general constructor, we don't have to add it to the others. Every chain terminates at the three-argument version, so one copy of the checks guards all three entry points — which is a large part of why chaining beats three independent constructors.

Why B tempts people
It does not need one; it delegates, and the delegate checks.
Why C tempts people
A chained call is an ordinary constructor invocation and runs the whole body.
Why D tempts people
The exception is thrown before any vertex is computed.

60. Default arguments, the Java way

Real world

Some languages let you write RegularPolygon(int nsides, int radius = 50). Java does not.

Discussion prompt

Java has no default parameter values, so constructor chaining does the job instead. What does that cost, and what does it buy?

Hint: Count the constructors, and ask where the default lives.

Answer:

It costs verbosity: three constructors instead of one signature with defaults, and each one has to be written and maintained.

It buys one thing that matters — the default is written in code, in exactly one place, and can be any expression at all. A defaulted parameter in some languages is limited to constant values.

And it is the same pattern regardless of language. Even where defaults exist, a telescoping set of constructors that all delegate to one implementation is the standard shape — because there should be one place the real work happens.

61. How sure are you?

Commit first

Commit to an answer and to your confidence.

Predict first

What does this(nsides, radius, Color.GRAY); do when written inside a constructor?

  • Invokes another constructor in the same class
  • Invokes the superclass's constructor
  • Creates a second object
  • Assigns the three values to instance variables

Correct: Invokes another constructor in the same class

Why: The keyword this, when used in a constructor, invokes another constructor in the same class. It has a similar syntax as the keyword super, which invokes a constructor in the superclass. No second object is created — the same object is being initialised, just by a different constructor. Note that this means something else everywhere outside this position: a reference to the current object. The constructor-call meaning applies only as the first statement of a constructor, and Java requires it to be first precisely so that no initialisation happens before the delegate runs.

62. Explain it to someone else

Explain it

Two minutes, out loud.

Discussion prompt

A classmate has written three constructors for a class and copied the same validation into all three. Explain what they should do instead, and why Java's rules push them that way.

Hint: Where must a this(...) call go?

Answer:

Pick the constructor with the most parameters and put the real work and all the checks in it. Make the others supply defaults and delegate to it with this(...).

Then every path ends in the validating constructor, so one copy of the checks covers every entry point — and there is only one place to update when a rule changes.

Java's rules push you there anyway: a this(...) call must be the first statement in a constructor, so you cannot put a check before it even if you wanted to. The language makes the good design the only convenient one.

63. Exit ticket

Exit ticket

One question before you close the deck.

Predict first

What is the difference between generalization and specialization?

  • Generalization extracts common code from existing classes into a new superclass; specialization extends an existing class to add or modify behaviour
  • Generalization uses extends and specialization uses implements
  • Generalization is for library classes and specialization is for your own
  • They are two names for the same thing

Correct: Generalization extracts common code from existing classes into a new superclass; specialization extends an existing class to add or modify behaviour

Why: Generalization: the process of extracting common code from two or more classes and moving it into a superclass. Specialization: extending a class to add new attributes or methods, or to modify existing behavior. Both use extends — the names describe the direction of the work, not the syntax. CardCollection and Automaton were generalizations, written to hold code that already existed elsewhere; GridCanvas and DrawablePolygon are specializations of library classes their authors never saw. The third option is a tendency rather than a rule: you can generalise or specialise either.

64. Draw the whole lesson

Connect it up

One page, from memory.

Draw it

Draw the inheritance chain Object → Polygon → DrawablePolygon → RegularPolygon, writing beside each class only what it adds. Then draw a hexagon centred at the origin with radius r, mark theta = 2π/n, and write the two formulas that give vertex i. Below that, write the three RegularPolygon constructors with arrows showing how new RegularPolygon(6) reaches the one that does the work — and mark where the validation lives and why it lives there.

65. Recap

Recap

Four sections that name two kinds of inheritance, then specialise a library class twice.

if you remember one thingit is this
about inheritancetwo directions, one keyword
about constructorsone does the work; the rest delegate
about argumentsreject bad ones where they arrive

Sources

  1. Downey & Mayfield, Think Java, 2nd edition (Green Tea Press / O'Reilly, 2020) — Think Java 2e, Chapter 17 (Advanced Topics), Sections 17.1-17.4, pp. 277-283
  2. Java SE 21 API — java.awt.Polygon
  3. Think Java 2e — free online edition and source code

Want this taught 1-on-1? Alexander tutors Java — $55/session, free consultation.

Book on Wyzant · Text (657) 465-8108