Luet oppimateriaalin englanninkielistä versiota. Mainitsit kuitenkin taustakyselyssä osaavasi suomea. Siksi suosittelemme, että käytät suomenkielistä versiota, joka on testatumpi ja hieman laajempi ja muutenkin mukava.
Suomenkielinen materiaali kyllä esittelee englanninkielisetkin termit. Myös suomenkielisessä materiaalissa käytetään ohjelmaprojektien koodissa englanninkielisiä nimiä kurssin alkupään johdantoesimerkkejä lukuunottamatta.
Voit vaihtaa kieltä A+:n valikon yläreunassa olevasta painikkeesta. Tai tästä: Vaihda suomeksi.
Chapter 2.3: Classes of Objects
About This Page
Questions Answered: What’s a practical way to define multiple
similar objects? How do I create a new object of a particular
kind? How can I manipulate an image stored as a
Topics: Classes: objects as instances of classes; classes as
data types; instantiation; the
Pic objects and
their methods. We’re going to dump another heap of new concepts and
terms on you. But they will be useful, we promise.
What Will I Do? Read and work on small programming assignments.
Rough Estimate of Workload:? A couple of hours, perhaps.
Points Available: A55.
When a human sees a chair, the experience is taken both literally as “that very thing” and abstractly as “that chair-like thing”. Such abstraction results from the marvelous ability of the mind to merge “similar” experience, and this abstraction manifests itself as another object in the mind, the Platonic chair or chairness.
Imagine filling in a tax form on paper. Obviously, you won’t start with a blank page: you’ll use a predesigned sheet that tells you what information you’re expected provide. It would be incredibly inconvenient if each taxpayer had to write the form template, too, since each person provides the same kind of information. It makes sense to use a standard template and make the necessary number of copies to be filled in.
Now recall, from Chapter 2.1, the GoodStuff application and the imaginary app for course enrollment, both of which had multiple communicating objects. Many of those objects were similar to each other: there were multiple courses, multiple students, and multiple experience objects. Similar objects stored similar information and behaved the same way; each course object, for example, stored a course code, a classroom, and a list of enrolled students.
Just as with the tax forms, it would be both unnecessary and inconvenient to define every single object on an individual basis as we’ve done so far.
Using only singleton objects would have other problems as well. The programmer details each singleton object in program code, but how can they know, at the time of writing, how many objects of each kind are needed for each future program run? In most cases, the programmer simply cannot know. The objects that are needed, and their specific attributes, are determined dynamically at runtime and may depend on input that the program receives from end users. In the GoodStuff application, for example, the user defines how many experience objects should be created and what those objects’ attributes are.
What a programmer can do is define the kinds of objects that will be needed when the program is run: what types of information the program will process and what behavior is associated with each type.
This chapter deals with object types, known as classes (luokka), and the creation of objects from class definitions. Objects created this way are much more common than singleton objects are.
Our path has a familiar shape: In this chapter, you’ll experiment with classes that have already been defined for you. In future chapters, you’ll learn how to write the program code that defines a class.
Objects as Instances of Classes
The metaphor of filling a form works to clarify the relationship between classes and objects:
So, as programmers, we define:
- what classes (“types of forms”) our program uses;
- when to create objects of a particular class (“when to make another copy of a form template and fill it”); say, whenever the user enters information about a new experience; and
- how to set the attributes of the new object (“the contents of the filled form”).
From here on in, we won’t be talking about paper forms. Instead, we’ll adopt these more technical terms:
- To describe the relationship between an object and its class, we’ll say that the object is an instance (ilmentymä) of the class.
- A class is a data type (or simply type) for objects (tietotyyppi) .
- Creating a new instance — a new object — from a class definition is called instantiation (instantiointi).
Using a Class in Scala:
Our first example class is called
Employee. This class describes a type of objects
just like the single employee object that we created in the previous chapter. The only
difference — but it’s a crucial one — is that now we won’t use just one object but a
data type that represent the general concept of employee. This type enables us to create
employee objects with different names and salaries.
If we have access to a class, we can easily create new instance of it, as shown for class
Employee below. You can follow along in the REPL; the code is in project IntroOOP.
Let’s first create one new employee:
import o1._import o1._ new Employee("Eugenia Enkeli", 1963, 5500)res0: o1.Employee = o1.Employee@1145e21
new. The word is followed by the name of the class that we want to instantiate — that is, the type of the object to be created. In Scala, most classes have names that start with a capital letter.
Employeeis defined so that whenever we create an employee object, we must provide a name, a year of birth, and a salary. (The new employee works full time by default, so its working time is 1.0.) The class’s program code uses the parameter values as it sets the specific characteristics of the new object.
When we create an object, it’s very often convenient also to assign the resulting reference to a variable. Below, we create another instance of the same class and define a variable that stores a reference to the new instance:
val justHired = new Employee("Teija Tonkeli", 1985, 3000)justHired: o1.Employee = o1.Employee@704234
Now we can use the name of the variable to access the object’s methods:
println(justHired.description)Teija Tonkeli (b. 1985), salary 1.0 * 3000.0 e/month
As you saw, we just created two entirely separate employee objects. They have different attributes but they each have some name, some year of birth, and some monthly salary.
On Instances, Variables, and References
Take a look at a couple of short animations.
That animation already showed that objects are manipulated via references, just as buffers were in Chapter 1.5. The next animation further emphasizes the importance of references.
Predict the behavior of the program as requested at the beginning of the animation below, then watch carefully to see what happens.
References to objects, references to buffers
It’s not coincidental that objects and buffers are accessed similarly via references. Buffers are objects, too. More on that in Chapter 4.2.
Questions on objects, classes, and references
The Many Purposes of a Class
Classes as data types
The term “data type” has been cropping up not just in this chapter but before we even
mentioned classes. And it’s the very same concept that we’re still talking about here.
For example, class
Employee is a data type for employee objects and class
is a data type for the experiences in GoodStuff in exactly the same sense as
Int is a
data type for integer numbers.
When we program, we draw on data types from different sources:
- Some types are closely associated with the particular application
domain that we’re working on. They have been defined by ourselves or
other programmers working on the same project. (E.g.,
- Some types are an integral part of the programming language.
- And some types we adopt from a useful software library. These library
classes may have been created by a third party. (E.g.,
Bufferin Scala’s standard library,
Picin O1’s own library.)
Data types and singleton objects
Even a singleton object has a data type, one unique to just that object. For example, the parrot from Chapter 2.1 has its very own type:
parrotres1: o1.parrot.type = o1.package$parrot$@4e0405c9
Whenever you define a singleton object, you implicitly define a unique data type for it.
It’s rarely necessary to expressly refer to singletons’ data types in program code.
Even the data type
Int is a (somewhat special) class, but we’ll
let that lie until Chapter 5.2.
Classes as parts of a conceptual model
In Chapter 2.1, we established that one of the goals of programming is to model different problem domains. Classes open up great possibilities for conceptual modeling! Whereas objects represent individual things in a model (such as specific employees), classes represent generic concepts whose instances those specific things are (such as the generic notion of an employee). The object-oriented programmer commonly directs their attention to the classes of the program and how the classes combine to represent, on a general level, the possible worlds of objects that are created as the program runs.
Singleton objects vs. classes
It’s actually fairly rare that a programming language lets us define singleton objects in program code in the way that Scala does. In many widely used languages that support OOP (e.g., Java, C++, Python), objects are always created by instantiating a class. Which is what we do in Scala, too, for the most part.
Classes as abstractions
The topic of abstraction has already surfaced in many of the preceding chapters. And again we have come across a new form of abstraction: classes are abstractions — generalizations — of objects. Furthermore, they are abstractions of the domain concepts that they represent.
Classes as parts of program code
The code of an object-oriented program written in Scala consists of the definitions of classes and singleton objects.
It’s possible to define all of a program’s classes in the same file. However, many Scala
programmers only put classes in the same file if the classes are very closely associated
with each other. In O1, you’ll usually write each class in its own
It’s customary to match the name of each code file to the class or singleton
object defined therein. The class
Employee, for example, is defined in a file named
Employee.scala, which we’ll inspect in the next chapter.
On Classes and Their Methods
In the animation of employee objects, did you notice that the objects’ attributes were stored separately from the class? And that methods were associated with the class, not with each object separately?
A class definition is shared among all objects of that type and there is no need to duplicate its contents for each object. Each object has a known type that defines its methods.
By defining the methods of its instances, a class determines how all objects of its
type behave. For example, all employee objects have the methods
description and each such object responds in the same manner when those methods are
invoked. As it does so, the object relies on its own instance-specific attributes.
The limitations of objects
We can only command an instance of a class to do something that it knows how to do.
If we call a method, the method must be defined in the appropriate class, and we must
provide parameters as dictated by the class definition. None of the following attempts
to command an
Employee object succeed, for example.
var example = new Employee("Edelweiss Fume", 1965, 5000)res2: o1.Employee = o1.Employee@177e207 example.repairMyGiraffe("pronto!")<console>:12: error: value repairMyGiraffe is not a member of o1.Employee example.repairMyGiraffe("pronto!") ^ example.ageInYear(2014, 2019)<console>:12: error: too many arguments for method ageInYear: (year: Int)Int example.ageInYear(2014, 2019) ^ example.ageInYear("2019")<console>:12: error: type mismatch; found : String("2019") required: Int example.ageInYear("2019") ^ example.ageInYear<console>:12: error: missing arguments for method ageInYear in class Employee; ... example.ageInYear ^
Practice on Using a Class
The IntroOOP project contains more than class
(again, in a grossly simplified manner) the money that customers owe to a telephone
operator: a single instance of the class is a single customer’s phone bill. Each
phone bill is associated with a number of phone calls (zero or more), each of which is
represented as a single
PhoneCall object, that is, an instance of class
Practice working with these classes in the REPL as instructed below. Both classes have already been defined for you; in this assignment, you don’t need to define them or indeed any other classes.
If you find this assignment difficult, you may wish to scroll down to the section titled Pitfalls before returning to work on the assignment.
Things to know about class
- When you create a
PhoneCallobject, you need to pass in the following constructor parameters: an initial fee (for starting the call), a price per minute, and a duration in minutes. All three are represented as
Doubles that correspond to euros.
- As defined in the class, each
PhoneCallobject can provide, upon request, the total price of the phone call that it represents. We can request this information from the object with the parameterless
totalPricemethod. To determine its total price, a
PhoneCallobject uses the constructor parameters that it received earlier; it also adds a surcharge for using the telecommunications network.
- The class has another parameterless method,
description, which produces a textual description of a phone call.
You’re free to experiment with the class in the REPL, but make sure you do at least the following.
- Create a new
PhoneCallobject and define a variable to store a reference to the object.
- Pick a name for the variable.
- Use an initial fee of 0.99 euros, a per-minute price of 0.47 euros, and a duration of 7.5 minutes.
- Call the
totalPricemethod of the object you created. Observe that the return value includes a surcharge equal to 0.99 euro cents plus 1.99 cents per minute.
descriptionon your object.
Things to know about
- When you create a
PhoneBillobject, use a single constructor parameter: the customer’s name as a string.
- The method
addCallexpects a reference to a
PhoneCallobject as a parameter. When you invoke this effectful method on a particular
PhoneBillobject, the object will add the given phone call to “itself”, that is, to the phone bill that it represents.
- Phone bills, too, have a
totalPricemethod that takes no parameters. It returns the sum of all the phone calls that have been previously added to the bill.
- The method
breakdownis also parameterless. It returns a multi-line string that spells out each phone call added to the bill.
Start by doing the following in order, then feel free to experiment as you please:
- Create a single phone-bill object and a variable that refers to it. Pick any customer name you wish. Name the variable as you wish.
- Add the phone call you created earlier to the new phone bill. You should already have a variable that refers to the phone call; use its name as a parameter expression.
- Add another phone call to the same phone bill:
- Use an initial fee of 1.2 euros, a per-minute price of 0.4 euros, and a duration of 30 minutes.
- A single line suffices to both create the
phone call and add it to the bill: make the
instantiating command (that begins with
new) a parameter expression of
addCall. This way, the reference to the newly created object will be immediately passed as a parameter to the method.
- Call the phone bill’s
- Call the phone bill’s
- Score some points by entering the return values of
breakdownin the form below.
An Updated Concept Map
This might be a good time to place the new concepts in our concept map:
Among the fundamental concepts of object-oriented programming, several pitfalls await the unwary beginner. Many have fallen prey. Look out.
Although we’ve tried to write this chapter so that it steers you clear of misconceptions, it’s perhaps best to underline some of them just to make sure.
Classes are not containers for objects
A fairly common beginner’s mistake is to think of a class as a group of objects or a storage space for objects. You might also think of objects as being located within a class in some sense. However: a class is a description of what a certain kind of objects have in common; it’s not a location for storing that kind of objects. Or, to extend our earlier metaphor: a class is “a particular form template”, not “a folderful of filled forms”.
One practical consequence of this is that you can’t use the name of a class to access, say, a list of the objects that the class “contains”. Neither does a class impose an order or organization on objects that are “located within it” (which they aren’t). Where we want to refer to objects, we generally use variables.
It is certainly common that we wish to manipulate several related objects as a group. When this is the case, we can place those objects inside a collection such as a buffer. We’ll be doing a lot of that in later chapters.
A class’s instances don’t have “names”
The instances of our employee class each had a name; the instances of a bank-account class could each have an account number; course objects could each have a course code, and so on. That being so, it’s easy to form a mistaken impression: that the instances of some classes, if not all, have a built-in name or identifier that we can use to refer to those instances in program code.
However, none of the above actually work for referring to an instance in program code.
If you write the name of an employee to the left of the dot —
— the code works precisely as badly as if you had entered the object’s salary or working time
instead of its name. The name, salary, and working time are attributes of the object, but
none of them identifies the object among all others, not even if there aren’t any other
objects with the same value for the attribute.
When we want to specify which object’s method we intend to call, the common thing to do is to store a reference to the object in a variable and access the object through the variable’s name. Which is what we’ve done multiple times in this chapter.
In this respect, the instances of classes differ from singleton objects. The code that
defines a singleton object gives the object a name that serves as an identifier for that
specific object. We have referred to singleton objects named
example, without defining variables that refer to those objects.
To be sure, sometimes we want our program to find, among a collection of objects, the object with particular attribute value, such as a particular name or course code. That’s a separate problem that can be solved in a number of ways. We’ll discuss some of those ways in Chapters 5.5 and 8.4.
A variable that refers to an object is not the object’s name
var example = new Employee("Edelweiss Fume", 1965, 5000)
Looking at this line of code as a whole, it would be natural to think that it creates
an object of type
Employee and gives it the name
example. That interpretation is
faulty, however. There’s rather more going on: a variable is defined, an object is
created, and a reference that points to the object is stored in the variable.
You’ve already seen that multiple distinct variables can simultaneously contain a
reference to the same place (the same object) and that even a single
var can refer
to different objects at different times during a program run. So we must recognize
that a name such as
example is not the name of an object but of a variable. This is
illustrated in many of the ebook’s animations.
Why don’t instances have names?
So, none of an instance’s attributes identifies it, and the name of a variable that refers to an instance is not an attribute of the instance. Why do we name variables rather than naming instances?
Two of the reasons are given below. They may seem a bit abstract now, but that’ll change as you gain more experience with object-oriented programming.
- Multiple sites within a program may refer to a single object, and the object may have different “roles” in different contexts. It makes sense to use different names to refer to the object as suits each context. This is not unlike people’s roles in the real world: “the president”, “the club’s treasurer”, “my wife”, “my mother”, “some passer-by”, “the next customer”, “the customer who placed the order”, and “that passenger” may all refer to the same individual as different parties consider a person from different perspectives.
- Often, we want to change which object has a particular “role” in a program while the program is running. Nevertheless, we want to use a specific name to access whichever object has that role at the moment. An example is the favorite experience recorded by the GoodStuff application, which may change during a program run. (The real-world comparison fits again: “the president”, “the next customer”, or “my wife” may refer to different individuals at different times.)
Review: manipulating instances through variables
More Practice: Pictures as Objects
In Week_1, we created images that had the
Pic data type.
Pic is actually the name of
a class, and each value of type
Pic is an object that stores data on what appears
in the image.
Pic type can be used for representing diverse shapes and other images. For our
convenience as we work with different kinds of
o1 provides multiple
distinct functions that create
Pics. The functions
two examples: each creates a new picture object and returns a reference to the created
object. We haven’t created
Pic objects by writing
new Pic; instead, we have used,
and will continue to use, these convenient functions.
The only thing we’ve done with a
Pics so far is pass it to the
show function, which
displays the image. However, that’s not nearly all we can do: these picture objects have
a wealth of methods. We’ll now take a look at a few of them.
The basic attributes of a
We can query a
Pic object for its width and height.
import o1._import o1._ val littleCircle = circle(100, Red)littleCircle: Pic = circle-shape littleCircle.widthres3: Double = 100.0 littleCircle.heightres4: Double = 100.0 val bigger = circle(littleCircle.width * 3, Red)bigger: Pic = circle-shape bigger.widthres5: Double = 300.0
The same functionality is available on any
Pic object, including ones loaded from
val loaded = Pic("ladybug.png")loaded: Pic = ladybug.png println("The pic has a total of " + loaded.width * loaded.height + " pixels.")The pic has a total of 900.0 pixels.
It’s easy to rotate an image. Try this and see for yourself.
val myRectangle = rectangle(100, 200, Orange)myRectangle: Pic = rectangle-shape val rotated = myRectangle.clockwise(45)rotated: Pic = rectangle-shape (transformed) show(rotated)show(myRectangle)
clockwisemethod takes in the magnitude of the rotation in degrees.
clockwisenor any other of a
Pic’s methods has an effect on an existing image. The original
Picremains what it was.
history method returns information about how a
Pic has been formed:
rotated.historyres6: List[String] = List(clockwise, rectangle)
Let’s create a new picture by placing two existing pictures side by side:
val first = rectangle(50, 100, Red)first: Pic = rectangle-shape val second = rectangle(150, 100, Blue)second: Pic = rectangle-shape val combination = first.leftOf(second)combination: Pic = combined pic show(combination)
below work like
leftOf does in the above REPL
session. Try them!
As another example, this code forms an image by arranging four copies of the same image in a two-by-two layout:
val testPic = Pic("https://en.wikipedia.org/static/images/project-logos/enwiki.png")testPic: Pic = https://en.wikipedia.org/static/images/project-logos/enwiki.png val sideBySide = testPic.leftOf(testPic)val sideBySide: Pic = combined pic show(sideBySide.above(sideBySide))
The virtual “ping-pong table” that you saw in Chapter 1.2’s Pong game was similarly constructed by placing shapes together.
In Week 1, you wrote a function
verticalBar that returned a value of type
the following exercises, too, you’ll write functions that return references to
objects. The first and easiest of these exercises is voluntary.
An easy training problem
Write an effect-free function called
- takes an image as its only parameter; and
- returns(!) an image in which the given image appears four times in a two-by-two layout.
The function is an abstraction of one of the examples above: it does the same thing but works for an arbitrary image that you pass in as a parameter.
Write the function in the Subprograms project from Week 1, in
A+ presents the exercise submission form here.
Flag #1: Somalia
Write an effect-free function
- takes as its only parameter the entire flag’s width as a
- returns a
Picof the national flag of Somalia, whose:
- width is determined by the parameter;
- height is two thirds of the width;
- star is four thirteenths of the width of the entire flag; and
- colors are
Place the function in project Subprograms.
You can use the function
star in package
o1 to create a five-pointed star. Pass the
star’s width and color as parameters.
Position the star with
onto. Here’s a separate example of that method:
val darkCircle = circle(300, Black)darkCircle: Pic = circle-shape show(testPic.onto(darkCircle))
Watch out with integer division when you do the math. Remember this (Chapter 1.3):
2 / 3res7: Int = 0 2 * 150.0 / 3res8: Double = 100.0
Also note that your function should return the picture, not display it with
If you want to show the result, you can combine the two functions like this:
A+ presents the exercise submission form here.
Flag #2: Finland
In the same file, write a function
- receives a width as a
Doublejust like the previous function; and
- returns an image of the Finnish flag with the given width and the official proportions as per the illustration on the right.
Use the colors
Instructions and hints:
- First compute the length denoted by x in the illustration. Use
that as a basic unit for computing the other measurements.
Create rectangles of the right sizes to serve as the flag’s
“pieces”. Position the pieces side by side or one above the
other. There’s no need to a scaling method.
- Please note that the function’s parameter specifies the width of the entire flag, not the length marked as x in the illustration.
- Use local variables.
Picobjects are immutable. Their methods generate new
Picobjects; they don’t mutate the original
Pic. (See, for example, the rotating-horse example above.)
- Don’t make your function
showthe flag. Just return the picture.
- See if you can solve the assignment both with the help
A+ presents the exercise submission form here.
Go ahead and make other images as well.
Summary of Key Points
- In addition to singleton objects, you can define classes. A class represents the shared characteristics (variables and methods) of objects that resemble each other.
- Classes make object-oriented programming more convenient. In object-oriented programs written in Scala, class definitions tend to play a larger part than singleton objects do.
- Given a class definition, you can instantiate the class. That is to say, you can create an instance of it, an object. The instance has the characteristics (variables and methods) defined by its class but it has its own instance-specific values stored in the variables.
- Adopting different perspectives, you can view classes as: 1) parts of program code; 2) data types; 3) descriptions of generic concepts in a conceptual model; and 4) useful abstractions.
- In Scala, you use the keyword
newto instantiate a class. As you do so, you pass in any constructor parameters that are required for initializing the instance.
- Objects are manipulated via references.
- You can assign an object reference to a variable and then access the object via the variable.
- Multiple references may point at the same object from different contexts.
- A single (
var) variable may store a reference to one object at one time and a reference to another object at another time.
- Links to the glossary: class, data type; instance, to instantiate, constructor parameter; reference; abstraction.
Please note that this section must be completed individually. Even if you worked on this chapter with a pair, each of you should submit the form separately.
Thousands of students have given feedback that has contributed to this ebook’s design. Thank you!
Weeks 1 to 13 of the ebook, including the assignments and weekly bulletins, have been written in Finnish and translated into English by Juha Sorva.
Weeks 14 to 20 are by Otto Seppälä. That part of the ebook isn’t available during the fall term, but we’ll publish it when it’s time.
The automatic assessment of the assignments has been developed by: (in alphabetical order) Riku Autio, Nikolas Drosdek, Joonatan Honkamaa, Jaakko Kantojärvi, Niklas Kröger, Teemu Lehtinen, Strasdosky Otewa, Timi Seppälä, Teemu Sirkiä, and Aleksi Vartiainen.
The illustrations at the top of each chapter, and the similar drawings elsewhere in the ebook, are the work of Christina Lassheikki.
The animations that detail the execution Scala programs have been designed by Juha Sorva and Teemu Sirkiä. Teemu Sirkiä and Riku Autio have done the technical implementation, relying on Teemu’s Jsvee and Kelmu toolkits.
The other diagrams and interactive presentations in the ebook are by Juha Sorva.
The pedagogy behind O1Library’s tools for simple graphical programming (such as
is inspired by the textbooks How to Design Programs by Flatt, Felleisen, Findler, and
Krishnamurthi and Picturing Programs by Stephen Bloch.
The course platform A+ has been created by Aalto’s LeTech research group and is largely developed by students. The current lead developer is Jaakko Kantojärvi; many other students of computer science and information networks are also active on the project.
For O1’s current teaching staff, please see Chapter 1.1.
Additional credits appear at the ends of some chapters.