Classes, Objects and Enums
Week 4 split a program into methods. This week the methods get something to
belong to: objects, bundles of data together with the code allowed to touch it,
built from classes. You will write your own classes in Java and Python, see what
new and this really do, meet enums — types with a
fixed set of values — and see what Java’s switch has grown into since
Week 2.
The lecture, then the practice
Choose a section from the list to open it — or use the arrow keys once the list has focus. Every code example explains itself: hover over an underlined part (or tap it) to see what it does. Run runs it; ✎ Edit opens it in an editor to change it and break it on purpose. Several are meant to fail — read what the compiler says before you fix them.
01The ideas behind OOP
1.1A brief history
Object-oriented programming (OOP) is a broad family of ways to design programs, centred on one idea: bind data to the code that is allowed to manipulate it, in a single entity called an object.
Historically the idea grew, possibly ironically, out of the functional language Lisp in the late 1950s and early 1960s. Simula introduced many of the concepts still in C++ and Java — classes, dynamic binding, inheritance — with syntax close to Algol 60, which is why it reads fairly easily to a Java programmer. Smalltalk was designed as an OO language from the ground up and is often called the first “true” one, but Simula has had the greater effect so far — not least because Bjarne Stroustrup used it during his doctoral work and then built its ideas into C++.
The class concept allowed me to map my application concepts into the language constructs in a direct way that made my code more readable than I had seen in any other language.
Bjarne Stroustrup, A History of C++: 1979–1991, HOPL-II, 1993 — on writing his PhD simulator in Simula Portrait: Victor Zavyalov / ICPCNews, 2013 · CC BY 2.0OOP became the dominant paradigm in the 1990s, with C++, the arrival of Java, and a software industry that needed modular, reusable, maintainable programs at large scale. Like any tool it can be used badly; used well, it is a very powerful way to build large software that can be extended without being rewritten.
Two Turing Awards in three years went to object orientation. The 2001 award went to Ole-Johan Dahl and Kristen Nygaard “for ideas fundamental to the emergence of object-oriented programming, through their design of the programming languages Simula I and Simula 67”; the 2003 award to Alan Kay for “leading the team that developed Smalltalk”, among other things. Both Stroustrup and James Gosling, Java’s creator, have named Simula as a major influence. Source: Wikipedia — Turing Award and Simula.
1.2The four pillars
“Object-oriented” covers anything whose fundamental unit is the object, but almost every OO language rests on four principles:
| Pillar | What it means |
|---|---|
| Encapsulation | Data is enclosed in objects. That gives it context, and control over how it is accessed and changed. |
| Abstraction | Implementation details are hidden from the outside. Two implementations that do the same job are interchangeable; users only deal with the abstraction. |
| Inheritance | Objects can inherit properties from other objects, so subtypes exist, share what they have in common and differ only where they need to. |
| Polymorphism | One thing, many “shapes”: different implementations of the same method, objects with several types, type information abstracted away. |
They are not completely separate — encapsulation enables abstraction, inheritance supports polymorphism, polymorphism supports abstraction. Under all four sits a vaguer but stronger idea, shared with the modular programming that came before OOP: any unit of a program should not have to care how the rest of the program works in order to work correctly.
1.3Encapsulation
The simplest pillar to get hold of, though its payoff only really shows when you build something
large or fix someone else’s code. This BankAccount (the one from lab 5)
encapsulates two pieces of data and offers methods for controlled access to them. The balance is
kept as a whole number of cents:
newmakes aBankAccountobject and runs its constructor (lines 20–23) with these two arguments. Thefmakes100.10afloatliteral; without it, it is adoubleand will not fit thefloatparameter.mainlearns the balance only by calling a public method, which returns it as afloatnumber of dollars. Nothing inmainreveals that the account keeps cents.- Each call rounds
0.01 × 100to exactly 1 cent, so theintbalance grows by exactly 1000 cents, to $1,000,010. Keep this loop in mind for 1.4. - The blueprint for every account object: two pieces of data (lines 17–18) together with the only methods allowed to touch them. Binding data to the code allowed to change it is the central idea of OOP (1.1).
- Only code inside
BankAccountmay useaccountName, orbalanceon line 18, which is private too.Runner’smainis outside, so it must go through the public methods. - An
intholds whole numbers exactly, so $100.10 is kept as 10010 and adding or subtracting cents never loses anything to rounding. - Same name as the class and no return type, not even
void. It runs once, whennewcreates the object, and gives the fields their first values. - The parameter has the same name as the field, so plain
accountNamemeans the parameter.this.accountNameis the field of the object being built. - Multiply to get cents, still a
float, then round to the nearest whole number. Given afloat,Math.roundreturns anint, so the result goes intobalancewith no cast. - The cast applies to
this.balancealone, so the division is done infloatand keeps the cents: 11919 becomes 119.19. Without the cast,intdivision would give 119. - The amount is turned into whole cents before it is subtracted. No parameter is called
balance, so the plain name means the field;this.is optional here. - A method may share a field’s name; the brackets tell Java which is meant. It hands out the name, and no method changes it, so outside code can read the name but never alter it.
Two classes in one file: only one may be public, and the file is
named after it — the Week 1 rule. BankAccount has no access modifier, which is
what lets it share the file.
accountName and balance are private, so code outside the
class cannot interfere with them except through the methods. Try it: this main reaches
for the balance directly.
- The constructor is
public, so any class may create an account. But a compile error stops the whole file, so not even this line runs. mainis inRunner, andbalanceis private toBankAccount, so the compiler refuses the assignment (javac: balance has private access in BankAccount). Sharing a file does not help:privatemeans this class only.- This cut-down class has only a constructor. With no public methods, code outside the class cannot read or change
balanceat all. - Access to
balanceis limited to code written insideBankAccount’s braces. Remove this word and line 4 compiles — andmaincan set any balance it likes. - The constructor belongs to
BankAccount, so it may assignbalancefreely. The same field is out of bounds inmain.
private is there to stop accidental interference, not a determined
attacker. It is about safety, and a step towards abstraction: code that uses
BankAccount never needs to know how the data is stored inside, only which public
methods it can call.
1.4Abstraction — and we tested the claim
Abstraction says you should not have to care about the insides of something when they do not matter to what it does. A clockwork watch, a digital watch and a phone all present the time; you use them interchangeably whether there are gears inside or a connection to an atomic clock.
For code, that means the rest of a program should not have to change when an implementation does.
The Ed lesson offers a second BankAccount that stores a float instead of
cents, and says the lab program could “probably (I haven’t tested it)” use it
unchanged. So test it. Same main, word for word; only the inside of
BankAccount differs:
maindepends only on the names, parameters and return types of the constructor and the four public methods. Those are identical, somaincompiles without a change.- In the cents version this loop added exactly 1000 cents. Here each call does a
floataddition that changes nothing (line 34), so Bach’s balance never moves from 1000000.0. - The field now holds dollars directly, as a
float. Because it is private, no code outside the class could depend on it — which is why the swap is invisible tomain. - No conversion to cents. But
100.10fcannot be held exactly: the nearestfloatis 100.09999847…, whichcurrentBalancelater rounds back to the cent. - Turns dollars into cents and rounds to the nearest whole number. Given a
float,Math.roundreturns anint. - The cast applies to the
intfromMath.round, so/ 100divides infloatand keeps the cents. Without it,intdivision would drop them. - Each subtraction and addition rounds its result to the nearest
float. Near Linh’s 100 dollars those errors are far below a cent, andcurrentBalancerounds them away. - Near 1,000,000 the nearest
floats are 1000000.0 and 1000000.0625. The exact sum, about 1000000.01, is much closer to the first, so the result rounds back to the old balance.
Linh’s account agrees with the cents version, to the cent. Bach’s does not: a thousand
one-cent deposits into a million-dollar account simply vanish. A Java float is an IEEE
754 32-bit number with only about seven significant digits, and near 1,000,000 the gap between one
float and the next is 0.0625 — so adding 0.01 rounds straight back to where it
started.
The swap worked exactly as abstraction says it should: main compiled and ran
without a single change. What abstraction cannot do is make the new implementation correct.
That is why the Java lesson suggests storing money as an int number of cents — and
why “I haven’t tested it” is a sentence worth never leaving in your own code.
1.5Inheritance and polymorphism, briefly
Inheritance arrives properly in the next few lessons. In Java it comes in two forms, class-based inheritance and interfaces, and it does two jobs at once: a child class reuses its parent’s data and methods instead of reimplementing them, and a child can be treated as the same kind of thing as its parent.
Polymorphism comes in three basic forms:
- Ad-hoc — the same method name applied to different parameter lists. You
have used it constantly: it is Week 4’s overloading, and the
reason
System.out.printlnhas ten versions. - Parametric — type information abstracted out, so code can store or move objects without caring much what type they are. Coming very soon.
- Sub-type — an object can be treated as having the type of any of its parent classes. Also coming very soon.
02Classes and objects in Java
2.1The dot operator, said out loud
A piece of syntax you have been using quietly since Week 1: ., said “dot”.
It takes the thing on its left and retrieves the member inside it named on its right.
| Code | What the dots do |
|---|---|
| System.out.println("Hi") | look in the class System for the class-level variable out (a PrintStream — Week 4, 3.3); in that object find the method println(String), and run it with "Hi" |
| sc.nextInt() | go to the object that the variable sc refers to, find its method nextInt(), and run it with no arguments |
A dot gives you either a value (reading a variable) or a method (which then runs straight away). Because an object variable holds a reference — Week 4, section 6.3 — the dot is sometimes called the dereference operator: it follows the reference to the object. Beyond that there is nothing special about it; it just gets things out of other things.
2.2Classes are templates for objects
An object is a construct that encapsulates data and offers methods to interact with it. An OO language needs a way to define objects — what data they hold, what methods they have — and Java is class-based (as opposed to prototype-based): a class is the template for its objects.
<access modifier> class <Name> {
<data and methods>
}- Access modifiers are the ones you know from methods. Classes are almost always
publicfor now — and each.javafile can have only one public class, named the same as the file. - Class names follow the variable rules, but start with an upper-case letter:
Scanner,String,BankAccount.
An empty class is perfectly legal, and occasionally handy as a placeholder to fill in later.
Empty here has no access modifier at all:
- Only one class per file may be
public, and the file must be named after it —Runner.java. It holdsmain, so this is where the program starts. - The variable’s type is the class
Empty. Java is case-sensitive, soEmptyandemptyare different names; by convention only the class starts with a capital. - The class is only the template;
newmakes an actual object from it and gives back a reference, stored inempty. It works althoughEmptydeclares no constructor, because Java supplies a default one (section 4.2). - With no
public,Emptycan shareRunner’s file. Its body holds no data and no methods, yet the class is legal and usable as a type.
A class should have a single clear purpose: it models one entity in the system. What counts as one entity depends on the system, and gets clearer with practice — but it is usually obvious what does not belong. A class modelling a bank account should not also store the staffing details of a branch.
2.3A class with data and methods
A bank account should know whose it is and how much is in it, and offer ways to read and change that. Both the data and the methods are members of the class:
newcreates a freshBankAccountobject and runs its constructor (line 22) with these two arguments. The reference that comes back is stored inacc.- The inner call runs first:
nextDouble()reads 50 and returns it as adouble. Then the dot followsaccto its object and runs that object’sdepositwith 50.0 asamount. maincan only ask the account for 500;withdrawdecides. The balance is 150.0, so the request is refused and nothing tells the caller.- The fields are private, so
maincannot writeacc.customerName; it calls a method that returns the value instead. A method used like this is often called a getter. - No
mainand nothingstatic: the class only describes what every account holds and can do. Nothing happens until another class makes an object from it. - Every
BankAccountobject gets its owncustomerNameandbalance.privatemeans only code inside this class can use them by name. - Named after the class, with no return type — not even
void. It runs once, whennewmakes the object, to give the fields their starting values; section 4.2 covers it fully. this.customerNameis the object’s field; plaincustomerNameis the parameter of the same name. The parameter’s value is copied into the field (more in section 4.3).- A
voidmethod that modifies the object instead of returning a value — Week 4’s rule that a method normally does one or the other. - Both must hold: enough money, and a positive amount. Because
balanceis private, this method is the only way to reduce it, so no caller can skip the check. - The other half of that rule:
getBalancehands back the current balance and leaves it alone. Line 13 joins it onto the string, givingLinh has 150.0.
With 50 then 500, the withdrawal is quietly refused — the rule lives inside the class, so no caller can forget it. The class has all the fundamental elements:
- Data members, declared
privateso that only objects of this class can reach them. What could go wrong if they werepublic? - Method members, which allow controlled access to the data.
And notice what is missing: none of these members is static, and there is no
main. BankAccount cannot do anything by itself — you create objects to
hold the data, and other classes build them into a program. An OO program works through the
interactions of classes and objects.
A real version would want an AccountHolder class instead of a bare name, would store
cents rather than a double (section 1.4), and would say what happens when a withdrawal
fails — and it is probably not this class’s job to validate amounts at all.
2.4Classes are types
A class is not only a template: it is a type. A variable’s type can be a class, and so can the element type of an array, the return type of a method and the type of a parameter. That buys real confidence:
- The class name is used just like
intinint[]: every element ofaccountsmust be a reference to aBankAccount(ornull). - This makes the array only — no
BankAccountobjects. Every slot starts asnulluntil an account made withnew BankAccount(…)is stored in it.
Nothing that is not a BankAccount can get into accounts — the
compiler (or, failing that, the runtime) stops it. And because an object can be handed to a method,
you can pass in functionality, not just data: below, countSameOwner does not pull
the names out and compare them itself; it asks the account, because the account knows
what “same owner” means.
- One account and an array of them. Only
BankAccounts can be passed in, so the compiler knows each one offers ahasSameOwnermethod. candidaterefers to each account in the array in turn — the objects themselves, not copies, since the array holds references.- The method does not pull the names out (it could not: they are private). It asks
account, passing the candidate, and thebooleanit gets back is the condition. - Three
nullreferences until lines 15–17 store an account in each. - Each
newmakes a separate account, and its reference goes into one slot. Theint100 is widened to the constructor’sdoubleautomatically. - Both arguments are references (Week 4, section 7), so the method works on the very same account and array. The first account is in the array too and matches itself, so the count is 2.
- A method of the account itself:
thisis the account it was called on (accounton line 6) andotheris the one passed in. equalscompares the characters and returns aboolean;==only asks whether both are the same String object (section 4.4). Here==would happen to work, as identical literals share one object — names read from input would not.- Reading another account’s private field is allowed, because this code is inside
BankAccount. The same expression inRunnerwould not compile.
Two details: hasSameOwner may read other.customerName
even though it is private, because private means “only code in this class”,
not “only this object”. And Strings are compared with .equals, not
== — section 4.4 shows why. The Ed lesson’s own version of this method returns
a List, which you have not met in Java yet; it is the same idea.
Now try to put something else in the array:
- Every slot of
accountsmay hold only aBankAccountreference (ornull), and the compiler checks each assignment into it before anything runs. - A
BankAccountinto aBankAccountslot. The class writes no constructor, so Java’s default one, with no parameters, is used (section 4.2). - The compiler checks the right-hand side against the element type and refuses. ECJ: Type mismatch: cannot convert from String to BankAccount; Ed’s
javac: incompatible types: String cannot be converted to BankAccount. - No fields and no methods, yet
BankAccountis a type the compiler enforces as strictly asintorString. Because of line 5, nothing runs — not even line 4.
03static: the class itself
3.1One copy, shared by every object
Objects quickly become the main actors in a Java program, but the class is still a thing in its
own right. Anything marked static lives on the class: it exists without
any objects, and it is not copied into objects at all. Reach a static variable through an
object and you get the one class-level copy that every object shares:
- The variable belongs to the class
Sillyitself. It is created and set to 10 beforemaineven starts, with no object in existence, and it is never copied into objects. aandbrefer to two separate objects, butSillyhas no instance variables, so neither object holds avalueof its own.- The compiler uses only the type of
a,Silly, to find the field — the one class-levelvalue, still 10. The object itself plays no part. - There is no “
b’s value” to change: this assigns 30 to the single shared copy, exactly asSilly.value = 30would. - Now 30, although nothing was done to
a:a.valueandb.valueare two names for one variable. ClassName.membersays plainly that the variable is static. It needs no object at all:Silly.valueworks even in a program that never saysnew Silly().
The Java Language Specification: for a static field “there exists exactly one
incarnation of the field, no matter how many instances (possibly zero) of the class may eventually
be created”. If every object of a class should share one value, this is how; it is also how to
define program-wide constants, as static final — like the pizzas array in
Week 4’s pizza shop.
a.value looks as though it belongs to a. It does not. Write
Silly.value for static members, so the code says what is really happening.
3.2Static methods
The class also provides all the static methods. main is the obvious one
— it runs first, before any object exists. But any method that needs nothing beyond its
parameters (and other static data) can be static and used without creating an object:
- No
static, so everyCircleobject gets its ownx,yandradius(three declared in one statement). They have no memory until an object is made. static: one copy, on the class;final: it can never change;public: other classes may read it asCircle.PI. Capitals are the naming convention for constants.- It would run once for each
new Circle(…), copying its arguments into that object’s variables.mainnever creates aCircle, so it never runs in this program. - No
static: it runs for one particular circle, so it can read that circle’sradius. It would be called on an object, asc.area(). - An instance method may call a static one freely. One
doubleargument matchesarea(double)on line 17, not this method itself: overloading again. - The static
areauses nothing but its parameter and the staticPI, so it needs no object. Itsradiusis the parameter, a new local variable, not any circle’s field. mainis static too, so it calls the staticareadirectly. Theint5 is widened to5.0to fitdouble radius;area()takes no arguments, so it is not a candidate.
x, y and radius need an object before they have any memory or
value, but the static constant PI and the static area(double) can be used with
no Circle in existence. The instance method area() simply hands its own radius
to the static one — two methods called area, told apart by their parameter lists:
Week 4’s overloading again.
Java also provides a far better π as the static constant Math.PI
— another class used without ever creating an object of it.
3.3What a static method cannot see
A static method runs on the class, with no particular object — so there is no
radius for it to read. The compiler says so:
- An instance variable: it exists only inside a
Circleobject, one copy per object, and only once that object has been created. - The constructor would give one circle its
radius, butmainnever callsnew Circle(…). Even if the compiler allowed line 10, there would be no radius to read. - A static method runs on the class itself, not for any particular circle, so there is no
this— and no object whose variables it could read. - Static code naming an instance variable. The compiler rejects each use of
radiuson this line: this console says Cannot make a static reference to the non-static field radius; javac says non-static variable radius cannot be referenced from a static context. - Calling a static method from
mainis fine; the trouble is insidearea. Either dropstaticand callnew Circle(5).area(), or giveareaa parameter, like the staticarea(double radius)in the previous example.
| Member | Lives on | Needs an object? | Can use |
|---|---|---|---|
static variable | the class — one copy | no | — |
| instance variable | each object — one copy per object | yes | — |
static method | the class | no: Circle.area(5) | its parameters and static members only |
| instance method | the class, run for one object | yes: c.area() | everything, including that object’s variables via this |
This is the same rule as Week 4’s Cannot make a static reference to the non-static method, now for a variable.
04Constructors, this, and objects in memory
4.1What an object is, in memory
In Java all objects are created dynamically, so they always live on the
heap (Week 4, section 6.3). A new object gets space only
for the class’s non-static variables, plus a little bookkeeping. The methods, the
static variables and everything else shared by all objects of the class are stored once,
with the class — the JVM specification calls that area the method area, which
stores “per-class structures such as the run-time constant pool, field and method data, and the
code for methods and constructors”.
An object variable is then just a reference to where that data sits on the heap — which is exactly what made Week 4’s arrays behave as they did when passed to methods.
new Circle(6, 7, 3) and new Circle() in section 4.3: two
objects, each with its own four variables, and one class holding the shared
PI and the code of every method. (Each colour is itself a reference, to a
String elsewhere on the heap.)4.2new and constructors
You have written this line in every program that reads input:
- The type is the class
Scannerand the name issc. The variable will hold a reference to a Scanner object on the heap, not the object itself. newasks the JVM for enough heap memory for oneScanner, then runs a constructor on it. The whole expression’s value is the reference to the new object, which is stored insc.- The class name with an argument list chooses the constructor.
System.inis the program’s standard input stream, so this is the Scanner constructor that takes an input stream, and the new object is set up to read from it.
Type, variable name, then an initialisation that creates an object on the spot.
new tells the JVM to allocate the right amount of heap memory for a Scanner and
run a special method called a constructor — here the one that takes
System.in. A constructor is where the object’s data members are normally initialised
and anything else it needs is prepared. It runs once per object, and always
first.
public class <ClassName> {
<access modifier> <ClassName>(<parameter list>) {
<code>
}
}- An access modifier and a parameter list, like any method — but no return
type, not even
void. - Its name is the class’s name, so it is one of the few method names that starts with a capital letter.
- A class can have several constructors, following the normal overloading rules.
- If you write no constructor, Java supplies a default
constructor that takes no parameters and does nothing beyond creating the object. That is why
new Empty()worked in section 2.2.
The catch in that last rule: no constructor. Write one of your own and the free one is gone:
- The error line.
new Point()asks for a constructor with no parameters, butPointdeclares onlyPoint(int, int), so the program does not compile (this console: The constructor Point() is undefined). Either pass two ints, as innew Point(0, 0), or declare aPoint()yourself. - Declares two
intdata members in one line. EachPointobject gets its ownxandy, and only code insidePointcan reach them. - Named after the class, with no return type — not even
void. Because the class now declares a constructor, Java no longer supplies the free defaultPoint(), and that is what breaks line 3. - Inside the constructor, a plain
xmeans the parameter, which hides the field of the same name.this.xis the field of the object being built, so this line copies the argument into it (section 4.3).
Ed’s javac words it as constructor Point in class Point cannot
be applied to given types. The fix is to declare a Point() yourself, if a point with no
coordinates makes sense — which is where the next section’s trick comes in.
4.3this
An object can always get a reference to itself with the keyword this. It has two
handy uses:
- Telling a field from a parameter with the same name:
this.x = x;sets the object’sxto the value of the parameterx. Common practice in constructors. - Calling another constructor:
this(0, 0, 0);runs a different constructor of the same class. It must be the first line of the calling constructor. This lets a short constructor fill in defaults without repeating code.
static: a single copy, stored with the class rather than in each object.final: it can never be changed. The value is a poor π —Math.PIis the one to use.- Lines 5–8 are not
static, so everyCirclegets its own four variables on the heap. Until a constructor sets them, the numbers are0.0andcolourisnull. - The class’s name and no return type make it a constructor. The comment calls it the default constructor, but strictly that name belongs to the one Java supplies when you write none (4.2); this one is written by hand.
this(…)runs the constructor of this class whose parameters fit the arguments — three numbers, so line 16 — with theintzeros widened todouble. It must be the first statement in the constructor.- Passes its own three parameters on, adding a default colour, to the four-parameter constructor. Writing the default here means no caller has to supply it.
- The parameter
xhides the fieldx, sothis.xis needed to reach the object’s field. Written asx = x;, the parameter would be assigned to itself and the field would stay0.0. - The instance method passes this object’s
radiusto the staticarea(double). The twoareas are overloads, told apart by their parameter lists (Week 4, 3.3). - A getter: a public method that lets other code read a private field. No parameter is called
x, soreturn x;would mean exactly the same; thethis.only makes it explicit. - Picks the constructor on line 16, which passes everything on to line 21 with
"Colourless". Theintarguments are widened todouble, so the object holds 6.0, 7.0 and 3.0. - Runs
area()on the objectcrefers to, using its radius of 3.0. The result, 3.141529 × 9, prints as28.273760999999997because adoublecannot hold most decimals exactly. - Line 11 calls line 16 with zeros, which calls line 21 with
"Colourless". Only line 21 sets fields, sodends up at (0.0, 0.0) with radius 0.0 — one place to get it right.
Follow new Circle(): it calls the three-parameter constructor with
zeros, which calls the four-parameter one with "Colourless". Only one constructor actually
sets fields, so there is one place to get it right. (This PI, 3.141529, is the Ed
lesson’s own “approximation” — it even has two digits swapped compared with
section 3.2’s. One more reason to use Math.PI.)
4.4Two variables, one object — again
Because an object variable is a reference, everything Week 4 showed for arrays holds for objects you design yourself. Copy the variable and you have two names for one account; pass it to a method and the method can change the account through it:
- A class is a type (2.4), so it can be a parameter’s type. The call copies the reference into
account, so it refers to the caller’s object; the account itself is never copied. - The inner call runs first:
getBalance()returns the balance, and 10% of it goes todeposit. That changes the objectaccountrefers to — the same object asmain’sa. newmakes the object on the heap and the constructor on line 25 sets its two fields; theint100 is widened to100.0.areceives a reference to it.- No
new, so no new account:bnow refers to the very objectarefers to — Week 4’s “backup” array again. - A second
newmakes a separate object with the same starting data. Nothing done throughaorbcan reach it, so it keeps its100.0. - Deposits into the one object that
aandbshare, so it holds 150.0. Line 13’s interest adds 15.0 more, and line 14 shows 165.0 for both names. ==on object variables compares the references, not the contents.aandbrefer to the same object, so it istrue.- Both accounts began as Linh with 100.0, but they are two objects in two places on the heap, so
==givesfalse. Comparing contents would need a method written for the job.
== on objects compares references
a == c is false although the two accounts started identical: they are two
different objects, and == asks “are these the same object?”. That is
also why two Strings are compared with .equals — a String is an object, and two
Strings with the same letters need not be the same object. Enums, in section 7, are the pleasant
exception.
4.5The pizza shop, with classes
The Ed lesson finishes by rebuilding the pizza shop with classes, over four video parts and forkable
workspaces. Here is one way to do it: a Pizza that knows its name and price (in cents
— section 1.4), and an Order that holds pizzas and can total itself. Enter three
selections, then 0 to finish:
- One menu for the whole class (
static), assigned once (final). Its element type is the classPizza, so each element is a reference to a Pizza object. - Each
newrunsPizza’s constructor (line 36). A static variable is initialised along with its class, so the three pizzas exist beforemainstarts. Prices are whole cents (section 1.4). MENU[i]is a reference to one Pizza; the dot follows it and calls that object’sdescribe(), which returns its name and price as one String.- Runs
Order’s constructor (line 60) with capacity 10, which sets up an empty array for the pizzas and a count of 0. - Choices 1–3 become indices 0–2.
addstores the reference to the menu’s Pizza, so choosing 2 twice puts two references to the same object in the order. order.total()returns the sum in cents, andPizza.dollarsformats it.dollarsisstatic, so it is called on the class name, with no Pizza object involved.- The parameter has the field’s name, so
this.namemeans the new object’s field and plainnamethe argument passed to the constructor. - Inside the class the static
dollarsneeds no class name. An instance method can call a static one freely; the reverse needs an object (3.3). %gives the remainder after dividing by 100 — the cents — and adding it to""turns the number into a String. Line 52’scents / 100is integer division, giving the whole dollars.- Pads a single-digit cents value with a leading zero, so 1205 shows as
$12.05rather than the misleading$12.5. - Makes an array of 10
Pizzareferences, every onenull. No Pizza object is created here;addfills the slots later. - The loop stops at
count, notpizzas.length: the unused slots are stillnull, and callinggetPrice()onnullwould throw aNullPointerException.
Read main and notice who does what: the shop never adds prices up
itself — it asks the order — and never formats money itself — it asks the static
helper on Pizza. Two orders of the same pizza hold two references to one
Pizza object from the menu, which is fine: nobody changes it.
05Classes and objects in Python
5.1Roughly Java’s model
Python’s object model is roughly similar to Java’s: a class is a template, objects are instances of it, and the class itself is still a thing in the code, so data and functions can live on the object or on the class.
And since every Python variable is already a reference (Week 4, section 7.4), two variables can hold references to the same object and change the same object — exactly like the Java accounts in section 4.4.
5.2Classes and objects
A class is defined much as you would guess: class, a name, a colon, and indentation.
class, a name and a colon; every indented line below belongs to the class. There are no braces and nopublic, and Python does not tie the class to a file name as Java does.- An assignment directly in the class body creates a variable on the class itself — one copy, like a Java
staticfield (3.1), not one per object. - Written like any function, but indented inside the class, so it belongs to it. With no parameters it can only be called through the class, as on line 11 — a static method, in 5.3’s terms.
- Back at the left margin, so this line is outside the class — and it is the first to print, because running the
classstatement only made the class, without callingfunc. - The dot reaches an attribute of the class directly, with no object made — like Java’s
Silly.valuein 3.1. - Calls
funcwith no arguments, matching its empty parameter list. Calling it through an object instead fails — that is the “Forgot self” example below.
Objects are created by calling the class like a function, with whatever arguments it needs —
no new:
- An instance method’s first parameter receives the object the method was called on. It plays the part of Java’s
this, but is written out in the parameter list; the nameselfis only a convention, though a universal one. - Calling the class like a function creates a new
Fooobject and returns a reference to it, whichobjthen holds — nonew. No arguments are needed, becauseFoohas no__init__asking for any (5.4). - The brackets are empty, yet
barhas one parameter: Python passesobjin asself, so this does the same asFoo.bar(obj). Without an object,Foo.bar()fails:TypeError: Foo.bar() missing 1 required positional argument: 'self'.
5.3Attributes, and three kinds of method
Python’s general word for a class’s data and functions is attributes, reached with the dot, just as in Java. Once functions live on a class or an object they are called methods, and there are three kinds:
| Kind | Declared | First parameter | Called as | Closest Java |
|---|---|---|---|---|
| static method | a plain function in the class, optionally marked @staticmethod | none | Foo.baz(x) | a static method |
| class method | @classmethod | cls — the class, supplied automatically | Foo.bar() | a static method that uses static data |
| instance method | no marker | self — the object, supplied automatically | obj.bar() | an ordinary method; self is like this |
- One copy, on the class, which line 14 reads through
cls. The double quotes delimit the string; the single quotes are part of its text, so they show in the output. - No marker and no parameters, so it works as a static method when called through the class, as on line 20. Through an object,
obj.bar()would fail, because Python would pass the object in with nowhere to put it. - A line starting with
@modifies thedefbelow it.@staticmethodtells Python never to pass in the object or the class, sobazcan safely be called either way:obj.baz(5)works too. thinghas no type, so the caller may pass anything;strturns it into text before+joins it. Without it,Foo.baz(42)would raise aTypeError.- With
@classmethod, Python passes the class itself as the first argument, namedclsby convention — whether the call goes through the class or through an object. - Here
clsisFoo, so this reads the attribute from line 3. Reaching the class’s data throughclsis what a class method is for — the table’s “static method that uses static data”. - No marker, and a first parameter that receives the object. It is the only one of the three kinds that needs an object: through the class,
Foo.hello()fails withTypeError: Foo.hello() missing 1 required positional argument: 'self'. - No argument is written, yet
showhas one parameter: Python fillsclswithFoo, the class before the dot. - In the same way, Python fills
selfwithobj, the object before the dot. Line 25 had to create that object first, by calling the class.
Why must an instance method take self? Because calling a method through an
object passes that object in as the first argument. A function with no parameter has nowhere to
put it:
- An empty parameter list:
funccan accept no arguments at all. Called through the class,Simple_Class.func(), it works as in 5.2; to call it through an object it needsdef func(self):. - Creating the object works; nothing goes wrong until a method is called through it. Python only finds the problem at run time, on line 7.
- Calling through an object passes
objin as the first argument, sofuncreceives one argument though the brackets are empty. Python stops withTypeError: Simple_Class.func() takes 0 positional arguments but 1 was given— the 1 isobj.
The word “static” is being used slightly differently from Java: the intent is the same (no object needed), the details are not. What Java calls a static method is often closer to Python’s class method.
5.4__init__ and instance data
Unlike Java, Python instance data is generally not declared in the class. It is
created in the __init__ method — Python’s equivalent of a constructor, and an
instance method — by assigning through self, and read back through self
too. Every class has an __init__, even if you do not write one.
- Python calls
__init__automatically on each new object, straight after creating it, with the object asself. You do not call it yourself:Foo()on line 9 does. - Assigning through
selfcreates an attribute on this object — there is no declaration anywhere else. EachFooobject gets its ownsecret, unlike a class attribute. selfalways comes first; the caller’s arguments fill the parameters after it, so line 10’s single argument goes tonum.- The object’s data is reached only through
self: Java’sthis.is optional, Python’sself.is not. A baresecrethere givesNameError: name 'secret' is not defined. - The new object is passed to
__init__asself, which gives it itssecret. No argument is written because__init__has no parameter besidesself. objgoes in asselfand2asnum; the method returns2 * 3, andprintshows that value.
With parameters, __init__ does exactly the job of section 2.3’s Java constructor.
The same bank account, in Python:
- The job of Java’s
BankAccount(String customerName, double startingBalance), withselfadded at the front and no types. Callers supply only the two afterself. - The same move as Java’s
this.customerName = customerName;: the left side is the object’s attribute, the right side the parameter. Hereself.cannot be left out —customer_name = customer_namewould only assign the parameter to itself. - Java declared
private double balance;at the top of the class; in Python this assignment is what creates the attribute, and nothing makes it private. - Line 8 lets only positive amounts through, as in Java’s
deposit.selfis whichever account the method was called on, so only that object’s balance changes. - Calling the class makes the object and runs
__init__: the new object goes in asself, then"Linh"and100.0fillcustomer_nameandstarting_balance. accountgoes in asselfand50asamount. Anintadded to thefloat100.0 gives afloat, which is why the balance shows as150.0.- Python attributes are public, so code outside the class reads
balancedirectly, where Java’sprivatefield neededgetBalance(). Writing it is just as open, as the note below says.
Nothing stops account.balance = 1000000 here: in Python, attributes
are public unless you say otherwise. Section 6.3 is what “otherwise” looks like.
06Dunder methods and open doors
6.1“Dunder” methods
Python has methods with special names, surrounded by double underscores
— hence “dunder”, also called magic or special methods. They connect a class to the
built-in functions and operators of the language. __init__ is one: it hooks into object
creation.
Another is __str__, the method str() calls — and print
too — much like Java’s toString(). Each dunder has its own rules:
__init__ may take extra parameters and returns nothing; __str__ takes only
self and must return a string.
Money(1250)makes a new object and calls__init__on it, withselfas the new object andcentsas 1250. Keeping whole cents in anintavoids floating-point rounding.- Called by
str(), and so byprint, whenever aMoneymust become text. It takes no parameter butselfand must return astr. //is floor division, so 1250//100 is 12.str()turns theintinto a string so+can join it to the others.%gives the cents left over (5 for 305), and the string methodzfill(2)pads with leading zeros to two characters, giving05rather than5.- For
a + bPython callsa.__add__(b):selfis the left operand,otherthe right. It returns a brand-newMoney, leaving both operands unchanged. printconverts its argument withstr(), which callsMoney.__str__. Without a__str__you would see something like<__main__.Money object at 0x…>.- Gives a real string, so
+here is string concatenation. Withoutstr(),drink + " extra"would call__add__with a string, which has nocents. - Becomes
pizza.__add__(drink), which returnsMoney(1555);printthen shows it through__str__.
Dunders are the standard way to give your own type built-in behaviour:
__add__ for +, __lt__ for <, and many more. (Java
has no equivalent for operators: + on your own class is simply a compile error.)
6.2Horrible things you can do in Python
At a simple level, a Python class’s attributes behave like a dict, and access is
public by default — so other code can replace them, if it knows the names. Offered without further
comment, from the Ed lesson:
- Assigned in the class body, so it belongs to the class
mitself, not to any object, and is reached asm.valorcls.val. - No
selfand nocls, so in section 5.3’s terms this is a static method:m.f()works, but calling it through an object would fail, since there is no parameter for the object. - The decorator makes Python pass the class itself as the first argument,
cls, som.f1()returnsm.val— 9 — without needing an object. - An annotation saying the method returns an
int. It documents intent only: Python does not check it. - Methods are just attributes of the class, so this assignment rebinds the name
fto the functionthree. The originalfis gone, andm.f()now runsthree. - The class method is replaced by a plain function, so the
@classmethodbehaviour goes too:m.f1()now callsthreewith no arguments. - Assigning to a name the class never had simply creates it, at run time. In Java, using an undeclared field like this is a compile error.
A method replaced from outside the class, and a brand-new attribute bolted on at run time. The Java compiler would reject every one of these lines. Python lets you — which is precisely why you should not.
6.3How to stop (some of) that
Python gives you a way to discourage this. Unless you say otherwise an attribute is public. Start its name with two underscores and code outside the class can no longer reach it by that name:
- A class attribute whose name starts with two underscores (and does not end with two). Inside the class body Python stores it under another name — the last example in this section shows which.
- Outside the class nothing is renamed, so Python looks for an attribute literally called
__priv_data, finds none, and stops with anAttributeError.
It works for methods too — but not for dunder methods, even though they look similar, because a dunder name also ends in two underscores:
- The name ends in two underscores as well, so Python does not rename it. It also breaks the
__str__rule by having noself:print(Foo())would fail with aTypeError. - Two leading underscores but not two trailing ones, so inside the class Python stores this method as
_Foo__hidden. - Looks up
__str__on the class and calls it with no arguments, which suits a method with no parameters. - The lookup itself fails, before any call: the class has no attribute called
__hidden, so Python raises anAttributeError.
Now look back at the error from the first example: Python itself suggests _Foo__priv_data. The two
underscores do not make the attribute private in Java’s sense; inside the class, Python quietly
renames it (“name mangling”), and the new name works from anywhere:
- Inside class
Foo, Python rewrites__priv_dataas_Foo__priv_data: one underscore, the class name, then the original name. - The mangled name is an ordinary attribute name, so any code can use it. The renaming exists to avoid clashes with names in subclasses, not to stop access.
“Private” instance variables that cannot be accessed except from inside an object don’t exist in Python.
The Python Tutorial, §9.6 Private VariablesSo the double underscore is a strong “keep out” sign, not a lock —
which matches section 1.3’s point that encapsulation is about safety, not security. A
single leading underscore (_spam) is the other convention, which the
tutorial describes as marking a non-public part of the code; the Ed lesson calls this
“protected” and leaves it for later. Neither stops you adding brand-new attributes at run
time.
07Enums
7.1Enumerated types
An enumerated type is a data type with a fixed, finite set of named values. There are many ways to implement one, but the goal is always the same: data of that type can only ever be one of the known values. They appear in most languages descended from Algol — Pascal, C, C++, C#, Java — and in Swift, Fortran, Lisp, TypeScript and every functional language with algebraic types.
The classic example: a standard deck has exactly four suits, so any question about a card’s suit must be answered with one of those four, and nothing else.
7.2Enums in Java
enum <Name> { [<VALUE_NAME> [, <VALUE_NAME>]*]; }The keyword enum, a name (capitalised like a class), then a comma-separated —
possibly empty — list of value names ending in a semicolon. By convention the values are in
capitals, like constants. An enum can be declared on its own or inside a class. A value is reached as
Suit.HEARTS, like a field, and every enum has a values() method that works with
the for-each loop:
- The keyword
enumdeclares a new type,Suit, whose values are exactly the ones listed. Declared insideCards, it can be used anywhere in the class, like any other type. - Capitals by convention, as for constants, with a semicolon after the list. The order written here is the order
values()hands them back in. - Declared like an
intorStringvariable, but it can hold only aSuit. The value is reached through the type’s name, like a field;trumps = 3;would not compile. - Joining a value to a string calls its
toString(), which by default gives the value’s name exactly as declared,SPADES. - Every enum has a
values()method that returns aSuit[]holding all its values in declaration order — just what the for-each loop needs. - There is only ever one
SPADESobject, so comparing references with==correctly answers “is this the same suit?” (section 7.3). - If the comparison is true, the bracket’s value is
" <- trumps"; otherwise it is the empty string, so only the trumps line gets the arrow. - Because
values()returns an array,.length— no brackets, it is not a method — gives the number of values, 4.
Enums arrived in Java 5. ? : is the conditional operator from the Weeks
2+3 operator tables — a tiny if/else that produces a value.
Before Java 5, the standard way to write an enumerated type was a row of int
constants: public static final int SEASON_WINTER = 0; and so on. The Java 5 release notes
list what was wrong with it, starting with “Not typesafe” — any int could
be passed where a season was expected, and two seasons could be added together. Enums fixed that by
making each set of values a type of its own.
Source:
Oracle — Enums (J2SE 5.0 guide).
7.3== is safe on enums
Java enums are implemented as a special kind of class: think of a class with a fixed number of pre-declared objects — the values. You cannot create more (or fewer) of them, so you never need a constructor call, and there is exactly one object for each value. The Java Language Specification: “An enum class has no instances other than those defined by its enum constants.”
So, unlike the accounts in section 4.4, comparing with == works as you would naively expect
— for enums, .equals gives the same answer as comparing references, because there are
never two copies of the same value.
7.4Switching on an enum
You can switch on an enum value. Inside the switch, the cases are written without the
enum’s name:
- The number read becomes an index into the array of values, in declaration order, so 2 gives
DIAMONDS. A number outside 0–3 throws anArrayIndexOutOfBoundsException. - Allowed since Java 5. This classic switch statement jumps to the case matching the value of
s, and it does not check that every suit has a case: leave one out and it still compiles. - The switch already knows
sis aSuit, so the case label is the bare nameHEARTS, without the enum’s name in front. case HEARTS:has no statements of its own, so it runs straight on into this label: hearts and diamonds share the sameprintln.- Jumps out of the switch. Without it, a red suit would fall through into the black cases and print both lines.
This is the classic switch statement from Weeks 2+3, stacked cases and all. It
does not check that you covered every value. Put a return in every case of a method and the
compiler still wants one more, because as far as it is concerned the switch might match nothing:
- The return type
Stringmeans every way through the method must end in areturnof aString. This page’s console flags the error on this line: the method must return a result of typeString. - A classic switch statement with no
defaultis treated as possibly matching nothing — the compiler does not count the cases against the four values ofSuit. - A
returnleaves the whole method at once, so these cases need nobreak. - As far as the compiler knows, the method could reach this closing brace without returning. javac reports
missing return statementat this brace. Addingdefault: return "unknown";fixes it. - This call would match
HEARTSperfectly well, but a compile error anywhere stops the whole program, so nothing runs.
Week 4’s missing return statement again. Add
default: return "unknown"; to satisfy it. The newer switch expressions in
section 8 are checked for exhaustiveness — one of the Ed lesson’s selling
points for them.
7.5Enums are classes, so they can have methods
Because an enum is a class, you can add methods to it — and each value is an object, so
this inside the method is “whichever value I am”. Here each suit knows its own
symbol and colour:
- Now the braces hold more than a list of values: after the values come ordinary method declarations, just as in a class.
- The values must come first. With methods following, the semicolon after them is required: it separates the values from the rest of the body.
- No
static, so the method belongs to each value: it is called on one, ass.symbol(), and works with that value. - Inside the method,
thisis the value it was called on, so switching onthispicks the symbol for whichever suit is asking. - The last suit is caught by
defaultrather thancase CLUBS, so every path ends in areturnand there is no missing return statement as in 7.4. truewhen this value is one of the two red suits. Inside the enum the values need noSuit.in front, and==is safe because each exists exactly once.- Dot notation, as with any object:
symbol()runs withthisset tosand returns that suit’s character. - The
booleanfromisRed()picks the word. The brackets matter: without them the whole joined string to the left would become the condition, and aStringis not aboolean, so it would not compile.
An enum can also hold fields and constructors of its own, which the Ed lesson mentions but does not go into. And one striking application: make a class an enum with a single value, and the compiler itself guarantees there is exactly one instance — the Singleton pattern, with no checking code at all.
7.6The deck of cards
The Ed lesson’s full example builds a short (Euchre) deck: two enums, a record for a
card, and switch expressions to print them. Records (Week 4) and switch expressions need Java 16
and 14, so it will not run in this page’s Java 8 console — here is its second version, with the
printing moved into the enums, and its real output. Run it on Ed.
- An annotation: this
toString()replaces the one every object inherits. The compiler checks the claim, so a misspelttostring()would be an error rather than a silent new method. - A switch expression (Java 14, section 8): the whole switch works out to one value, which is returned. That is why it ends with
};on line 18. - After the
->comes the value the switch produces forHEARTS. Arrow cases never fall through, so there is nobreak. - A switch expression must cover every possible value. These four cases cover all of
Suit, so the compiler accepts it without adefault— unlike the switch statement in 7.4. Leave a suit out and it will not compile. - Five values times four suits makes a 20-card deck. This order is the order
values()returns, so each suit prints as A, 10, J, Q, K. - A card is a suit and a value, held together in a Week 4 record. Because the components are enum types, a
Cardcan only ever hold a real suit and a real value. - Calls the two enums’
toString()methods, value first, so a card prints asA♥. Without this override the record’s own form,Card[suit=♥, value=A], would be used. - Seeded from the clock (milliseconds since 1970), so each run gets a different sequence of random numbers, and so a different shuffle.
- 4 suits × 5 values = 20 slots. Because it is worked out from the enums, adding a value to either would resize the deck automatically.
- The nested for-each loops visit every suit-and-value pair.
i++usesias the index and then adds 1, so the cards fill slots 0 to 19 in order. - Returns a random
intfrom 0 up to, but not including, 20 — always a valid index. Each card in turn is swapped with the one at this random position. - Printing an object calls its
toString(): hereDeck’s, whoseArrays.toStringbuilds the square-bracketed list from each card’stoString(), which in turn uses the enums’.
Check that all the cards are there: [A♥, 10♥, J♥, Q♥, K♥, A♤, 10♤, J♤, Q♤, K♤, A♦, 10♦, J♦, Q♦, K♦, A♧, 10♧, J♧, Q♧, K♧] Now we can shuffle them: [Q♧, J♦, A♤, Q♦, 10♧, K♥, A♥, 10♥, A♦, J♥, K♤, K♧, A♧, Q♥, J♧, K♦, 10♦, 10♤, Q♤, J♤]
@Override marks a method that replaces one the class would otherwise inherit — here,
the toString() every object has, which is what + and println call.
That is inheritance showing through, a lesson early. The same deck in Java 8 terms — a small
Card class instead of the record, classic switches instead of switch expressions — runs
right here:
- A switch statement, which Java 8 understands: each case
returns its symbol, so nobreakis needed. - A switch statement is not checked for completeness, so with four
cases the compiler would still report a missing return (7.4). Catching the last suit withdefaultguarantees every path returns. - A small class nested inside
Deck.staticmeans aCarddoes not belong to any particularDeckobject — a nested record is like that automatically. - Each record component becomes a field written out by hand:
privateso only this code can see it,finalso it is set once and never changed. - The record wrote this for us. It is named after the class, has no return type, and runs when
new Card(s, v)is called. - The parameter has the same name as the field:
this.suitis the field of the card being built,suitthe value passed in. - A plain class has no readable printed form of its own: without this method a card would print as something like
Deck$Card@1b6d3586. The enums’ owntoString()methods supplyAand♥. - The seed is the current time in milliseconds, so every run produces a different shuffle — run it twice and compare.
- Exactly as in the record version: the nested loops make each suit-and-value pair once, and
i++moves on to the next slot after each card. - Holds card
iwhile its slot is overwritten with the random card; line 69 then puts it in the random slot. Withouttemp, one of the two cards would be lost.
Run it twice: the shuffle changes, because the Random is seeded from the
clock. Compare the two versions line by line — everything the record and the arrows did is here
written out in full.
7.7Python enums (sort of)
Python has enums too, somewhere between Java’s approach and C’s. You import
Enum from the enum module and create your enum as a class that
extends it — inheritance again, a little early — listing names with values. The
example the Ed lesson takes from the Python documentation, with two lines added:
- Brings the class
Enumin from the standard library’senummodule; without it,Enumon line 3 would be an undefined name. - The base class in brackets means
ColourextendsEnum, which turns the names in the class body into enum members rather than ordinary class variables. - Each
NAME = valueline makes one member. Unlike Java, you choose the value and it can be anything; 1 to n is just the usual habit. - Reached through the class, like an attribute.
cnow refers to the member objectColour.RED, not to the integer 1. printuses the member’s string form, which by default is the class name and member name,Colour.RED, not the value. The next example replaces it.- Every member carries both:
.nameis the string'RED', and.valueis whatever it was given in the class, here the integer 1. - There is only one
Colour.REDobject, so this isTrue;c is Colour.REDwould beTrueas well. - A member is not equal to its value, so this is
False. Compare withc.valueinstead, or go from the number to the member withColour(1).
As they are classes, you can add methods — here the dunder __str__ from section 6.1,
which print then uses:
- Names given values become members, but a
defin the body is an ordinary method.__str__is the dunder thatprintandstr()use for the text form, so this replaces the defaultColour.RED. - The member the method was called on — Python’s version of Java’s
thisin section 7.5. self.nameis the member’s name, such as'RED'; the string methodlower()returns a new lower-case copy,'red', leaving the name itself unchanged.printasks the member for its string form, which now runs our__str__withselfset toColour.RED.
The differences from Java are in the values. They can be anything — you would
normally see the integers 1 to n, but RED = "red", GREEN = 2,
BLUE = 3.0 is allowed. And nothing stops two names sharing a value, which quietly changes
what the names mean:
- The first name with the value 1 creates a member,
RED. - The value 1 is taken, so no new member is made:
GREENbecomes a second name forRED. Python raises no error unless the class is marked with the@uniquedecorator fromenum. - Gives back the member the alias stands for,
RED, socrefers toColour.RED. - The printed form uses the member’s own name, so it shows
Colour.REDeven though we asked forGREEN;c.nameis'RED'too. isisTrueonly when both sides are the same object — and they are, becauseGREENandREDname one member.lencounts members, and aliases do not count, so the “three-colour” enum has length 1. Aforloop overColourwould likewise visit onlyRED.
A second name with an existing value becomes an alias for the first
member, so this “three-colour” enum has one member, RED, with two extra names.
The Ed lesson’s conclusion stands: Python enums work well when used as intended, without the
guarantees of Java’s. (The members are objects of the enum class, though:
isinstance(Colour.RED, Colour) is True, as the Python enum HOWTO shows.)
7.8And in C, just numbers
For comparison: an enum in C is a set of names for the numbers 0 to (number of values − 1),
assigned automatically. That keeps the key property — a fixed set of distinct values — but not
Java’s type safety: underneath, it is just an int.
- C numbers the names automatically, in order:
HEARTSis 0,SPADES1,DIAMONDS2,CLUBS3. They are plainintconstants, used with noSuitin front. - In C the type is called
enum Suit, both words, every time it is used. Declared outside any function,sis a global variable, which C starts at 0. SPADESis the number 1. C would accepts = 42;just as happily — nothing tiessto the four suits, which is the type safety Java’s enums add.%dprints anintin decimal, andsis one underneath, so out comes its number rather than a name. C has no built-in way to turn an enum value back into its name.
1
08Advanced switch
Weeks 2+3 taught the switch statement: equality only, fall-through, and
break. Java has since added three things: switch expressions,
pattern matching, and when clauses. All three need a newer
Java than this page’s console — section 8.2 shows exactly what happens if you try — so
most examples here are static, with their real output from a current JDK. Run them on Ed, which uses
Java 23.
8.1Stretching the old switch
A plain switch only tests equality, but careful choice of the expression you switch on can stretch it. This runs anywhere, including here:
- The
ScannermethodnextIntreads the next whole number typed and returns it as anint, ready to switch on. - Works out the expression in brackets once, then jumps to the case whose value is equal to it. That single calculation is the only work done before choosing.
%gives the remainder after dividing by 2, which is 0 for every even number. Switching on it turns “isneven?” into an equality test, the only test a plain switch can do.- Matches only when
n % 2equals 0 exactly. A plain case label must be a constant, so a condition such ascase n > 0:is not allowed. - Jumps out of the switch. Without it an even number would fall through into the
defaultcode and print both lines (Weeks 2+3, section 3). - Runs when no case matched. It is better than
case 1:here: for a negative odd number,n % 2is −1 in Java, anddefaultcatches that too.
But the only computation happens once, in the switch’s own expression. The newer features let each case do some of the work.
8.2Why the rest will not run on this page
Here is a switch expression — section 8.3 explains it — in the page’s console. Run it and read the error. It is not a mistake in the program; it is the compiler telling you which version of Java you need:
- Joining a switch to a string with
+works only because a switch expression has a value, here"even"or"odd". Section 8.3 explains how. - ECJ, this console’s compiler, is set to Java 8 and underlines the whole switch:
Switch Expressions are supported from Java 14 onwards only. Nothing is wrong with the program itself. - The arrow form
case … ->belongs to the same Java 14 feature, so ECJ flags it separately:Arrow in case statement supported from Java 14 onwards only. - The same error for the second arrow — three errors in all, each naming the Java version needed rather than a mistake to fix.
- A compile error means nothing runs, not even the prompt on line 6. On Java 14 or later, such as Ed’s Java 23, the input 7 makes this line print
The number is odd.
8.3Switch expressions
A switch expression is like a switch statement, except that each case produces a
value. The syntax changes to case … -> <value>;: an arrow
where you would expect a colon, and no break. The switch hands back the value
after the arrow and is finished — no fall-through, much as a return ends a method. And
because it produces a value, it can go anywhere an expression can (Weeks 2+3, section 1.2):
- Here the switch is an operand of
+: it produces aString, which is joined to"The number is ". A switch statement could never stand in this spot. - If
n % 2is 0, the whole switch has the value"even"and is finished: nobreak, and no fall-through into the next case. - A switch expression must be exhaustive, and an
inthas far more values than 0, sodefaultis required. Replace it withcase 1and javac reports that the switch expression does not cover all possible input values.
Enter a number: The number is odd
Switch expressions must also be exhaustive: every possible value has to be covered.
On an enum that means every constant — which is how the Ed deck’s toString in
section 7.6 gets away with no default, and why deleting a case there is a compile error rather
than a silent bug. Compare section 7.4’s old-style switch, which needed a default anyway.
Switch expressions are older than they look. They were previewed in Java 12 and 13 and became a standard feature in Java 14, in 2020 (JEP 361). The Ed lesson presents them alongside newer features that they combine with. Source: JEP 361 — Switch Expressions.
8.4Pattern matching and when
Pattern matching lets a case examine the type and structure of the value being
switched on, and give it a name. A when clause then narrows a case to a
sub-case with any boolean condition:
nextLinereturns aString, which anObjectvariable can hold. Declared asObject,ncould in principle be anything, so the switch has to find out what it is.- The old switch accepted only a few types, such as
int,char,Stringand enums. With pattern matching (Java 21) it can switch on any object. - Matches if
nis aStringand, if so, names its, with typeString, for this case only — so String methods can be called on it. - Narrows the case: it matches only if the boolean condition after
whenis also true.length()returns the number of characters, 0 for an empty line. - Colon cases still fall through, so the breaks stay. Leaving this one out is a compile error: javac will not let execution fall through into a case that declares a pattern variable like
s. - Every String the first case did not take lands here. Put this case first and javac refuses the program, since the guarded case could never match:
this case label is dominated by a preceding case label. - A switch that uses patterns must cover every possible value, and
ncould be anyObject. Remove thedefaultand javac says the switch statement does not cover all possible input values.
Enter a name: You didn't enter a name! Enter a name: The name you entered was Linh
case String s matches if n is a String, and names it s for that
case; when s.length() == 0 restricts the first case to the empty ones. Pattern matching works
on objects — it is why n is declared as Object here (the most general type;
more when inheritance arrives). Matching on primitive types is still a preview feature.
Python’s match (Weeks 2+3, section 4.3) has the same
thing, called a guard: if after the pattern. And unlike the Java above, it
runs in this page’s console:
- Shows the prompt, waits for a line and returns what was typed as a
str, without the newline. Pressing Enter alone gives the empty string''. - Tries each
casepattern againstname, top to bottom, and runs only the first that matches. There is no fall-through and nobreak. - A bare name matches any value and binds it to
s— like Java’scase String s, but with no type check. - Python’s version of
when: the case matches only if this condition is also true.lengives the number of characters, 0 for an empty line. - With no guard this pattern matches anything, so it must be the last case — Python rejects any case after it with a
SyntaxError. It does the job of Java’sdefault.
8.5Record patterns, _, and a switch with no default
A tantalising hint of how far this goes, using a record from Week 4:
- A name and a mark, with a constructor and component methods written for you. Because its components are known, a pattern can take it apart again.
- The parameter is an
Object, sogradeaccepts aStudentor anything else; the switch works out which it got. Line 25 passes itluke. - The switch produces a
String, andreturnhands it straight back. The;after its closing brace on line 16 ends thereturnstatement. - Matches if
ois aStudentand deconstructs it: the mark component goes straight into a new variablemark, with no need to writemark(). - Cases are tried in order, so a mark over 100 is caught here, before
mark >= 85on the next line could claim it. - The name component must be listed to match a
Student’s shape, but grading never uses it, so it gets the unnamed variable_(Java 22) instead of a name. - Reaching this case means the mark is neither over 100 nor 85 or more, so
>= 75here really means 75 to 84 — just like anelse ifladder. - No
when, so it matches anyStudentstill left: by now, any mark below 50, negative ones included. - Matches any object at all, so together the cases cover every possible value of
o. The switch is exhaustive, as a switch expression must be, with nodefault.
What mark should Luke get? Luke got a HD. What mark should Luke get? Luke got a caution for cheating. What mark should Luke get? Luke got a Z.
- Deconstruction:
Student(String _, int mark)matches aStudentand pulls its components out, straight into variables. _means “I don’t care about this one” — the name is never used, so it is never named.- No
default:case Object _matches anything left, so the compiler can see the switch is exhaustive. - The cases are tried in order, so 105 hits the first one before it could reach
>= 85— the same ordering rule as anelse ifladder in Weeks 2+3.
The Ed lesson says these became standard “as of Java 23”, the version Ed runs. To be
precise: pattern matching for switch and record patterns became standard in Java
21 (JEP 441, JEP 440), and the _ unnamed variable in Java 22 (JEP
456). Everything here works on Ed’s Java 23.
09Coding exercises
Eight exercises, marked automatically against test cases — the same idea as the Ed labs, but nothing here is recorded or submitted. Write your answer, press Run tests, and read what comes back. A worked solution sits under each one, but try it first.
A capital letter or a missing full stop fails the test, exactly as it would on Ed. Trailing spaces and the final newline are the only things forgiven.
A program that prints the right thing without the class the brief asks for will pass — and
miss the point. Each brief names the class, its private data and its methods; write them, and use
them from main. The Java exercises put two classes in one file, like section 1.3: only the
one with main is public.
10Team activity
Three or four people, fifteen minutes, then two minutes to report back. These run in the lecture and in the lab — class activities like these count towards the participation side of your mark.
Nouns and verbs: design a library
A small library lends books to members. A member can borrow at most three books; a book is either on the shelf or on loan to exactly one member.
- Underline the nouns. Which ones deserve to be classes, and which are just data inside a class?
- For each class, list its
privatedata and its public methods. Which class should enforce “at most three books”? - Is there a fixed, finite set of values anywhere in the description? Should it be an enum?
Report back: the rule your group moved from one class to another, and why.
Break the encapsulation
Take section 2.3’s BankAccount and make balance public.
- Write the shortest
mainyou can that leaves an account in a state the methods would never allow. - Now make it
privateagain. Could the same bug still happen? From where? - Do the same with section 5.4’s Python class, then with a double-underscore name. What did the underscores actually stop?
Report back: the worst realistic bug that private prevents.
int constants versus an enum
Someone writes static final int HEARTS = 0, SPADES = 1, DIAMONDS = 2, CLUBS = 3; instead of
an enum.
- Write a line that compiles with the
intversion but is nonsense. Would it compile with the enum? - What does
System.out.printlnprint for a suit in each version? - Section 7.7’s Python enum with duplicate values: which of your problems does it share?
Report back: the one-sentence case for enums you would make to that someone.
Designing classes is mostly an argument about where things belong. Having that argument with other people, early and often, is the fastest way to get good at it.
11Quiz
Twenty-four questions. Answer them all, then grade. Every question explains its answer, including why the wrong options are wrong. Nothing is recorded or submitted.
Work it out in your head first. If you are unsure, paste the snippet into the Playground afterwards — but guess before you run, or you learn nothing about what you actually believed. (Snippets marked Java 14+ or 21+ will only run on Ed.)
12Further reading
None of this is assessed. It is here for the moment a section leaves you curious, and for the claims above that you should check rather than take our word for.
The official word
Classes and objects
Oracle's walkthrough of declaring classes, fields, constructors, this, and class versus instance members — the reference version of sections 2 to 4.
Enum types
Enums with fields, constructors and methods — the part section 7.5 mentions but does not show, with a solar-system example.
docs.oracle.com Tutorial · sections 5, 6Python classes
Chapter 9 of the Python tutorial: class and instance variables, methods, and §9.6 on private variables and name mangling.
docs.python.org HOWTO · section 7.7Enum HOWTO
The Python guide to enums: names and values, aliases for duplicate values, and why members are instances of their class.
docs.python.orgBehind the claims on this page
A History of C++: 1979–1991
Stroustrup on how writing a simulator in Simula for his PhD shaped C++. The first few pages are the story told in section 1.1.
stroustrup.com JEP · section 8.3JEP 361 — Switch Expressions
The design of switch expressions: the arrow form, yield, and exhaustiveness.
JEP 441 — Pattern Matching for switch
Type patterns, guards with when, and the rules that make a pattern switch exhaustive.
JEP 440 — Record Patterns
Deconstructing records inside a pattern, as in the grading example.
openjdk.org PEP · section 8.4PEP 636 — Pattern matching tutorial
Python's match, including guards and matching on your own classes.
M1Classes in Java and Python, side by side
Reference, not reading.
| Idea | Java | Python |
|---|---|---|
| declare a class | public class Foo { … } | class Foo: |
| create an object | Foo f = new Foo(); | f = Foo() |
| constructor | a method named after the class, no return type | def __init__(self, …): |
| none written | a default constructor is supplied — until you write one | an __init__ always exists |
| instance data | declared as fields in the class | created in __init__ through self. |
| the current object | this — implicit | self — an explicit first parameter |
| class-level data | static field, one copy | a variable in the class body |
| method with no object | static method | static method, or @classmethod with cls |
| hide a member | private, checked by the compiler | __name: renamed, not truly private |
| text form of an object | toString() | __str__ |
| operators on your class | not available | __add__, __lt__, … |
| add an attribute at run time | impossible | possible — and a bad idea |
| enum | enum Suit { HEARTS, … } — a class with fixed instances | class Suit(Enum): HEARTS = 1 — values can be anything |
| same object? | a == b | a is b |
What to actually do
On Ed, work through the five Week 5 lessons. This order matches the sections above:
- Fundamentals of Object Oriented Programming — section 1
- Classes and Objects in Java — sections 2 to 4, including the four pizza-shop parts and their forkable workspaces
- Classes and Objects in Python — sections 5 and 6
- Enums in Java (and Python sort of) — section 7
- Advanced switch Statements in Java — section 8
Records, switch expressions, pattern matching and when need Java 14 to 22; this page’s
console runs Java 8. Those examples are printed with their real output — paste them into Ed to run
them, and change them.
Assessed lab exercises are due 11:55pm the Monday of the following week, and you must pass all test cases in an exercise to get its marks. Canvas → Assignments is authoritative for what is due and when; where this page differs, Canvas and the announcements win.
Write one small class of your own from nothing — private data, a constructor, two methods —
and use it from a main in another class. Then do it again in Python. Everything from here on
is built out of that.
Everything cited on this page
The lecture content comes from the Week 5 lessons on Ed. The links below are for the claims this page adds around them — checking a source yourself is a better habit than trusting any lecture slide, including ours.
Specifications and documentation
| Used in | Source |
|---|---|
| 1.4 | The Java Language Specification, Java SE 25, §4.2.3 — float is IEEE 754 binary32; the 0.0625 spacing at 1,000,000 is Math.ulp(1000000f), run on JDK 26 |
| 3.1 | JLS §8.3.1.1 — “exactly one incarnation of the field” |
| 4.1 | The Java Virtual Machine Specification, Java SE 25, §2.5.4 — the method area |
| 4.2 | JLS §8.8.9 — “If a class contains no constructor declarations, then a default constructor is implicitly declared” |
| 2–4 | Oracle — Classes, The Java Tutorials |
| 6.3 | The Python Tutorial, §9.6 Private Variables — name mangling and the _spam convention |
| 7.2 | Oracle — Enums, J2SE 5.0 language guide — the int enum pattern, “Not typesafe” |
| 7.3 | JLS §8.9 — “An enum class has no instances other than those defined by its enum constants.” |
| 7.7 | Python — Enum HOWTO (aliases; isinstance of members); PEP 435 (enum added in Python 3.4) |
| 8.3–8.5 | JEP 361 (switch expressions, JDK 14); JEP 441 (pattern matching for switch, JDK 21); JEP 440 (record patterns, JDK 21); JEP 456 (unnamed variables and patterns, JDK 22) |
| 8.4 | PEP 636 — Structural Pattern Matching: Tutorial — guards |
Quotations
Every quotation on this page was checked against a primary source — the original paper or documentation, not a quotation website.
| Used in | Source |
|---|---|
| 1.1 | Bjarne Stroustrup, A History of C++: 1979–1991, ACM HOPL-II, 1993, §1 Introduction |
| 1.1 | 2001 and 2003 Turing Award citations, as listed in Wikipedia — Turing Award |
| 6.3 | The Python Tutorial, §9.6 Private Variables |
Photographs
From Wikimedia Commons, reused under the licence each one names. Author and licence come from the Commons API rather than from anyone’s memory — see media/fetch-photo.py.
| Image | Source |
|---|---|
| cookie-cutter | Apple Shaped Cookies (14888835148) — Personal Creations, 2014 · CC BY 2.0 |
| playing-cards | Playing Cards - number fives — Sudzie, 2021 · CC BY-SA 4.0 |
| kristen-nygaard | Kristen-Nygaard-SBLP-1997-head — Jorge Stolfi, 1997 · public domain |
| stroustrup | Bjarne Stroustrup (2013) — ICPCNews (photographer Victor Zavyalov), 2013 · CC BY 2.0 |
Articles
| Used in | Source |
|---|---|
| 1.1 | Simula — Stroustrup and Gosling acknowledging Simula as a major influence |