In the previous section, we showed an example of an Airplane
class in Java and how we can define the class name, its fields (state) and methods (behavior).
However, no objects from that class were created in that example.
The new
keyword
There are different ways to create an instance of a class, the most standard one being the use of the new
keyword in Java. This keyword asks Java to reserve memory for a new instance of a particular class and creates the requested object.
In its simplest form, for our Airplane
example, to create a new instance of that class, all we need to do is invoke new Airplane()
.
Example 1
Airplane plane1 = new Airplane();
In this example, we can see three main actions taking place:
-
The creation and initialization of a new
Airplane
object (thenew Airplane()
part). -
The declaration of a new variable called
plane1
of typeAirplane
(theAirplane plane1
part). -
The assignment of the new object to the
plane1
reference that was defined (the=
that connects the 2 parts above).
Visually, if we assume the application memory was empty before executing the line above, we can think of our memory as an empty area that Java will start filling as we create objects, and at a high level we can see it as follows:
Objects vs References
It is important to make a distinction between a reference to an object
and an object
.
In our example above, plane1
is a reference to an instance of class Airplane
, but plane1
isn’t an Airplane
object as such.
An object reference
points to an object
of the declared type. As seen in the figure above, plane1
is pointing to the 'Airplane Object 1' that was created by the new Airplane()
statement.
So, in this case, we have one Airplane
object and one reference of type Airplane
called plane1
.
Let’s make a few simple modifications to our example code to help clarify the difference with the examples below.
Example 2
Airplane plane1 = new Airplane();
Airplane plane2;
In this case, there is still only one Airplane
object being created (via the new Airplane()
statement), but two object references, plane1
and plane2
. In this example, plane2
isn’t pointing to anything, and we could represent it like this:
Example 3
Airplane plane1 = new Airplane();
Airplane plane2;
plane2 = plane1;
System.out.println("plane1 is pointing to the same instance as plane2: " + (plane1 == plane2));
The == operator in the last line is used to compare 2 variables in Java. When used with object references, it will return true if the two references are pointing to the same object, or false otherwise. In this case plane1 == plane2 is true as both point to the same Airplane object.
|
Output:
plane1 is pointing to the same instance as plane2: true
Similar to the previous example, one object and two references. However, in this case, we are changing the plane2
reference to point to the same object that plane1
is pointing to.
If we represented this visually, we could think about it like this:
Example 4
Airplane plane1 = new Airplane();
Airplane plane2 = plane1;
Airplane plane3 = new Airplane();
System.out.println("plane1 is pointing to the same instance as plane2: " + (plane1 == plane2));
System.out.println("plane1 is pointing to the same instance as plane3: " + (plane1 == plane3));
Output:
plane1 is pointing to the same instance as plane2: true
plane1 is pointing to the same instance as plane3: false
Here, we now have two calls to new Airplane()
which will result in two different objects being allocated in memory. However, we have 3 references of type Airplane
, namely, plane1
, plane2
, and plane3
.
In the code, we are making plane2
point to the same object as plane1
, and plane3
points to the second object that was created.
Or, visually:
This differentiation between objects and references is important to get right and it will become even more relevant later when we talk about memory management and garbage collection, and also when we introduce the concept of a null reference. |
Null reference
Now that we understand that an object and an object reference are different, it is important to note that object references can have a special value called null
.
This value represents that the object reference isn’t pointing to an object. null
as a value has no memory representation (there is no actual object).
For example:
Airplane plane = null;
System.out.println("The plane is: " + plane);
plane.land();
System.out.println("End of program");
Output:
The plane is: null
Exception in thread "main" java.lang.NullPointerException
at NullExampleApplication.main(NullExampleApplication.java:15)
Here, we are telling Java that our plane
reference will be null
, this means that no Airplane
object is associated with it.
Invoking methods or accessing fields on null
references
When we then try to invoke a method on that reference, plane.land()
in our example, Java will throw an exception, indicating that it cannot operate over a null
reference (there is no object that Java can use in this case). The same will occur if we try to access a field when having a null
object reference.
We’ll cover the basics of exceptions in Chapter 3, however, they are events that disrupt the normal execution of our application. In our case, the exception that is thrown by Java is called a NullPointerException, indicating that we tried to perform an operation on a null
reference.
In our output, this exception is what causes the application to print out: "Exception in thread "main" java.lang.NullPointerException…"
You can see that the last line of the code System.out.println("End of program");
wasn’t executed due to the exception (it disrupted the normal application flow and in our case exited the main method at the point of the exception).
Null references and NullPointerExceptions (a.k.a NPEs) or their equivalent in other languages are called the "billion-dollar mistake" as they are the cause of several application errors/crashes and vulnerabilities. See https://en.wikipedia.org/wiki/Tony_Hoare for more details. |
We’ll cover exceptions in more detail later, how to handle them and what options we have at hand when they occur. |