Lesson 56Phase 6: Functional and modern Java
Modern Java syntax: var, text blocks and more
The newer language features that make everyday code shorter: var, text blocks, switch expressions, compact main methods and helpful error messages.
By BasithUpdated 40 min
- var
- text block
- switch expression
- pattern matching
- compact source file
- String methods
A new version of Java comes out every six months, and many of them add something small that makes everyday code shorter or easier to read. This lesson collects those additions in one place. Some are new to you: var, text blocks for text that runs over several lines, a few newer String methods, and the underscore _ for values you never use. Others you have met already, and here they work together in one program. A table at the end says which Java version brought each feature, so you know what you can use on an older Java at school or at work.
- Declare local variables with
var, say where Java refuses it, and decide when writing the type out reads better. - Write text that spans several lines as a text block, and predict the exact string from its indentation and its closing delimiter.
- Keep spaces at the end of a line with
\s, join lines with a backslash, and fill in values withformatted. - Use the newer
Stringmethods:strip,lines,indent,stripIndentandchars. - Combine record patterns,
whenguards and_in one switch, and use_for any variable you must declare but never read. - Say which Java version made each feature on this page standard.
var briefly in variables and types, and isBlank, repeat and strip in strings. The diamond <> from generics comes back next to var. The methods lines and chars hand you streams, and formatted fills in the same %s and %d blanks as String.format from money and exact numbers. Switch expressions, records, sealed types and patterns each had a lesson of their own, and this page links back to them where they come up.var: let the compiler write the type#
Here is the same variable from a bookshop program, declared three ways:
TreeMap<String, Integer> stock = new TreeMap<String, Integer>();TreeMap<String, Integer> stock = new TreeMap<>();var stock = new TreeMap<String, Integer>();The first line names the type twice. The diamond from generics lets you drop the second copy. Since Java 10 you can drop the first copy instead, by writing var. It tells the compiler: work out this variable's type from the value on the right. The compiler sees new TreeMap<String, Integer>(), so stock gets exactly that type, the same as in the first line.
This is called local variable type inference. Inference means the compiler works the type out for itself. Local means it only works for local variables, the ones declared inside a method, which exist only while the method runs.
import java.util.ArrayList;import java.util.TreeMap; public class Main { public static void main(String[] args) { var copies = 3; var price = 9.99; var title = "Dune"; var tags = new ArrayList<String>(); tags.add("sci-fi"); tags.add("classic"); var stock = new TreeMap<String, Integer>(); stock.put("Dune", copies); stock.put("Emma", 5); for (var entry : stock.entrySet()) { System.out.println(entry.getKey() + ": " + entry.getValue()); } System.out.println(title + " costs " + price + ", tags " + tags); }}Step through it. On the first three lines the narration says Declare int copies, Declare double price and Declare String title, and the memory view shows each variable's type next to its name. The compiler read 3 as an int, 9.99 as a double and the text in quotes as a String. The loop variable entry is where var saves the most typing: its full type is Map.Entry<String, Integer>. The program prints Dune: 3, Emma: 5 and Dune costs 9.99, tags [sci-fi, classic].
The type is still fixed
var does not make a variable flexible. Once the compiler has picked a type, the variable keeps it for life, exactly as if you had written the type yourself. Try to put text into a variable that started out as a number:
public class Main { public static void main(String[] args) { var count = 10; count = "ten"; System.out.println(count); }}$ javac Main.javaMain.java:4: error: incompatible types: String cannot be converted to int count = "ten"; ^1 errorThat is the message you would get after int count = 10; as well. The static typing you met in variables and types works the same with or without var.
var or letcan hold a number now and a string later, and Python variables work the same way. Java's var looks alike and does much less: it only saves you typing the type. Java still gives the variable one type and checks every use of it before the program runs.Key idea
varasks the compiler to copy the type from the starting value. The variable still has one fixed type, checked before the program runs, exactly as if you had written it out.
Where var works
You can use var wherever you declare a local variable and give it a value in the same statement:
- A local variable with a starting value:
var total = 0; - The counter of a
forloop:for (var i = 0; i < 3; i++) - The variable of an enhanced
forloop:for (var entry : stock.entrySet()) - A resource in try-with-resources, from reading files:
try (var br = new BufferedReader(new FileReader("notes.txt"))) - The parameters of a lambda, since Java 11:
(var a, var b) -> a + b. Either every parameter usesvaror none does.
The lambda form looks pointless, since (a, b) -> a + b already leaves the types out. It exists so that you can put an annotation on a lambda parameter without writing its type, and annotations are the subject of the next lesson.
Where var does not work
var needs a value to copy a type from. These are the two mistakes people make first:
public class Main { public static void main(String[] args) { var total; total = 42; var winner = null; System.out.println(total + " " + winner); }}$ javac Main.javaMain.java:3: error: cannot infer type for local variable total var total; ^ (cannot use 'var' on variable without initializer)Main.java:6: error: cannot infer type for local variable winner var winner = null; ^ (variable initializer is 'null')2 errorsThe first error is about the missing starting value, which javac calls an initializer. int total; followed by total = 42; is fine, because the type is written down, but with var the compiler would have nothing to read the type from. The second error is about null. It means “no object”, and it says nothing about which kind of object might arrive later, so there is no type to copy.
A few more places where javac says no:
| What you write | What javac says | Write this instead |
|---|---|---|
var scores = {90, 85, 77}; | array initializer needs an explicit target-type | var scores = new int[] {90, 85, 77}; |
var shout = s -> s.toUpperCase(); | lambda expression needs an explicit target-type | Function<String, String> shout = s -> s.toUpperCase(); |
var on a field, a method parameter or a return type | 'var' is not allowed here | the type, written out |
var a = 1, b = 2; | 'var' is not allowed in a compound declaration | one variable per declaration |
The braces of an array and the arrow of a lambda only make sense once Java knows the type they are for, so var cannot work it out from them. The last two rows show the other limit: var is only for local variables. Fields, parameters and return types are how a class talks to the rest of the program, so Java wants their types written where every reader can see them.
When var makes code harder to read
Code is read far more often than it is written, and var hides the type from the reader. That costs nothing when the right-hand side shows the type anyway. It hurts in three situations.
The first is a method call whose result is not obvious. In var result = library.search("dune");, is result one book, a list of books or an Optional? The reader has to go and find search to know. Writing List<Book> result answers the question on the spot, and so does a better name such as matchingBooks.
The second is a plain number. A whole number written without a decimal point is always an int, so var makes an int even when you meant something else. Decide what this prints before you run it.
public class Main { public static void main(String[] args) { var slices = 10; var friends = 4; System.out.println(slices / friends); var price = 10.0; System.out.println(price / friends); }}Each var takes its type from its starting value. What do the two lines print?
It prints 2 and then 2.5. 10 made slices an int, so slices / friends is integer division, the surprise from operators and casting. 10.0 made price a double, and the division kept its half. With the types written out, int slices = 10; would have shown the problem at a glance.
The third is the diamond. In var names = new ArrayList<>(); nothing says what the list holds, so Java picks the most general type there is, Object, and names becomes an ArrayList<Object>. The mistake shows up later, far from its cause:
import java.util.ArrayList; public class Main { public static void main(String[] args) { var names = new ArrayList<>(); names.add("Ada"); names.add("Grace"); String first = names.get(0); System.out.println(first.toUpperCase()); }}$ javac Main.javaMain.java:9: error: incompatible types: Object cannot be converted to String String first = names.get(0); ^1 errorWith var, put the type arguments on the right: var names = new ArrayList<String>();. Or keep the type on the left and the diamond on the right, as you did in generics. Here is a rule of thumb drawn from the guide the Java team wrote on using var:
new TreeMap<String, Integer>(), text in quotes, a number with a decimal point.entry in a loop over a map.long or a double.var, look at the variable's name. With the type gone, the name has to say what the value is: count instead of c, matchingBooks instead of list. Most of this course still writes types out, because seeing them helps while you learn.Text blocks: text that keeps its shape#
Writing several lines of text in an ordinary string is fiddly. Every line needs a \n at the end and a + to join it to the next, and every quotation mark inside the text needs a backslash in front. Here is a short note to a friend written that way, and the same note as a text block:
public class Main { public static void main(String[] args) { String oldWay = "Dear Sam,\n" + "Chapter 3 made me say \"wow\" out loud.\n" + "See you Friday,\n" + "Ada\n"; String newWay = """ Dear Sam, Chapter 3 made me say "wow" out loud. See you Friday, Ada """; System.out.print(newWay); System.out.println(oldWay.equals(newWay)); }}A text block starts with three double quotes, """, and a line break. The text begins on the next line and runs until the closing """. Line breaks are real line breaks, and quotation marks need no backslash. The program prints the note and then true: both versions build exactly the same string.
A text block is just another way to write a String. It works anywhere a string in quotes would, and every String method works on it. Text blocks became a standard part of Java in Java 15, and they are the usual way to put SQL, JSON or HTML into a Java program.
""" on its line, because the text always starts on the next one. String s = """Hello"""; does not compile: javac reports illegal text block open delimiter sequence, missing line terminator. For one line of text, use ordinary quotes.Which spaces Java keeps#
In the source code, every line of that note starts with 16 spaces, which line it up with the code around it. None of those spaces reach the output: Java removes them on purpose. The designers of text blocks call them incidental white space, spaces that only come from where the code sits.
Java looks at every line that has text on it, and at the line of the closing delimiter when the delimiter stands on its own. It finds the smallest indentation among those lines and removes that many spaces from the start of every line. Any extra indentation a line has stays in the string. Then Java removes the spaces at the end of each line.
public class Main { public static void main(String[] args) { String menu = """ Breakfast Eggs Toast Lunch Soup """; System.out.print(menu); }}Breakfast, Lunch and the closing delimiter are the least indented lines, at 16 spaces, so Java removes 16 from every line. The dishes sit 4 spaces further in, and they keep those 4:
Breakfast Eggs ToastLunch SoupKey ideaJava removes the indentation that all the lines share, counting the closing delimiter's line when it stands alone, and the spaces at the end of each line. The rest of what you typed stays.
Where the closing delimiter goes#
Because the closing delimiter's line can count, where you put it changes the string:
- On its own line, lined up with the text, as in the menu. The string ends with a line break after the last line, and no extra indentation is kept.
- At the end of the last line of text, as in
Soup""";. That line holds text, so only the text lines count, and the string ends right afterSoupwith no final line break. - On its own line, further left than the text. Now it is the least indented line, so Java removes less, and every line keeps the difference. At the very start of its line, it makes Java remove nothing at all.
Further right than the text makes no difference, because the text lines are then the least indented. Try each position in the lab. It writes the block at the left edge of the code, so the numbers stay small: Breakfast and Lunch start 4 spaces in, the dishes 8.
Each dot is a space in the source code. Move the closing """ and watch which spaces Java keeps.
The string in menu, line by line
The closing delimiter lines up with Breakfast and Lunch, at 4 spaces, so Java removes 4 from every line. The dishes keep the 4 extra spaces that set them apart.
Now check yourself on the two most common positions. The square brackets in the output show where each string starts and ends.
public class Main { public static void main(String[] args) { String a = """ red green"""; String b = """ red green """; System.out.println("[" + a + "]"); System.out.println("[" + b + "]"); }}The two text blocks differ only in where the closing delimiter sits. Write the lines this program prints.
It prints five lines: [red, green], [red, green and a line holding only ]. The string a ends right after green, so its closing bracket follows at once. The string b ends with a line break, so its closing bracket lands on a line of its own.
Two escapes made for text blocks#
The usual escapes such as \t still work inside a text block, and two more are useful here:
- A backslash at the very end of a line joins that line to the next. It lets you keep a long sentence on short lines in your code without putting a line break into the string.
\sstands for one space. Java translates escapes last, after it has removed the spaces at the ends of lines, so a\sat the end of a line protects the spaces in front of it, and then becomes a space itself.
public class Main { public static void main(String[] args) { String sentence = """ Java removes the line break \ after a backslash."""; System.out.println(sentence); String plain = """ Total """; String kept = """ Total \s"""; System.out.println("[" + plain + "]"); System.out.println("[" + kept + "]"); }}Java removes the line break after a backslash.[Total][Total ]The first text block prints as one line. In plain, the three spaces after Total are removed, even though you can see them in front of the closing delimiter. In kept, the two spaces in front of \s survive and \s adds a third, so three spaces sit before the closing bracket. Spaces at the end of a line matter in a few places, such as files where every line must be exactly the same length.
" in a row need no escape. Three in a row would end the block, so write \""" to put three quotation marks into the text.Filling in values with formatted#
A text block has no way to drop a variable straight into the text. Joining pieces with + works, but it chops up the shape you were trying to keep. The tidy way is the method formatted, added in Java 15 for this job. It works like String.format, called on the text itself: each blank, such as %s or %d, is filled in from the values you pass, in order.
public class Main { public static void main(String[] args) { String guest = "Mia"; int nights = 3; double price = 89.5; String confirmation = """ Hello %s, Your room is booked for %d nights. Total to pay: %.2f """.formatted(guest, nights, nights * price); System.out.print(confirmation); }}%s takes guest, %d takes the whole number of nights, and %.2f takes the total and shows it with two decimal places, so the last line reads Total to pay: 268.50. "...".formatted(a, b) gives the same result as String.format("...", a, b). On a text block, formatted keeps the values right next to the text they fill.
Game over, Ravi! and then Final score: 1200. The values must exist before the text block can use them.Tap a line, then tap where it should go. Or use the arrows.
slip with a text block and formatted so the program prints four lines: Borrowed: Dune, then by Frank Herbert and Due back in 21 days, each indented by two spaces, then Enjoy the book, Ravi!. The slip ends with a line break.Newer String methods#
Java 11 and the versions after it gave String a set of methods for jobs that used to need a loop. Like every String method, each one hands back a new value and leaves the original alone. You met three of them in strings: isBlank, repeat and strip. This section shows what strip does that trim does not, and adds the rest.
strip, stripLeading and stripTrailing
strip() removes white space from both ends, like trim(). Its two halves clean one end only: stripLeading() the start and stripTrailing() the end. The difference from trim() is what counts as white space.
public class Main { public static void main(String[] args) { String typed = " Ada Lovelace "; System.out.println("[" + typed.strip() + "]"); System.out.println("[" + typed.stripLeading() + "]"); System.out.println("[" + typed.stripTrailing() + "]"); String wide = "\u3000Ada\u3000"; System.out.println(wide.trim().length()); System.out.println(wide.strip().length()); String spaces = " "; System.out.println(spaces.isEmpty()); System.out.println(spaces.isBlank()); }}[Ada Lovelace][Ada Lovelace ][ Ada Lovelace]53falsetrueThe brackets show what each of the first three calls kept. The next two lines show the difference from trim(). \u3000 is the wide space used in Chinese and Japanese text. trim() only knows the ordinary space and the invisible control characters below it, so it leaves both wide spaces and the length stays 5. strip() removes every character Java counts as white space, so only the three letters of Ada remain: 3. The last two lines are the recap from strings: three spaces are not empty, so isEmpty() gives false, but they are blank, so isBlank() gives true.
strip() where older code uses trim(), and check typed input with isBlank(): a name made of three spaces is not a name. One character still slips through: the non-breaking space that web pages use, \u00A0, is not white space to Java, so neither strip() nor trim() removes it.repeat and lines
repeat(n) gives the string n times in a row, which is handy for dividing lines and bars in console output. lines() cuts a string at its line breaks and hands you the lines as a Stream<String>, without the line breaks, so every stream method from streams is ready to use.
public class Main { public static void main(String[] args) { System.out.println("=".repeat(24)); System.out.println("Ha".repeat(3)); String song = """ Row, row, row your boat Gently down the stream Merrily, merrily """; System.out.println(song.lines().count()); song.lines() .filter(line -> !line.isBlank()) .map(String::toUpperCase) .forEach(System.out::println); }}It prints a row of 24 equals signs, then HaHaHa, then 4. The song has 4 lines: the empty line in the middle counts, and the line break at the very end only finishes the last line, so it does not start a fifth. The stream then drops the blank line and prints the other three in capitals.
indent and stripIndent
indent(n) moves every line n spaces to the right, or up to n spaces to the left when n is negative, and makes sure the result ends with a line break. stripIndent() runs the text block rule on any string: it removes the indentation that all the lines share, and the spaces at the ends of lines. That helps with text your program builds or reads, such as a quote copied out of an indented e-mail.
public class Main { public static void main(String[] args) { String recipe = "Mix the flour\nAdd two eggs"; System.out.print(recipe.indent(4)); System.out.print(" Bake for 20 minutes\n".indent(-6)); String quote = " To be, or not to be,\n that is the question."; System.out.println(quote.stripIndent()); }} Mix the flour Add two eggs Bake for 20 minutesTo be, or not to be,that is the question.indent(4) pushes both recipe lines 4 spaces to the right and adds a line break after the second, which is why print is enough. indent(-6) takes 6 of the 8 spaces in front of Bake away. stripIndent() sees that both lines of the quote start with 8 spaces and removes all 8.
chars: the characters as numbers
chars() is older than the rest, from Java 8, and it surprises nearly everyone once. It returns an IntStream, a stream of int values, with one number for each character. Decide what this prints.
public class Main { public static void main(String[] args) { "Hi!".chars().forEach(c -> System.out.println(c)); }}The stream holds one value for each character of Hi!. Pick the output.
It prints 72, 105 and 33. A char is stored as a number, its code in the character table, as you saw in operators and casting, and chars() hands you those numbers. To turn one back into a character, cast it: (char) c. Often you do not need to, because comparing with a character such as c == 's' works on the numbers directly, and so do Character methods such as isDigit:
public class Main { public static void main(String[] args) { String word = "Mississippi"; long esses = word.chars().filter(c -> c == 's').count(); System.out.println(esses); String pin = "4071"; System.out.println(pin.chars().allMatch(Character::isDigit)); System.out.println("40x1".chars().allMatch(Character::isDigit)); }}It prints 4, true and false. Counting one letter takes a single line, and so does checking that a PIN is made only of digits.
| Method | What it gives back | Since |
|---|---|---|
strip(), stripLeading(), stripTrailing() | a new string without white space at both ends, at the start, or at the end | Java 11 |
isBlank() | true when the string is empty or only white space | Java 11 |
repeat(n) | the string n times in a row | Java 11 |
lines() | a Stream<String> of the lines, without line breaks | Java 11 |
indent(n) | every line moved n spaces, ending with a line break | Java 12 |
stripIndent() | the text block indentation rule, applied to any string | Java 15 |
formatted(...) | the string with its blanks filled in, like String.format | Java 15 |
chars() | an IntStream of character codes | Java 8 |
"tea\n\ncake\n".lines().count() count?1. milk, then the number of items.Switch, records and patterns in one program#
You have met three more of Java's newer features in earlier lessons. A switch expression produces a value, with yield for a case that needs several lines, from making decisions. A record holds plain data in one line, from records. Patterns test what kind of value you have and take it apart, and a sealed type closes the list of kinds, from sealed types and pattern matching. Modern Java code uses them together, so here they are in one program: a music app's queue of songs, podcasts and adverts.
public class Main { public static void main(String[] args) { Media[] queue = { new Song("Clair de Lune", 302), new Song("Intro", 45), new Podcast("Java Weekly", 12, 74), new Ad("Sunny Juice", 15) }; for (Media item : queue) { System.out.println(describe(item)); } if (queue[2] instanceof Podcast(var show, _, var minutes) && minutes > 60) { System.out.println("Save " + show + " for the train"); } } static String describe(Media item) { return switch (item) { case Song(var title, var seconds) when seconds < 60 -> "A short song: " + title; case Song(var title, var seconds) -> title + ", " + seconds / 60 + " min"; case Podcast(var show, var episode, var minutes) -> { String size = minutes > 60 ? "long" : "short"; yield show + " #" + episode + ", a " + size + " one"; } case Ad _ -> "An advert"; }; }} sealed interface Media permits Song, Podcast, Ad {}record Song(String title, int seconds) implements Media {}record Podcast(String show, int episode, int minutes) implements Media {}record Ad(String brand, int seconds) implements Media {}The sealed interface Media says there are exactly three kinds of item, each a record. Read describe from the top:
case Song(var title, var seconds) when seconds < 60is a record pattern with a guard. It matches a song, unpacks its two components into variables, and takes only songs shorter than a minute.- The next case takes every other song. It has to come after the guarded one, or it would take all songs and javac would reject the guarded case as dominated.
- The podcast case needs two lines, so its arrow leads to a block that ends with
yield. case Ad _is a type pattern without a name, because describing an advert needs nothing from inside it.- There is no
default.Mediais sealed, so these cases cover every possible item, and javac checks that when it compiles the program.
The if at the end of main uses a record pattern with instanceof. Podcast(var show, _, var minutes) unpacks the show and the length and skips the episode number, and the test after && can use minutes because it only runs when the pattern matched. The program prints Clair de Lune, 5 min, A short song: Intro, Java Weekly #12, a long one, An advert and Save Java Weekly for the train.
The unnamed variable _#
You just saw _ twice: for the episode number nobody needed, and in place of a name for the advert nobody looked inside. Since Java 22, a single underscore can stand in for a variable that Java makes you declare but that you never read. It is called the unnamed variable, and it tells the reader that the value is unused on purpose. It works outside patterns too:
import java.util.TreeMap; public class Main { public static void main(String[] args) { var plays = new TreeMap<String, Integer>(); plays.put("Blue", 12); plays.put("Intro", 3); plays.forEach((title, _) -> System.out.println(title)); try { int volume = Integer.parseInt("loud"); System.out.println(volume); } catch (NumberFormatException _) { System.out.println("Volume must be a number"); } }}It prints Blue, Intro and Volume must be a number. forEach on a map hands its lambda two values, a key and a value. This one only prints titles, so the play count is _. A catch must declare its exception even when the block never uses it, so that is _ as well. The same goes for the variable of an enhanced for loop that only counts its turns. Three rules come with it:
- You cannot read
_.System.out.println(_);stops javac withunderscore not allowed here. - One method can declare several, even side by side as in
(_, _) -> 0, because none of them has a name to clash with. - It is for local variables, lambda and
catchparameters, and patterns. A field or a method parameter cannot be_.
Key ideaWrite
_where Java makes you name something you will never read: a part of a pattern, a caught exception, a lambda parameter. The reader then knows it is unused on purpose.
_. Which one does not compile?Compact source files and helpful error messages#
Compact source files
Your very first program, in your first Java program, was a compact source file: a void main() with no class written around it, printing with IO.println. Java 25 made that form standard. Two more facts about it are useful now. A compact source file may declare records, classes and other methods next to main. And it can use every public class in Java's core library, the part called java.base that holds java.util, java.io and java.time, without a single import line. Here is a small program in that form that uses several features from this page:
record Item(String name, int cents) {} void main() { var basket = List.of(new Item("Bread", 145), new Item("Milk", 89), new Item("Jam", 230)); var total = 0; for (var item : basket) { total += item.cents(); } var receipt = """ Items: %d Total: %.2f """.formatted(basket.size(), total / 100.0); IO.print(receipt);}It prints Items: 3 and Total: 4.64. List needed no import, and the record Item sits right in the file. The classic form you see everywhere else in this course still matters: real projects are made of many classes in many files, and compact files are meant for small programs like this one.
Helpful NullPointerException messages
In reading errors and debugging you read a message such as Cannot invoke "String.length()" because "last" is null. Java 14 added these detailed messages, and since Java 15 they are switched on by default. They help most in a chain of calls, where more than one value could be the null:
public class Main { public static void main(String[] args) { Customer grace = new Customer(new Address("Arlington")); Customer ada = new Customer(null); System.out.println(grace.getAddress().getCity()); System.out.println(ada.getAddress().getCity()); }} class Customer { private Address address; Customer(Address address) { this.address = address; } Address getAddress() { return address; }} class Address { private String city; Address(String city) { this.city = city; } String getCity() { return city; }}NullPointerException: Cannot invoke "Address.getCity()" because the return value of "Customer.getAddress()" is null
A variable held null, so there was no object to use.
Grace has an address, so the first line prints Arlington. Ada's address is null, and the second line stops the program with this message: Cannot invoke "Address.getCity()" because the return value of "Customer.getAddress()" is null. It names both halves of the problem. The call that failed is getCity(), and the value it was called on was the null that getAddress() returned. Before Java 14 the exception carried no message at all, only a line number, and you had to work out for yourself which call was to blame. If a customer may really have no address, the Optional from the previous lesson says so in the type.
Which Java version brought what#
Since 2018 a new Java has come out every March and every September. A new language feature usually spends a version or more as a preview, which you have to switch on with the --enable-preview option, before it becomes a standard part of Java. The table lists the version where each feature became standard. The versions marked LTS get long-term support, so most companies run one of them. If your job uses Java 17, everything in the rows up to Java 17 works there, and nothing below it does.
| Version | Released | What became standard |
|---|---|---|
| Java 8 (LTS) | March 2014 | Lambdas, method references, streams and Optional |
| Java 10 | March 2018 | var for local variables (JEP 286) |
| Java 11 (LTS) | September 2018 | var in lambda parameters (JEP 323), the String methods strip, isBlank, lines and repeat, and running a file with java Main.java (JEP 330) |
| Java 14 | March 2020 | Switch expressions and yield (JEP 361), and helpful NullPointerException messages (JEP 358), on by default from Java 15 |
| Java 15 | September 2020 | Text blocks (JEP 378), with stripIndent and formatted |
| Java 16 | March 2021 | Records (JEP 395) and instanceof patterns (JEP 394) |
| Java 17 (LTS) | September 2021 | Sealed classes and interfaces (JEP 409) |
| Java 21 (LTS) | September 2023 | Patterns in switch, with when and case null (JEP 441), and record patterns (JEP 440) |
| Java 22 | March 2024 | The unnamed variable and pattern _ (JEP 456) |
| Java 25 (LTS) | September 2025 | Compact source files, void main() and the IO class (JEP 512) |
Try it
Answer each one in your head or on paper first, then open the answer.
var, and say what type groups has afterwards: HashMap<String, List<String>> groups = new HashMap<String, List<String>>();var total; fail to compile, when int total; is fine?Cannot invoke "Order.getTotal()" because the return value of "Customer.lastOrder()" is null. Which method returned null, and which call failed?case Podcast(var show, _, var minutes) ->, what does the _ do, and can the code after the arrow use it?Practice
Work through these on your own. Each one builds on this page.
var. Then put the type back wherever the right-hand side no longer tells a reader what the variable holds. Write one sentence for each type you put back.String box(String text) that draws a frame around text, so that box("Hello") returns three lines: +-------+, | Hello | and +-------+. Then make it work for text with several lines, padding each line to the width of the longest.{"title": "Dune", "pages": 412} spread over four lines, with the title and the page count filled in by formatted. Print it. Then move the closing delimiter 4 spaces to the left, predict what changes in the output, and run it again to check.Video record with a title, a length in minutes and a boolean for subtitles to the playlist's sealed Media family. On your own computer, compile before you touch describe and write down what javac says. Then add a case for videos with subtitles, using a guard, and one for the rest, with _ for every component a case does not need.Further reading#
- Using the Var Type Identifier, the dev.java tutorial on where var can and cannot go
- Local Variable Type Inference: Style Guidelines, the Java team's advice on when var helps the reader
- Programmer's Guide to Text Blocks, Oracle's guide to indentation, the closing delimiter and the escapes
- JEP 456: Unnamed Variables and Patterns, every place the underscore may go and why
Spotted a mistake, or a part that did not make sense? Report it by email