Lesson 29Phase 3: Abstraction and design
Records: data classes in one line
Classes whose job is to hold data, with the constructor, accessors, equals, hashCode and toString written for you.
By BasithUpdated 40 min
- record
- components
- canonical constructor
- compact constructor
- accessor
- immutability
Some classes exist only to carry a few values around together, such as a point on a map or a book in a catalogue. Written the classic way, even a point made of two numbers needs fields, a constructor, getters, and methods for printing and comparing, about thirty lines that look the same in every class of this kind. A record is Java's short form for such a class. You list the values once, and Java writes the rest.
- Declare a record, create one with
new, and read its values with accessors such asp.x(). - Name everything Java writes for a record: the fields, the canonical constructor, the accessors,
equals,hashCodeandtoString. - Predict what printing,
equalsand==give for two records that hold the same values. - Write a compact constructor that rejects bad values or tidies them before they are stored.
- Add methods, static factory methods and
withmethods to a record, and make it implement an interface. - Choose between a record and a class, and protect a record that holds a list.
==asks “same object?” while equalsis meant to ask “same contents?”, and you wrote your own toString in OOP practice. final comes from static and final. Last lesson showed that an enum is a class with its own short syntax. A record is another one.A class that only carries data#
Picture a drawing app that stores the points a user taps on the screen. A point is two whole numbers, x and y, and nothing else. To turn that into a well-behaved class with what you know so far, you would write:
- Two
private finalfields, so nobody can change a point by accident. - A constructor that fills them in.
- A getter for each field.
toString, so that printing a point shows its numbers.equals, so that two points with the same numbers count as equal.hashCode, a partner method that has to agree withequals. You will see why shortly.
Here is that class. It is long, so skim it rather than reading every line, then press Run.
public class Main { public static void main(String[] args) { Point a = new Point(1, 2); Point b = new Point(1, 2); System.out.println(a); System.out.println(a.getX() + ", " + a.getY()); System.out.println(a.equals(b)); }} class Point { private final int x; private final int y; Point(int x, int y) { this.x = x; this.y = y; } int getX() { return x; } int getY() { return y; } @Override public boolean equals(Object o) { if (!(o instanceof Point)) return false; Point other = (Point) o; return x == other.x && y == other.y; } @Override public int hashCode() { return 31 * x + y; } @Override public String toString() { return "Point[x=" + x + ", y=" + y + "]"; }}It prints Point[x=1, y=2], then 1, 2, then true. Everything works. Now count: the Pointclass is about thirty lines, and all it says is “a point is two numbers”. A Book or a Price would need the same thirty lines again with other names in them. Code like this, typed over and over with only the names changed, is called boilerplate.
Boilerplate is also where bugs hide. Suppose a point later gains a third number, z. You add the field and the constructor parameter, and you forget the one line in equals. The program still compiles and runs. Two points that differ only in z now count as equal, and nothing warns you.
The same point as a record#
Here is the same program with the whole class replaced by a single line.
public class Main { public static void main(String[] args) { Point a = new Point(1, 2); Point b = new Point(1, 2); System.out.println(a); System.out.println(a.x() + ", " + a.y()); System.out.println(a.equals(b)); }} record Point(int x, int y) { }The output is the same, line for line. The only change in main is that the values are read with a.x() and a.y() instead of getX() and getY(). Step through it and look at the objects: each point is an ordinary object holding an x and a y, just like the long version.
Read the declaration from left to right:
recordstarts the declaration, where a class starts withclass.Pointis the name of the new type.(int x, int y)is the record header. Each entry in it is a component: one value the record carries, with a type and a name. A point has two components.{ }is the body. It can stay empty, because Java writes everything this record needs. Later in this lesson you put your own methods in it.
Key ideaA record is a class whose job is to carry a fixed set of values. You name the values once, in the header, and Java writes the constructor, the accessors,
equals,hashCodeandtoStringfrom them.
What Java writes for you#
From the one line record Point(int x, int y) { }, the compiler builds a class with these members:
| Member | For record Point(int x, int y) | What it does |
|---|---|---|
| A field for each component | private final int x; and the same for y | Holds the value. It is private, so outside code cannot touch it, and final, so it never changes after the constructor. |
| The canonical constructor | Point(int x, int y) | Takes one argument per component, in header order, and stores each one in its field. |
| An accessor for each component | public int x() and public int y() | Returns the component's value. It has the component's name. |
equals | a.equals(b) | True when b is also a Point and every component is equal. |
hashCode | a.hashCode() | An int built from all the components. Equal records always get the same one. |
toString | Point[x=1, y=2] | The record's name, then each component's name and value in square brackets. |
Canonicalmeans “the standard one”. The canonical constructor is the constructor whose parameters match the header exactly: the same types in the same order. Every record has one.
The widget below shows the same thing at full length. The top line is a record, and underneath is the ordinary class Java writes for it, close to what the compiler produces. Add a component, for example the int z from the bug story above, and see every place it has to appear. Tap a component to light up the lines that use it.
Your record
record Point(int x, int y) { }Components (tap one to light up its lines)
The same record, written out as an ordinary class
final class Point { private final int x; private final int y; Point(int x, int y) { this.x = x; this.y = y; } public int x() { return x; } public int y() { return y; } @Override public boolean equals(Object o) { if (!(o instanceof Point)) return false; Point other = (Point) o; return x == other.x && y == other.y; } @Override public int hashCode() { int h = 0; h = 31 * h + Integer.hashCode(x); h = 31 * h + Integer.hashCode(y); return h; } @Override public String toString() { return "Point[x=" + x + ", y=" + y + "]"; }}For a two-component point the class runs to 30 lines, and each component you add costs six more: a field, a line in the constructor, an accessor, and a line each in equals, hashCode and toString. In a record it costs a type and a name in the header.
Accessors: p.x(), not p.getX()#
To read a component you call its accessor, a method with the same name as the component and no parameters: p.x() and p.y(). Records leave off the get prefix you used for getters in the encapsulation lesson, so the header tells you which methods exist.
public class Main { public static void main(String[] args) { Point p = new Point(1, 2); System.out.println(p.x()); // fine: prints 1 System.out.println(p.getX()); // error: cannot find symbol System.out.println(p.x); // error: x has private access in Point }} record Point(int x, int y) { }The compiler rejects the last two lines. There is no method called getX, and p.x without parentheses names the field, which is private to the record. From outside the record, the accessor is the only way to read a value.
Book b = new Book("Dune", "Frank Herbert", 1965); for record Book(String title, String author, int year), which expression gives you the title?Printing and comparing records#
Two of the generated methods decide how a record prints and how it compares. Both are built from the components.
toString: readable from the start#
Printing a record shows its name and each component with its value. In OOP practice you wrote a toString yourself so that objects stopped printing as a class name and an address. A record has a readable one from the start.
public class Main { public static void main(String[] args) { Book b = new Book("Dune", "Frank Herbert", 1965); System.out.println(b); System.out.println(b.title() + " came out in " + b.year()); }} record Book(String title, String author, int year) { }This prints Book[title=Dune, author=Frank Herbert, year=1965] and then Dune came out in 1965. String components print without quotation marks, so the title shows up as title=Dune.
toString may change in a future version. Use it for printing and debugging. When a program needs a value, it should call the accessor instead of cutting the text apart.equals compares components, == compares objects#
Two points below hold the same numbers, and a third variable copies one of them. Decide what the program prints before you run it.
public class Main { public static void main(String[] args) { Point a = new Point(3, 4); Point b = new Point(3, 4); Point c = a; System.out.println(a.equals(b)); System.out.println(a == b); System.out.println(a == c); }} record Point(int x, int y) { }What do the three lines print?
a.equals(b) is true: both are points, and each component matches, 3 with 3 and 4 with 4. a == b is false, because == still asks whether two variables point at the same object, and new ran twice to make two objects. a == c is true because c = a copied the reference, so both names point at one point.
Key ideaTwo records are equal when they are the same record type and every component is equal.
==keeps its old meaning, “the same object”, so compare records withequals, as you do with strings.
hashCode: the partner of equals#
hashCode turns an object into a single int. Lookup structures such as HashSet and HashMap use that number to decide where to file an object, much as the first letter of a surname decides which drawer a paper file goes in. For that to work, two equal objects must always produce the same number. A record keeps this rule without any effort from you, because its hashCode is built from the same components as its equals.
public class Main { public static void main(String[] args) { Point a = new Point(3, 4); Point b = new Point(3, 4); Point d = new Point(4, 3); System.out.println("a equals b: " + a.equals(b)); System.out.println("same hash code: " + (a.hashCode() == b.hashCode())); System.out.println("a equals d: " + a.equals(d)); }} record Point(int x, int y) { }Equal points, equal hash codes. Unequal points usually get different numbers, but nothing promises that: two different points may share a hash code, and that is allowed.
equals and hashCode by hand for an ordinary class, come later in HashMap and HashSet and equals, hashCode and toString.Records do not change#
Look back at the table: every field is final, and a record has accessors but no setters. Once a record is built, its components never change. An object that cannot change after it is built is called immutable, the word you met in the strings lesson.
public class Main { public static void main(String[] args) { Point p = new Point(1, 2); p.setX(5); // error: cannot find symbol p.x = 5; // error: x has private access in Point }} record Point(int x, int y) { void moveRight() { x = x + 1; // error: cannot assign a value to final variable x }}There is no setter to call, the field is private to the record, and even a method inside the record cannot give a final field a new value.
This is what makes a record safe to share. In the references lesson, two variables pointed at one object, and a change made through one of them surprised the other. With a record that cannot happen: nobody can change the object, so it does not matter how many variables point at it.
To change a record, make a new one#
Real data still changes: a book is reprinted, or a point moves. With a record you build a new one that holds the new value. The usual way is a small method named withSomething that returns a copy with one component replaced. Java does not write these for you, but each one is short: a single return of a new record.
public class Main { public static void main(String[] args) { Book first = new Book("Dune", "Frank Herbert", 1965); Book reprint = first.withYear(2005); System.out.println(first); System.out.println(reprint); }} record Book(String title, String author, int year) { Book withYear(int newYear) { return new Book(title, author, newYear); }}first.withYear(2005) calls the canonical constructor with the same title and author and the new year, and returns that new book. first is untouched, so the output shows 1965 and then 2005. Step through it and you will find two separate book objects. It is the same arrangement as toUpperCase(), which gives you a new string and leaves the old one as it was.
public class Main { public static void main(String[] args) { Book b = new Book("Dune", 1965); b.withYear(2005); System.out.println(b.year()); b = b.withYear(2005); System.out.println(b.year()); }} record Book(String title, int year) { Book withYear(int newYear) { return new Book(title, newYear); }}The result of the first withYear call is never stored. What do the two lines print?
It prints 1965 and then 2005. The first call builds a book from 2005 and throws it away, because nothing stores it. The second call stores the new book back in b. So a with method is only useful when you keep its result, in a new variable or in the old one.
Checking values: the compact constructor#
The constructor Java writes accepts any values at all. new Range(9, 2) would build a range that starts at 9 and ends at 2, which makes no sense. In the encapsulation lesson a class guarded its fields inside its setters. A record has no setters, so the constructor is the one place to stand guard, and records have a short way to write it.
public class Main { public static void main(String[] args) { Range week = new Range(1, 7); System.out.println(week); Range broken = new Range(9, 2); System.out.println(broken); }} record Range(int lo, int hi) { Range { if (lo > hi) { throw new IllegalArgumentException("lo " + lo + " is above hi " + hi); } }}IllegalArgumentException: lo 9 is above hi 2
A method was given a value it does not accept.
The block Range { ... }, with no parentheses after the name, is a compact constructor: the canonical constructor written without its parameter list. The parameters still exist and have the components' names, lo and hi; you just do not write them out. Java runs your block first, then stores each parameter in its field.
The first range passes the check and prints Range[lo=1, hi=7]. For the second, lo > hi is true, so the throw line runs. throw new IllegalArgumentException(...) stops the program at once with that message, here lo 9 is above hi 2, and the broken range is never built. IllegalArgumentExceptionis the standard Java error for “you passed a value I cannot accept”.
throwmeans “refuse and stop”. Catching an exception so that the program can carry on is the subject of exception handling.Tidying values before they are stored#
A compact constructor can also change a parameter. Give it a new value, and the new value is what Java stores. Suppose a website treats usernames as the same whatever their capital letters or stray spaces. The record can store one tidy form of each name:
public class Main { public static void main(String[] args) { Username typed = new Username(" MayaK "); Username saved = new Username("mayak"); System.out.println(typed); System.out.println(typed.equals(saved)); }} record Username(String name) { Username { name = name.trim().toLowerCase(); }}Inside the compact constructor, name is the parameter. The line name = name.trim().toLowerCase(); replaces it with a tidied copy, using two string methods you already know, and when the block ends Java stores that copy in the field. Turning values into one standard form like this is called normalising. The output is Username[name=mayak] and true: every username goes through the same tidying, so two names that differ only in case or spaces end up equal.
The rules of a compact constructor#
- There are no parentheses after the name.
Range() { }would be a different constructor, one with no parameters. - The parameters are there, with the components' names. You may read them and give them new values.
- You may not assign the fields yourself.
this.lo = lo;is a compile error (“cannot assign a value to final variable lo”), because Java does that step after your block ends. - A
returnstatement is not allowed. The block runs to its end, or stops with an exception.
Range repairs a backwards range instead of refusing it: its compact constructor swaps lo and hi through a temporary variable. Put the lines in order so that the program prints Range[lo=2, hi=9].Tap a line, then tap where it should go. Or use the arrows.
The full form, and extra constructors#
You can also write the canonical constructor out in full, with its parameter list, the way you would in a class. Then storing the fields is your job:
public class Main { public static void main(String[] args) { Range week = new Range(1, 7); System.out.println(week); }} record Range(int lo, int hi) { Range(int lo, int hi) { if (lo > hi) { throw new IllegalArgumentException("lo " + lo + " is above hi " + hi); } this.lo = lo; this.hi = hi; }}It does the same job as the compact version and prints Range[lo=1, hi=7]. The compact form is shorter and stores the fields for you, so it is the usual choice.
A record may also have extra constructors with other parameters. Each one must hand over to another constructor with this(...), as you saw in the constructors lesson, so every record still passes through the canonical constructor and its checks:
public class Main { public static void main(String[] args) { Range firstFive = new Range(5); System.out.println(firstFive); }} record Range(int lo, int hi) { Range { if (lo > hi) { throw new IllegalArgumentException("lo " + lo + " is above hi " + hi); } } Range(int hi) { this(1, hi); }}new Range(5) runs the one-parameter constructor. It calls this(1, 5), which runs the compact constructor's check before the fields are stored, and the program prints Range[lo=1, hi=5].
Key ideaA compact constructor runs before the fields are stored. Throw an exception to refuse a bad value, or give a parameter a new value to tidy it.
record Rating(String film, int stars). Add a compact constructor that trims spaces from the film name and keeps stars between 1 and 5: anything below 1 becomes 1, and anything above 5 becomes 5.Adding your own methods#
The body of a record can hold more than a compact constructor. Anything you can work out from the components can be a method, written as in a class. Inside the record, the components are plain field names.
public class Main { public static void main(String[] args) { Range teens = new Range(13, 19); System.out.println(teens.length()); System.out.println(teens.contains(15)); System.out.println(teens.contains(21)); }} record Range(int lo, int hi) { int length() { return hi - lo + 1; } boolean contains(int n) { return n >= lo && n <= hi; }}teens.length() counts the whole numbers from 13 to 19, ends included, which is 7. contains checks whether a number falls inside the range: true for 15 and false for 21.
Static fields and static factory methods#
A record may have static fields and static methods, because those belong to the type rather than to any one record. A static factory method is a static method that builds an object and returns it. Its name can say something that new cannot, such as which unit a number is in:
public class Main { public static void main(String[] args) { Temperature boiling = Temperature.fromFahrenheit(212); System.out.println(boiling); System.out.println(Temperature.FREEZING); System.out.println(boiling.isWarmerThan(Temperature.FREEZING)); }} record Temperature(double celsius) { static final Temperature FREEZING = new Temperature(0); static Temperature fromFahrenheit(double fahrenheit) { return new Temperature((fahrenheit - 32) * 5 / 9); } boolean isWarmerThan(Temperature other) { return celsius > other.celsius; }}Temperature.fromFahrenheit(212) converts to Celsius and calls the constructor, which gives Temperature[celsius=100.0]. Someone reading new Temperature(212)could not tell which unit the 212 is in; the factory's name answers that. FREEZING is a static constant shared by the whole program, and isWarmerThan is an ordinary method that compares two temperatures, so the last line prints true.
Static fields are fine, but a record cannot declare an extra instance field in its body. Its state is its components and nothing else. If a point needs a z, the z goes in the header.
Writing your own toString#
Any of the generated methods can be replaced: write one yourself and Java uses yours instead. A playing card reads better as Q of hearts than as Card[rank=Q, suit=hearts]:
public class Main { public static void main(String[] args) { Card queen = new Card("Q", "hearts"); System.out.println(queen); System.out.println(queen.equals(new Card("Q", "hearts"))); }} record Card(String rank, String suit) { @Override public String toString() { return rank + " of " + suit; }}Your toString must be public, like the one every object inherits, and @Override asks the compiler to check that it really replaces that method. Only toString changed. equals is still Java's, so the second line prints true.
Implementing an interface#
A record cannot extend a class, but it can implement interfaces, which lets different records share a type. Here a square and a rectangle are both shapes:
public class Main { public static void main(String[] args) { Shape[] shapes = { new Square(3), new Rectangle(2, 5) }; for (Shape s : shapes) { System.out.println(s + " has area " + s.area()); } }} interface Shape { int area();} record Square(int side) implements Shape { public int area() { return side * side; }} record Rectangle(int width, int height) implements Shape { public int area() { return width * height; }}The loop only knows that it has Shape values. Each record supplies its own area(), which is polymorphism at work: the output is Square[side=3] has area 9 and then Rectangle[width=2, height=5] has area 10. The area methods are public because interface methods are public, so whatever implements them must make them public too.
Shape. The next lesson, sealed types and pattern matching, shows how to close the list, so that only the shapes you name can implement Shape, and how a record pattern such as shape instanceof Rect(double w, double h) takes a record apart into its components in one step.Item(String name, int priceInCents, int quantity). Finish total() so that it returns the price times the quantity, and withQuantity so that it returns a new item with a different quantity and everything else the same.Records that hold other objects#
A component can have any type, including another record or a list. How the record behaves then depends on the objects inside it.
A record made of records#
A line segment on the canvas is two points, so it can be a record whose components are records:
public class Main { public static void main(String[] args) { Line line = new Line(new Point(0, 0), new Point(3, 4)); System.out.println(line); System.out.println(line.end().x()); System.out.println(line.equals(new Line(new Point(0, 0), new Point(3, 4)))); }} record Point(int x, int y) { } record Line(Point start, Point end) { }The generated methods reach inside. Line's toString prints each point with Point's own toString, giving Line[start=Point[x=0, y=0], end=Point[x=3, y=4]]. Line's equals compares the points with Point's equals, so two separately built lines with the same ends are equal. line.end().x() is two accessors in a row: end() gives a point, and x()gives that point's x, which is 3.
Records inside a class#
A record can also be declared inside another class, which suits a record that only that class uses. A record declared this way is automatically static, so it never carries a hidden link to an object of the outer class (the difference between static nested and inner classes from the inner classes lesson).
public class Main { record Score(String player, int points) { } public static void main(String[] args) { Score[] table = { new Score("Ana", 12), new Score("Ben", 9) }; for (Score s : table) { System.out.println(s.player() + ": " + s.points()); } }}This prints Ana: 12 and Ben: 9. You can even declare a record inside a method, as a local record, when one method needs to group a few values for a moment.
Records in a list#
A robot on a grid keeps each square it visits as a Point in an ArrayList. The list methods that search, such as contains, indexOf and remove, compare with equals:
import java.util.ArrayList; public class Main { public static void main(String[] args) { ArrayList<Point> visited = new ArrayList<>(); visited.add(new Point(0, 0)); visited.add(new Point(1, 0)); visited.add(new Point(1, 1)); System.out.println(visited); System.out.println(visited.contains(new Point(1, 0))); System.out.println(visited.indexOf(new Point(1, 1))); visited.remove(new Point(0, 0)); System.out.println(visited); }} record Point(int x, int y) { }visited.contains(new Point(1, 0)) is true. The point built on that line is a different object from the one in the list, but the two are equal, and equal is what contains looks for. indexOf reports position 2, and remove takes out the equal point at the front. With a class that has no equals of its own, all three would find nothing, since the inherited equals only matches the very same object.
A list inside a record: shallowly immutable#
Immutability has a catch. A final field always points at the same object, but that object can still change if it is the changeable kind. Strings and numbers cannot change, and neither can a record built only from them. A list can grow. Step through this playlist and watch the list in the memory view:
import java.util.ArrayList;import java.util.List; public class Main { public static void main(String[] args) { List<String> songs = new ArrayList<>(); songs.add("Intro"); songs.add("Sunrise"); Playlist mix = new Playlist("Morning", songs); System.out.println(mix); songs.add("Surprise"); System.out.println(mix); mix.songs().add("Another one"); System.out.println(mix); }} record Playlist(String name, List<String> songs) { }The playlist changes twice without anyone touching the record. First, the record stored the very list it was given, so main's songs variable and the record share one list: the aliasing from the references lesson. Adding Surprise through songs changes the playlist. Second, mix.songs() hands out that same list, so any code can add to it.
The record's own fields never changed. They point at the same name and the same list from start to finish. The Java documentation calls a record shallowly immutable for this reason: its fields are fixed, but the objects they point to may not be.
The fix is to store a copy that cannot change. List.copyOf(list) builds a new list with the same items, and like the lists made by List.of in the ArrayList lesson, it is unmodifiable: adding, removing or replacing an item throws an UnsupportedOperationException. Make the copy in the compact constructor, so that every playlist gets one. The last line of main tries to sneak a song in through the accessor; watch what happens.
import java.util.ArrayList;import java.util.List; public class Main { public static void main(String[] args) { List<String> songs = new ArrayList<>(); songs.add("Intro"); songs.add("Sunrise"); Playlist mix = new Playlist("Morning", songs); songs.add("Surprise"); System.out.println(mix); mix.songs().add("Another one"); System.out.println(mix); }} record Playlist(String name, List<String> songs) { Playlist { songs = List.copyOf(songs); }}UnsupportedOperationException
This collection cannot be changed. List.of and similar methods make read-only lists.
main's later add now changes only its own list, so the record still prints two songs: Playlist[name=Morning, songs=[Intro, Sunrise]]. The attempt through mix.songs() is refused, and the program stops with an UnsupportedOperationException. Either way, the playlist stays as it was built.
List.copyOf in the compact constructor. Components that cannot change, such as strings, numbers and enums, need no copy, and neither does a record built only from them. List.copyOf also refuses a list that contains null, throwing a NullPointerException, which for most records is a welcome extra check.record Team(String name, List<String> members), stops outside code from changing a team's members?Record or class?#
A record is a class with one narrow job, and its limits come from that job.
What a record cannot do#
- Extend another class. Every record already extends
java.lang.Record, and a Java class has only one parent. It can implement as many interfaces as it needs. - Be extended. A record is
final. - Declare instance fields beyond its components. Static fields are fine.
- Change after it is built. There are no setters, and the fields are final.
class Shape { String label = "shape";} record Circle(double radius) extends Shape { } // error: '{' expected record Square(int side) { } class BigSquare extends Square { // error: cannot inherit from final Square BigSquare() { super(10); }} record Point(int x, int y) { int z; // error: field declaration must be static}Each marked line breaks a rule, and its comment shows the compiler's message. The first message is an odd one, '{' expected, because the compiler does not accept the word extends after a record header at all.
Choosing between them#
A good test is to ask whether the thing changes over time or is a fact. A bank account changes every time money moves, so it is a class. Each transaction on that account is a fact: an amount, a date and a description that never change once the transaction has happened. A transaction suits a record.
deposit or setName need to change fields.Point, Range, Book, a transaction, a search result.BankAccount, ShoppingCart, Game, Counter.Key ideaUse a record for a value: a fixed group of facts, equal when the facts are equal. Use a class for something with a life of its own: state that changes, or details it keeps to itself.
Try it
Answer each one in your head or on paper first, then open the answer.
record Song(String title, int seconds) { }.Range { if (lo > hi) { this.lo = hi; this.hi = lo; } }. What is wrong, and what should they write instead?record Account(String owner, double balance) and wants a deposit(double amount) method that adds to the balance. Why will that not work, and what would you suggest?Practice
Work through these on your own. Each one builds on this page.
Student class with a name, an id and a GPA. Rewrite it as a record. Add a compact constructor that refuses a GPA below 0.0 or above 4.0 with an IllegalArgumentException, and an isHonorRoll() method that returns true for a GPA of 3.5 or more. Count how many lines you saved.record Fraction(int numerator, int denominator). Its compact constructor should refuse a denominator of 0 and move a minus sign from the denominator to the numerator, so that new Fraction(1, -2) prints Fraction[numerator=-1, denominator=2]. Add a method double value(), and check that new Fraction(1, -2).equals(new Fraction(-1, 2)) is true.(0, 0) and follows the moves in the string "RRULLD": R is right (x plus 1), L is left, U is up (y plus 1) and D is down. Keep every square it stands on as a Point record in an ArrayList, adding a square only if the list does not already contain it. Print how many different squares the robot visited, the start included. You should get 6.record Triangle(int base, int height) implements Shape to the shapes example, with area() returning base * height / 2. Put a triangle with base 3 and height 5 in the array. What area does it report, and why is it not 7.5? Change the program so it reports 7.5, and say what else had to change.Further reading#
- JEP 395: Records, the proposal that made records part of Java 16, with the reasons behind each rule
- java.lang.Record in the Java SE 25 API documentation, with the rules the generated equals, hashCode and toString follow
- Record classes in the Java Language Specification, section 8.10, the precise rules for components, accessors and compact constructors
- The records tutorial on dev.java, the official Java learning site
Spotted a mistake, or a part that did not make sense? Report it by email