4.4. Constructors

We’ll often want to initialize, or set the initial value of, some of our fields when creating a new object from a class. Constructors allow us to do so.

In Java, constructors have the same name as the class and are most often declared public (though they can have any other valid access modifier). They are declared without a return type. Any function that is named the same as the class and has no return type is a constructor.

Here is an example of a constructor definition within the HelloWorld class:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public class HelloWorld {

    private String message = "Hello World";

    public HelloWorld(String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String aMessage) {
        message = aMessage;
    }

}

This constructor allows us to create HelloWorld objects with custom messages. The assignment this.message = message assigns the value passed into the constructor to the field message. Here’s how we might use it:

HelloWorld goodbye = new HelloWorld("Goodbye World");
System.out.println(goodbye.getMessage()); // prints "Goodbye World"

It’s not required that every class have a constructor. If you don’t provide one, the Java compiler will generate an empty constructor for you, known as a default constructor. For example, when we left out a constructor in our HelloWorld class above, the compiler created the following constructor for us:

public HelloWorld() {}

While this can be convenient, you almost always want to provide a constructor to properly initialize your objects.

4.4.1. Overloading Constructors

We can provide multiple constructors for a given class in order to allow for different initialization scenarios. This is known as constructor overloading. When providing multiple constructors, we must ensure that each has a different collection of arguments, as determined by the number, order, and type of the constructor arguments.

Let’s expand upon our Student class.

Example

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Student {

   private String name;
   private final int studentId;
   private int numberOfCredits;
   private double gpa;

   public Student(String name, int studentId,
            int numberOfCredits, double gpa) {
      this.name = name;
      this.studentId = studentId;
      this.numberOfCredits = numberOfCredits;
      this.gpa = gpa;
   }

   public Student(String name, int studentId) {
      this.name = name;
      this.studentId = studentId;
      this.numberOfCredits = 0;
      this.gpa = 0.0;
   }

   /* getters and setters omitted */

}

The first constructor allows for the creation of Student objects where the code creating the object provides initial values for each of the fields. The second allows for the creation of Student objects with only name and studentId. The first constructor would be most useful for creating a transfer student, where credits and a GPA might already be non-zero. However, for all new students, it would be safe to initialize numberOfCredits and gpa to be 0.

A better way to write the above constructors would be this:

Example

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Student {

   private String name;
   private final int studentId;
   private int numberOfCredits;
   private double gpa;

   public Student(String name, int studentId,
            int numberOfCredits, double gpa) {
      this.name = name;
      this.studentId = studentId;
      this.numberOfCredits = numberOfCredits;
      this.gpa = gpa;
   }

   public Student(String name, int studentId) {
      this(name, studentId, 0, 0);
   }

   /* getters and setters omitted */

}

In the example above on line 17, we use this() to invoke another constructor within the same class. In this case, the second constructor calls the first with the default values for numberOfCredits and gpa. If you use this syntax, the call to this() must be the first line in the constructor. This is a good practice not only because it makes your code shorter, but also because it allows any initialization behavior that may be carried out beyond just initializing variables to be contained in a smaller number of constructors. In other words, constructors can share initialization code. Notice from this example that a constructor doesn’t need to require an initial value for each field as an argument.

When defining constructors, think about:

  1. Which fields must be initialized for your class to work properly? Be sure you initialize every such field.
  2. Which fields should be initialized by the user creating an object, and which should be initialized by the class itself?
  3. What are the use-cases for your class that you should provide for?

4.4.2. Check Your Understanding

Question

A constructor is required for every class.

  1. True
  2. False

Question

Let’s take a look at a class called Dog.

1
2
3
4
5
6
7
8
9
public class Dog {
   private String name;
   private String breed;

   public Dog(String name, String breed) {
      this.name = name;
      this.breed = breed;
   }
}

What line of code would be appropriate for us to declare an instance of the Dog class called myDog and give it the name, “Bernie”, and the breed, “Beagle”?

  1. Dog myDog = new Dog(Bernie,beagle);
  2. Dog myDog = new Dog("Bernie","beagle");
  3. Dog Bernie = new Dog("Bernie","beagle");