Java Arena
Step 3 of 5

Checking values with a compact constructor

The constructor Java writes for a record stores whatever it's given. Often some values make no sense: a temperature colder than absolute zero, a percentage of 140, a name with spaces around it. A record can check and tidy its values in a constructor of its own.

A compact constructor

Main.java
public class Main {
    public static void main(String[] args) {
        Reading oulu = new Reading("  Oulu ", -4.5);
        System.out.println(oulu);
        Reading wrong = new Reading("Kemi", -300.0);
        System.out.println(wrong);
    }
}
Reading.java
public record Reading(String station, double celsius) {
    public Reading {
        if (celsius < -273.15) {
            throw new IllegalArgumentException("colder than absolute zero: " + celsius);
        }
        station = station.trim();
    }
}
Output
Reading[station=Oulu, celsius=-4.5]
The program crashes with
Exception in thread "main" java.lang.IllegalArgumentException: colder than absolute zero: -300.0
	at Reading.<init>(Reading.java:4)
	at Main.main(Main.java:5)

public Reading { ... } is a compact constructor: the record's name with no parameter list at all. Its parameters are the components, station and celsius, and Java runs the code in the braces first and then stores the parameters in the instance variables by itself. So inside it you can:

  • check a value and throw an exception (module 42) when it's wrong, so that no Reading with an impossible temperature can ever exist;
  • change a parameter before it's stored: station = station.trim(); gives the parameter a tidied value, and that's the value the record keeps. That's why " Oulu " comes out as Oulu.

Note that it says station = ..., not this.station = .... The parameter is what gets stored at the end; the instance variable itself is final, and only Java's own assignment after your code may set it:

Main.java
public class Main {
    public static void main(String[] args) {
        System.out.println(new Reading(" Oulu", -4.5));
    }
}
Reading.java
public record Reading(String station, double celsius) {
    public Reading {
        this.station = station.trim();
    }
}
What javac prints
Reading.java:3: error: cannot assign a value to final variable station
        this.station = station.trim();
            ^
1 error

The long form

The constructor that takes all the components is the record's canonical constructor. The compact form is a short way to write it. Here is the same one written in full, with the parameter list repeated and every this.x = x assignment written out:

public Reading(String station, double celsius) {
    if (celsius < -273.15) {
        throw new IllegalArgumentException("colder than absolute zero: " + celsius);
    }
    this.station = station.trim();
    this.celsius = celsius;
}

Both forms declare the same constructor, so a record can have only one of them. The compact form is what you'll usually see, since its whole point is to leave out what Java can write for you.

A tidied value matters for equality too: since the records keep Oulu either way, new Reading(" Oulu ", -4.5) equals new Reading("Oulu", -4.5).

Your task

Challenge 1 of 3

An online chess club keeps each player's nickname and rating. Nicknames are typed in carelessly, with spaces around them and capital letters here and there, but " Rook99 " and "rook99" are the same player. In Player.java, give the record Player a compact constructor that trims the nickname and turns it into small letters (toLowerCase()). The check runs:

Player typed = new Player("  Rook99 ", 1450);
Player stored = new Player("rook99", 1450);
System.out.println(typed);
System.out.println(typed.equals(stored));
System.out.println(new Player("QueenSide", 1720).nickname());

and expects:

Player[nickname=rook99, rating=1450]
true
queenside

Challenge 2

A download manager shows how far each file has come as a percentage. A buggy server sometimes reports values below 0 or above 100. In Download.java, give the record Download(String file, int percent) a compact constructor that keeps the percentage between 0 and 100: a value below 0 becomes 0, a value above 100 becomes 100, and anything else stays as it is. The check runs:

System.out.println(new Download("song.mp3", 64));
System.out.println(new Download("map.pdf", -3));
System.out.println(new Download("film.mkv", 140));

and expects:

Download[file=song.mp3, percent=64]
Download[file=map.pdf, percent=0]
Download[file=film.mkv, percent=100]

Challenge 3

A print dialog takes a range of pages, from the first to the last. People sometimes type the range backwards, 9 to 4, which should print the same pages as 4 to 9. In PageRange.java, give the record PageRange(int first, int last) a compact constructor that swaps the two numbers when first is greater than last, so a PageRange is always in order. The check runs:

PageRange typed = new PageRange(9, 4);
System.out.println(typed);
System.out.println(typed.equals(new PageRange(4, 9)));
System.out.println(new PageRange(2, 2));

and expects:

PageRange[first=4, last=9]
true
PageRange[first=2, last=2]