Instantiation
A playwright may introduce only so many characters before the audience gets overwhelmed. Maybe four? Eight? An object-oriented programmer, on the other hand, brings hordes of objects onto the stage in the blink of an eye. Whereas the characters in a play must be distinct from one another, objects tend to be mass-produced. They are manufactured en masse by treating a class as a blueprint.
New Semantics
To produce an object, which is an instance of a class, we must instantiate it. In many languages, the new
operator followed by a call to a constructor produces an instance, as in this C++ code:
Image *image = new Image(480, 320, Image::GRAYSCALE);
In Ruby, new
is a static method:
image = Image.new(480, 320, Image::GRAYSCALE)
The new
operator or method has two-fold semantics:
- Memory for the object's state is allocated on the heap.
- The constructor of the class is called to initialize the object's allocated state.
C++, Java, Ruby, and JavaScript all require an explicit new
. Languages that prioritize brevity of expression, including Python and Kotlin, omit new
from instantiations but still have its semantics. There's no new
in this Kotlin instantiation:
val image = Image(480, 320, Image.GRAYSCALE)
C has no objects and therefore no new
operator. But it does have structs, and we can write a function that initializes a struct as a constructor would. The work of new
is emulated with two separate statements in this C program:
struct image_t *image = (struct image_t *) malloc(sizeof(image_t));
initialize_image(image, 480, 320, GRAYSCALE);
The first statement allocates memory, and the second initializes that memory. In a language with the two-fold semantics of new
, there's no danger of forgetting one of these steps like there is in C.
Memory
Recall that the stack is where a function's local variables and parameters are stored, and the heap is where data that lives beyond a function's lifetime is stored. The new
operation places objects on the heap. That seems unnecessarily strict. Why can't objects be placed on the stack? When a function finishes executing, its memory is wiped. If the function returns an object stored on the stack, then the object must be copied into the caller to avoid being lost. Objects are potentially much larger than primitives, so copying them has a higher cost. Many languages eliminate this cost of copying by forcing all objects to live on the heap. Returning a heap object is cheap because only a pointer is copied.
C++ does allow objects to be allocated on the stack. A new stack object is declared and initialized with simpler syntax than a typical assignment:
Image image(480, 320, Image::GRAYSCALE);
Note that there's not an even assignment operator in this declaration even though it's performing an initialization. This object can be passed cheaply to called functions as a reference or a pointer. If passed as a value, the copy may be expensive.
Singletons
Classes permit many instances, but some objects are one of a kind. An object of which there is only a single instance is called a singleton. For example, suppose we want to issue logging messages from anywhere in a project, and we want all the messages to funnel into a single output stream. We want a singleton Logger
object that any function may access, but we don't want any other Logger
objects floating around and getting in the way.
We can create our own singleton using a class with a static variable initialized to hold the single instance. By making the constructor private, no other entity may create a second instance. This Java class defines a singleton Logger
:
public class Logger {
private static final SINGLETON = new Logger();
private Logger() {/* ... */}
private void warn(String message) {/* ... */}
private void err(String message) {/* ... */}
public static void warn(String message) {
SINGLETON.warn(message);
}
public static void err(String message) {
SINGLETON.err(message);
}
}
The only way to log a message is by going through the static methods which delegate to the private singleton:
Logger.warn("network lost");
Kotlin provides syntactic support for creating singletons in a simpler manner. A singleton is defined just like a class, only the keyword object
is used instead of class
:
object Logger {
fun warn(message: String) {
println("Warning: $message")
}
fun err(message: String) {
println("Error: $message")
}
}
There's no static variable to act as the receiver. Instead, the singleton's methods are invoked as if they were static:
fun main() {
Logger.warn("network lost")
}
No constructor is ever explicitly called. The singleton is implicitly instantiated upon its first access.