Object Construction in Java

You have seen how to write simple constructors that define the initial state of your objects. However, since object construction is so important, Java offers quite a variety of mechanisms for writing constructors. We go over these mechanisms in the sections that follow.

1. Overloading

Some classes have more than one constructor. For example, you can construct an empty StringBuilder object as

var messages = new StringBuilder();

Alternatively, you can specify an initial string:

var todoList = new StringBuilder(“To do:\n”);

This capability is called overloading. Overloading occurs if several methods have the same name (in this case, the StringBuilder constructor method) but different parameters. The compiler must sort out which method to call. It picks the correct method by matching the parameter types in the headers of the various methods with the types of the values used in the specific method call. A compile-time error occurs if the compiler cannot match the parameters, either because there is no match at all or because there is not one that is better than all others. (The process of finding a match is called overloading resolution.)

2. Default Field Initialization

If you don’t set a field explicitly in a constructor, it is automatically set to a default value: numbers to 0, boolean values to false, and object references to null. Some people consider it poor programming practice to rely on the de­faults. Certainly, it makes it harder for someone to understand your code if fields are being initialized invisibly.

For example, consider the Employee class. Suppose you don’t specify how to initialize some of the fields in a constructor. By default, the salary field would be initialized with 0 and the name and hireDay fields would be initialized with null.

However, that would not be a good idea. If anyone called the getName or getHireDay method, they would get a null reference that they probably don’t expect:

LocalDate h = harry.getHireDay();

int year = h.getYear(); // throws exception if h is null

3. The Constructor with No Arguments

Many classes contain a constructor with no arguments that creates an object whose state is set to an appropriate default. For example, here is a no-argument constructor for the Employee class:

public Employee!)

{

name = “”;

salary = 0;

hireDay = LocalDate.now();

}

If you write a class with no constructors whatsoever, then a no-argument constructor is provided for you. This constructor sets all the instance fields to their default values. So, all numeric data contained in the instance fields would be 0, all boolean values would be false, and all object variables would be null.

If a class supplies at least one constructor but does not supply a no-argument constructor, it is illegal to construct objects without supplying arguments. For example, our original Employee class in Listing 4.2 provided a single constructor:

public Employee(String n, double s, int year, int month, int day)

With that class, it was not legal to construct default employees. That is, the call

e = new Employee();

would have been an error.

4. Explicit Field Initialization

By overloading the constructor methods in a class, you can build many ways to set the initial state of the instance fields of your classes. It is always a good idea to make sure that, regardless of the constructor call, every instance field is set to something meaningful.

You can simply assign a value to any field in the class definition. For example:

class Employee

{

private String name = “”;

}

This assignment is carried out before the constructor executes. This syntax is particularly useful if all constructors of a class need to set a particular instance field to the same value.

The initialization value doesn’t have to be a constant value. Here is an example in which a field is initialized with a method call. Consider an Employee class where each employee has an id field. You can initialize it as follows:

class Employee

{

private static int nextId;

private int id = assignId();

private static int assignId()

{

int r = nextId;

nextId++;

return r;

}

}

5. Parameter Names

When you write very trivial constructors (and you’ll write a lot of them), it can be somewhat frustrating to come up with parameter names.

We have generally opted for single-letter parameter names:

public Employee(String n, double s)

{

name = n;

salary = s;

}

However, the drawback is that you need to read the code to tell what the n and s parameters mean.

Some programmers prefix each parameter with an “a”:

public Employee(String aName, double aSalary)

{

name = aName;

salary = aSalary;

}

That is quite neat. Any reader can immediately figure out the meaning of the parameters.

Another commonly used trick relies on the fact that parameter variables shadow instance fields with the same name. For example, if you call a param­eter salary, then salary refers to the parameter, not the instance field. But you can still access the instance field as this.salary. Recall that this denotes the implicit parameter—that is, the object being constructed. Here is an example:

public Employee(String name, double salary)

