Lesson 4: new and Constructors
In the last lesson we discussed the stack (a worker’s memory of what it is doing) and the heap (the place where all objects reside). This lesson discusses how new objects come into existence.
Recall that a class is a description of a kind of object. You might think of “number” as a class and “3” or “87” as objects, instances of the “number” class. You can’t do much with a class: you need an actual object for the methods to act on.
This distinction can get a bit confusing sometimes.
Partly this is because there are many levels of things describing other things.
For example, class Car
is a description of the state and behavior of car objects,
but car objects themselves are descriptions of real-world cars.
All of the words we usually use to distinguish between real and conceptual things
get changed in programming to be directions instead of designations.
“Abstract”, “concrete”, “physical”, “description”, etc.:
any one of them could be used to describe anything
depending on what that thing is being compared to.
This is part of the reason that in programming
we make crisp definitions for
One difference between computing and many other fields
is that computing disciplines re-define existing words
instead of introducing new ones based on Latin or people’s names.
I got back and forth on if I think this is good or bad decision.
“class”, “object”, “value”, “instance”, and “type”.
A class defines the states and behaviors of objects.
Objects are regions of memory
that contain values for all of the states defined by a class.
A value is either a primitive value, such as 3
or true
,
or it is a description of where another object is located in memory.
If class C describes object o
then o is an instance of C
and C is the type of o.
To make an object, then, requires three steps. We need to
The result of these three steps, which are usually handled together, is a value: a description of where the new object is located in memory.
There is ongoing research and heated debate among computing researchers about the simple question “how do we track what type each object has?” Glossing over myriad details, there are three options. First, we might make the type of an object be part of its state. Second, we might ensure that each variable only refers to one type, ever, so that we never need to ask an object what type it is and thus it need not know. Third, we might do both. Doing both might seem like a waste, replicating information unnecessarily, but it is what all of the languages I’ve used in this lesson series (C++, Java, C#, and D) actually do. A few older languages, such as C and Fortran, only track type by variables; many that are targeting shorter programs, such as Javascript, Python, PHP, Ruby, and most mathematical languages, only track type inside the state of an object.
In the languages which I am discussing herein,
a new object is usually created as follows:
TheType newName = new TheType(value, value, ...);
This single line of code has a lot of things going on,
many of which we have discussed in previous lessons.
TheType newName
is how we define a variable.
Technically we don’t need to do this, but without a variable
we won’t have a way of talking about the newly created object.
newName = ...
is how we assign a value to a variable.
Again, technically we don’t need to do this,
but if we don’t assign it then we won’t have a way of talking about the newly created object.
new TheType
is new to this lesson.
It does steps 1 and 2: getting a chunk of memory
and deciding what type is stored there.
TheType(value, value, ...)
looks a lot like a method invocation.
It is actually invoking a special kind of method
called a “constructor”, the job of which is to do step 3:
assigning a value to each field that makes up the new object’s state.
The exact syntax for coding a constructor varies from language to language. One thing they have in common: constructors don’t have a return type (not void, not anything else). A constructor can’t be invoked the way other methods can so the idea of a return type is not meaningful for them.
In C++, Java, and C# constructors have the same name as the class so we would write
class Fraction {
int numerator;
int denominator;
Fraction(int n, int d) {
this.numerator = n;
this.denominator = d;
}
}
In D constructors are named “this” (the same as the special “the object” variable)
so we would write
class Fraction {
int numerator;
int denominator;
this(int n, int d) {
this.numerator = n;
this.denominator = d;
}
}
In both cases, we could make an object representing
2 |
3 |
Fraction f = new Fraction(2, 3);
Once we have this fraction we can use it to invoke other fraction methods
or as an argument in other method invocations.
The following D code snippets use some ideas we have not yet discussed,
such as the *
and >
operators
and the order in which things happen within a method body;
but they also illustrate new, constructors, and using objects.
class Fraction {
int numerator;
int denominator;
this(int n, int d) {
this.numerator = n;
this.denominator = d;
}
bool isBiggerThan(Fraction other) {
int a = this.numerator * other.denominator;
int b = other.numerator * this.denominator;
return a > b;
}
}
Fraction x = new Fraction(2, 3);
Fraction y = new Fraction(11, 14);
bool xSmaller = y.isBiggerThan(x);
Looking for comments…