Declaring and Initializing a Class-Type Field
Before You Start
Check each box you can do from memory. A box you cannot check yet is not a problem; it points you to a quick refresher, not a grade.
If the first box is shaky, review Composition: Has-A, Owns Lifetime first.
Not sure? Take the 60-second self-check.
1. Default value. What is the default initial value of a reference-type field that is declared but not initialized?
Check
null. Java’s default for any reference type field is null (JLS 4.12.5). A declared but uninitialized reference field holds null until the constructor or an initializer assigns a non-null value.
2. Access modifier. What access modifier should class fields have by default in Steiner standards?
Check
private. Fields should be accessible only through the class’s own methods. Expose values through getters; accept changes through setters. (Bloch, Effective Java, Item 16.)
3. Constructor responsibility. When should every field of a class be assigned a non-null value?
Check
In the constructor. The constructor is responsible for bringing the object into a valid, fully initialized state. An object that leaves the constructor with a null field that should not be null is in an invalid state.
Try This First
Given:
public class Person implements Comparable<Person> {
private String fn;
private String ln;
private Color color;
}
What is the value of color immediately after new Person() is called with a no-arg constructor that does nothing?
Check
null. Reference fields are zero-initialized by the JVM; for objects, zero is null. Without a constructor body that assigns color, the field stays null.
What You Need To Walk In With
The Insight: A class-type field declaration looks exactly like a primitive field declaration. The semantics differ: a reference field holds an address (a pointer to an object), not the object itself. Java initializes it to null by default. The constructor is responsible for assigning a valid reference; null is almost never a valid final state for a domain object field.
If the null default surprises you, it helps to remember that Java is consistent: primitives default to their numeric zero (int to 0, boolean to false), and references default to the reference zero, which is null. There is no magic initialization of objects; the JVM does not know which constructor to call on your behalf.
You can: declare a private class-type field, write a Steiner-standard constructor that guards against null with an IllegalArgumentException, state the default value of any reference field, and identify the two pitfalls (null-as-permanent-state and public fields) before you make either mistake.
How It Works
S20 Lab 2: Person with class-type fields
public class Person implements Comparable<Person>
{
private String fn;
private String ln;
private Color color;
public Person(final String fn, final String ln, final Color color)
{
if (fn == null || ln == null || color == null)
throw new IllegalArgumentException("Bad parameter(s) in Person EVC");
this.fn = fn;
this.ln = ln;
this.color = color;
}
}
Three reference-type fields (String, String, Color). All three are:
- declared
private - initialized in the constructor (not elsewhere)
- guarded against
nullat the constructor entry
The Color enum is an object type in Java. Enum values are never null in normal code, but the precondition check covers the unusual case where the caller passes null explicitly.
Why the constructor is the initialization point
The constructor establishes the object’s initial valid state. After the constructor returns, every field should hold a meaningful value. If a field is left null, every accessor that dereferences it will throw NullPointerException at some unpredictable later point. The precondition check in the constructor catches the problem at the source.
Steiner standard for constructors
The course coding standard requires:
- Parameters declared
final. - Explicit null check:
if (x == null) throw new IllegalArgumentException("Bad <Type> in <ClassName>.<method>"); - Assignment only after the check.
This format produces a diagnostic message that names the class and the method, making the source of bad input immediately clear.
Quick check
Check your understanding
In the Person constructor shown above, why must the null check appear BEFORE the assignment lines?
Worked Example: Predict Then Check
public class Receipt {
private Customer customer;
// no initializer, no constructor
}
Receipt r = new Receipt();
System.out.println(r.customer); // direct field access (assume no encapsulation for this example)
Predict the output.
Reasoning
The field customer is a reference type. It was never assigned. Java’s default for unassigned reference fields is null. The println prints the string "null" for a null reference.
Show answer
Output: null
The field holds the JVM’s default value for uninitialized reference types.
Source: JLS 4.12.5.
Common Misconceptions
Misconception 1: Java initializes fields to sensible defaults
Wrong mental model: “Java picks a reasonable default for reference fields. I do not need to initialize them in the constructor.”
Why it breaks: Java’s default for reference fields is null, which is almost never a useful domain value. The first method call that dereferences the field (customer.getName()) throws NullPointerException. The error occurs far from the true source (the uninitialized constructor).
How to correct: Initialize every field in the constructor. If a field can legitimately be uninitialized, document that explicitly and write accessors that handle the null case. The pattern in CSCD 211 labs: every field is assigned in the constructor; null is a precondition violation, not a valid state.
Source: JLS 4.12.5; Bloch, Effective Java, Item 15.
Quick check
Check your understanding
A student declares ‘private Color color;’ but writes no constructor. What is the value of color after ‘new MyClass()’ completes?
Misconception 2: using public fields is acceptable for simplicity
Wrong mental model: “
public Color color;is shorter and easier; getters are boilerplate.”
Why it breaks: Public fields make the field’s type, name, and absence-of-constraints part of the class’s permanent public API. Any change to the field (renaming, changing type, adding validation) breaks every call site. Getters decouple the external API from the internal representation.
How to correct: Default to private. Add getColor() if callers need to read the color. Add setColor(Color c) if mutation is intended. This is the Steiner standard for every lab.
Source: Bloch, Effective Java, Item 16.
Formal Definition and Interface Contract
From JLS 4.12.5 (Initial Values of Variables):
Each class variable, instance variable, or array component is initialized with a default value when it is created. [...] For all reference types, the default value is
null.
From Bloch, Effective Java, Item 16:
In public classes, use accessor methods, not public fields. [...] Public fields make the class’s internal representation part of the API; if the representation is exposed, any change to it will break existing code.
Mental Model
Think of a class-type field as a labeled slot in a box. The box starts with the slot empty (null). The constructor fills every slot with a real value. If any slot is still empty after the constructor returns, any operation that reaches into that slot will find nothing and fail. The constructor’s job is to ensure all slots are filled before anyone uses the box.
Connections
Within CSCD 210/211: The precondition pattern (IAE with class-and-method message) is the same standard used in comparator classes. This lesson applies it to the constructor.
Looking back: The earlier lessons on Composition: Has-A, Owns Lifetime and Aggregation: Has-A, Can Outlive established what composition and aggregation mean structurally. This lesson shows the implementation: how the field is declared and initialized.
Looking ahead: Share vs Copy on Assign distinguishes whether this.x = x shares a reference or should copy. That distinction is the core of choosing between aggregation and composition at the implementation level.
Go Deeper (optional)
None of the following is required to write correct, passing lab code. Read it when you are curious about why the rules are shaped the way they are.
The null-default rule is not arbitrary: it mirrors exactly how primitives work. Primitives default to the additive identity of their type (0 for integers, 0.0 for floats, false for boolean). For references, the additive identity is the absence of any object, which Java spells null. The JVM applies one unified rule across all variable types, which is why the language specification can state it in a single sentence (JLS 4.12.5).
The requirement that all fields be private and initialized in the constructor has a name in the broader software-engineering literature: it is the discipline of making invalid states unrepresentable. When the constructor both validates inputs and completes all assignments, the invariant “every field holds a meaningful value” is established once and never needs to be rechecked inside any method. This is the same reasoning that David Parnas described in his 1972 paper on information hiding: a module should expose only its interface, not its representation. The Steiner standard you are following in lab is that same principle applied at the level of a single class.
In professional Java codebases, major style guides (Google Java Style Guide, the Checkstyle rules used at most large companies) enforce private fields and constructor initialization as automated checks, not suggestions. A field that can be null after construction is flagged as a code smell in tools like SonarQube and IntelliJ’s inspections. The precondition pattern you write in every EVC is what production code uses for the same reason: making null a construction-time failure rather than a runtime surprise that appears in a method far from the real source.
For fields that legitimately have no value at some point, Java 8 introduced Optional<T>. Bloch (Effective Java, 3rd ed., Item 55) recommends Optional primarily for return types, not for fields, because wrapping fields in Optional creates unnecessary heap allocations and complicates serialization. The stronger preference is to make absence of a value impossible at construction time by requiring a valid argument, which is exactly what the IAE precondition enforces.
Practice
Level 1
Write a complete Receipt class with one private Customer customer field, a constructor that initializes it with final parameter and an IAE null check. Follow Steiner standards.
Show answer
public class Receipt
{
private Customer customer;
public Receipt(final Customer customer)
{
if (customer == null)
throw new IllegalArgumentException("Bad Customer in Receipt EVC");
this.customer = customer;
}
public Customer getCustomer()
{
return this.customer;
}
}
Level 2
A student writes:
public class Team {
public String name;
public Player[] roster;
}
Identify every problem and provide the corrected form with a constructor.
Show answer
Problems: (1) public fields expose the class’s internal representation. (2) No constructor: fields are null by default. (3) No precondition checks.
Corrected form:
public class Team
{
private String name;
private Player[] roster;
public Team(final String name, final Player[] roster)
{
if (name == null || name.isEmpty() || roster == null)
throw new IllegalArgumentException("Bad parameter(s) in Team EVC");
this.name = name;
this.roster = roster;
}
public String getName() { return this.name; }
public Player[] getRoster() { return this.roster; }
}
Level 3
A Book from S20 Lab 3 has fields String title, String isbn, Genre type (enum), Publisher pub, Author[] authors, int pages. Write the constructor header and precondition checks following Steiner standards. Do not write the assignment lines.
Show answer
public Book(final String title, final String isbn, final Genre type,
final Publisher pubs, final Author[] author, final int pages)
{
if (title == null || title.isEmpty() ||
isbn == null || isbn.isEmpty() ||
type == null || pubs == null ||
author == null || pages < 1)
throw new IllegalArgumentException("Invalid parameter(s) in Book EVC");
// assignment lines follow
}
Source: S20.Lab3.Book.java precondition pattern.
Check Yourself
Close the notes and answer each one from memory, then reveal it. Pulling an idea back from memory is one of the strongest ways to make it stick.
Check your understanding
What is the default value Java assigns to a reference-type field that is declared but never initialized in a constructor?
Which of the following field declarations follows the Steiner standard for a class-type field inside a Person class?
In the Steiner constructor standard, what must happen BEFORE the field assignment lines?
Predict the output of this code (assume direct field access is permitted for the example): public class Receipt { private Customer customer; } Receipt r = new Receipt(); System.out.println(r.customer);
A student declares ‘public String name;’ instead of ‘private String name;’ with a getter. Which problem does this cause?