{

this.name = name;

this.salary = salary;

6. Calling Another Constructor

The keyword this refers to the implicit parameter of a method. However, this keyword has a second meaning.

If the first statement of a constructor has the form this(. . .), then the constructor calls another constructor of the same class. Here is a typical example:

public Employee(double s)

{

// calls Employee(String, double)

this(“Employee #” + nextId, s);

nextId++;

}

When you call new Employee (60000), the Employee (double) constructor calls the Employee(String, double) constructor.

Using the this keyword in this manner is useful—you only need to write common construction code once.

7. Initialization Blocks

You have already seen two ways to initialize a data field:

  • By setting a value in a constructor
  • By assigning a value in the declaration

There is a third mechanism in Java, called an initialization block. Class decla­rations can contain arbitrary blocks of code. These blocks are executed whenever an object of that class is constructed. For example:

class Employee

{

private static int nextId;

private int id; private String name;

private double salary;

// object initialization block

{

id = nextId;

nextId++;

}

public Employee(String n, double s)

{

name = n;

salary = s;

}

public Employee()

{

name = “”;

salary = 0;

}

}

In this example, the id field is initialized in the object initialization block, no matter which constructor is used to construct an object. The initialization block runs first, and then the body of the constructor is executed.

This mechanism is never necessary and is not common. It is usually more straightforward to place the initialization code inside a constructor.

With so many ways of initializing data fields, it can be quite confusing to give all possible pathways for the construction process. Here is what happens in detail when a constructor is called:

  1. If the first line of the constructor calls a second constructor, then the second constructor executes with the provided arguments.
  2. Otherwise,
    • All data fields are initialized to their default values (0, false, or null).
    • All field initializers and initialization blocks are executed, in the order in which they occur in the class declaration.
  3. The body of the constructor is executed.

Naturally, it is always a good idea to organize your initialization code so that another programmer could easily understand it without having to be a lan­guage lawyer. For example, it would be quite strange and somewhat error- prone to have a class whose constructors depend on the order in which the data fields are declared.

To initialize a static field, either supply an initial value or use a static initialization block. You have already seen the first mechanism:

private static int nextId = 1;

If the static fields of your class require complex initialization code, use a static initialization block.

Place the code inside a block and tag it with the keyword static. Here is an example. We want the employee ID numbers to start at a random integer less than 10,000.

// static initialization block

static

{

var generator = new Random();

nextId = generator.nextInt(10000);

}

Static initialization occurs when the class is first loaded. Like instance fields, static fields are 0, false, or null unless you explicitly set them to another value. All static field initializers and static initialization blocks are executed in the order in which they occur in the class declaration.

The program in Listing 4.5 shows many of the features that we discussed in this section:

  • Overloaded constructors
  • A call to another constructor with this(. . .)
  • A no-argument constructor
  • An object initialization block
  • A static initialization block
  • An instance field initialization

8. Object Destruction and the finalize Method

Some object-oriented programming languages, notably C++, have explicit destructor methods for any cleanup code that may be needed when an object is no longer used. The most common activity in a destructor is reclaiming the memory set aside for objects. Since Java does automatic garbage collec­tion, manual memory reclamation is not needed, so Java does not support destructors.

Of course, some objects utilize a resource other than memory, such as a file or a handle to another object that uses system resources. In this case, it is important that the resource be reclaimed and recycled when it is no longer needed.

If a resource needs to be closed as soon as you have finished using it, supply a close method that does the necessary cleanup. You can call the close method when you are done with the object. In Chapter 7, you will see how you can ensure that this method is called automatically.

If you can wait until the virtual machine exits, add a “shutdown hook” with the method Runtime.addShutdownHook. As of Java 9, you can use the Cleaner class to register an action that is carried out when an object is no longer reachable (other than by the cleaner). These are uncommon situations in practice. See the API documentation for details on these two approaches.

Source: Horstmann Cay S. (2019), Core Java. Volume I – Fundamentals, Pearson; 11th edition.

Leave a Reply

Your email address will not be published. Required fields are marked *