PersonImmutable.java
package fr.univtln.bruno.samples.java101.tp1.immutable;
import java.util.Objects;
/**
* Immutable representation of a person.
*
* <p>This class is a small, thread-safe value object: all fields are final and
* set at construction time. Construction is intentionally delegated to the
* nested {@link Builder}: the Builder performs validation and normalization
* (for example, trimming/stripping input) when {@link Builder#build()} is
* invoked and will raise appropriate exceptions for invalid input. The class
* constructor is private to enforce use of the Builder for public construction.
*</p>
*
* <p>Usage notes:
* <ul>
* <li>Instances are immutable and safe to share between threads.</li>
* <li>Use the {@link Builder} to create validated instances; intermediate
* builder states may contain {@code null} or invalid values until
* {@link Builder#build()} is called.</li>
* <li>Validation and normalization are performed by the nested {@link Builder}
* when {@link Builder#build()} is invoked; the constructor is private and
* expects already-validated values.</li>
* </ul>
*/
public final class PersonImmutable {
private final String firstName;
private final String lastName;
private final int age;
/**
* Private constructor. Instances should be created via {@link Builder}.
* The Builder is responsible for validating and normalizing inputs before
* calling this constructor.
*
* @param firstName non-null, already-normalized first name
* @param lastName non-null, already-normalized last name
* @param age validated age (>= 0)
*/
private PersonImmutable(String firstName, String lastName, int age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
/**
* Return the first name.
*
* @return the first name (never {@code null})
*/
public String getFirstName() { return firstName; }
/**
* Return the last name.
*
* @return the last name (never {@code null})
*/
public String getLastName() { return lastName; }
/**
* Return the age.
*
* @return the age (>= 0)
*/
public int getAge() { return age; }
@Override
public String toString() { return firstName + " " + lastName + "(" + age + ")"; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof PersonImmutable)) return false;
PersonImmutable that = (PersonImmutable) o;
return age == that.age && firstName.equals(that.firstName) && lastName.equals(that.lastName);
}
@Override
public int hashCode() { return Objects.hash(firstName, lastName, age); }
/**
* Create a new {@link Builder} to construct immutable instances.
*
* @return a fresh Builder
*/
public static Builder builder() { return new Builder(); }
/**
* Builder for {@link PersonImmutable}.
*
* <p>The builder allows setting properties in any order. Validation is
* performed when {@link #build()} is invoked; thus intermediate builder
* states may contain {@code null} or invalid values.</p>
*/
public static final class Builder {
private String firstName;
private String lastName;
private int age;
/**
* Public no-arg constructor for Builder (documented to satisfy Javadoc checks).
*/
public Builder() {
// intentionally empty; builder fields are initialized to defaults
}
/**
* Set the first name to use when building the instance.
*
* @param f the first name (may be {@code null} until {@link #build()} is called)
* @return this builder
*/
public Builder firstName(String f) { this.firstName = f; return this; }
/**
* Set the last name to use when building the instance.
*
* @param l the last name (may be {@code null} until {@link #build()} is called)
* @return this builder
*/
public Builder lastName(String l) { this.lastName = l; return this; }
/**
* Set the age to use when building the instance.
*
* @param a the age (may be negative until {@link #build()} is called)
* @return this builder
*/
public Builder age(int a) { this.age = a; return this; }
/**
* Build a validated {@link PersonImmutable} instance.
*
* @return a new PersonImmutable
* @throws NullPointerException if a required name is {@code null}
* @throws IllegalArgumentException if age is negative
*/
public PersonImmutable build() {
// validate required fields and normalize strings at the builder boundary
Objects.requireNonNull(firstName, "firstName must not be null");
Objects.requireNonNull(lastName, "lastName must not be null");
if (age < 0) throw new IllegalArgumentException("age must be >= 0");
String fn = firstName.strip();
String ln = lastName.strip();
return new PersonImmutable(fn, ln, age);
}
}
}