- Write a simple data class as a
recordand usevarsensibly - Write lambda expressions and method references
- Process collections with streams (
filter,map,sorted) and combine everything in a project
Java is over 30 years old, but it has not aged: in recent years the language has gained features that make code much shorter. A class that used to take 40 lines becomes a one-line record, and a five-line loop becomes a one-line stream. In this lesson you will meet these features and then combine everything from the course into a small but real project — a grade book.
record and var
A record (since Java 16) is a short way to write a class that holds immutable data. When you write record Point(int x, int y), Java creates the private final fields, the constructor, the x() and y() methods, and also equals, hashCode and toString. You can add your own methods to a record too. var lets the compiler work out the type of a local variable for you.
public class Main {
public static void main(String[] args) {
Point a = new Point(3, 4);
Point b = new Point(3, 4);
System.out.println(a);
System.out.println(a.x() + a.y());
System.out.println(a.equals(b) + " " + (a == b));
System.out.println(a.distanceToOrigin());
var message = "Records save time";
var count = message.length();
System.out.println(message + " (" + count + " chars)");
}
}
record Point(int x, int y) {
double distanceToOrigin() {
return Math.sqrt(x * x + y * y);
}
}Point[x=3, y=4] 7 true false 5.0 Records save time (17 chars)
a.equals(b) gave true: records are compared by their contents. a == b is false, because these are two separate objects. Note that the getters have no get in their names: not getX(), just x().
Lambdas and method references
A lambda expression is a short function without a name: n -> n * n means “take n, return n * n”. Lambdas stand in for functional interfaces — interfaces with just one abstract method: Predicate (tests a condition), Function (transforms a value), Comparator (compares two objects). If a lambda only calls an existing method, you can write it even shorter as a method reference: System.out::println.
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
public class Main {
public static void main(String[] args) {
Predicate<Integer> isEven = n -> n % 2 == 0;
Function<Integer, Integer> square = n -> n * n;
System.out.println(isEven.test(4) + " " + square.apply(7));
List<String> names = new ArrayList<>(List.of("Murad", "Aysel", "Elvin", "Leyla"));
names.sort((a, b) -> a.compareTo(b));
names.removeIf(name -> name.startsWith("E"));
names.forEach(name -> System.out.print(name + " "));
System.out.println();
names.forEach(System.out::println);
}
}true 49 Aysel Leyla Murad Aysel Leyla Murad
Streams (the Stream API)
A stream processes the elements of a collection like a conveyor belt: filter keeps the ones that match a condition, map transforms each element, sorted orders them, and toList, count, sum or average collect the result. A stream does not change the source collection. The code says what to do rather than how, so it reads easily.
import java.util.List;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
List<Integer> scores = List.of(78, 95, 62, 88, 45, 91);
List<Integer> passed = scores.stream()
.filter(s -> s >= 60)
.sorted()
.toList();
System.out.println(passed);
int total = scores.stream().mapToInt(Integer::intValue).sum();
double avg = scores.stream().mapToInt(Integer::intValue).average().orElse(0);
System.out.println(total + " " + avg);
String initials = List.of("Baku", "Ganja", "Shaki").stream()
.map(c -> c.substring(0, 1))
.collect(Collectors.joining("-"));
System.out.println(initials);
long count = scores.stream().filter(s -> s > 80).count();
System.out.println(count);
}
}[62, 78, 88, 91, 95] 459 76.5 B-G-S 3
Project: a grade book
- 1Model the data
A student is a
recordwith a name and a list of scores. Its compact constructor rejects scores outside 0–100 with anIllegalArgumentException. - 2Add the calculations
average()finds the mean score with a stream and rounds it to one decimal place, andletter()gives a letter grade with a switch expression. - 3Write the grade book class
GradeBookkeeps the students in aprivateArrayList, prints a sorted report and calculates the class average. - 4Put it all together in main
Add the students, catch bad data with
try/catchand print the report.
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
public class Main {
public static void main(String[] args) {
GradeBook book = new GradeBook();
book.add(new Student("Aysel", List.of(92, 88, 95)));
book.add(new Student("Murad", List.of(85, 78, 84)));
book.add(new Student("Leyla", List.of(58, 49, 61)));
book.add(new Student("Nigar", List.of(70, 65, 72)));
try {
book.add(new Student("Elvin", List.of(90, 120)));
} catch (IllegalArgumentException e) {
System.out.println("Skipped: " + e.getMessage());
}
book.printReport();
System.out.println("Class average: " + book.classAverage());
}
}
record Student(String name, List<Integer> scores) {
Student {
for (int s : scores) {
if (s < 0 || s > 100) throw new IllegalArgumentException(name + " has score " + s);
}
}
double average() {
double avg = scores.stream().mapToInt(Integer::intValue).average().orElse(0);
return Math.round(avg * 10) / 10.0;
}
String letter() {
return switch ((int) average() / 10) {
case 10, 9 -> "A";
case 8 -> "B";
case 7 -> "C";
case 6 -> "D";
default -> "F";
};
}
}
class GradeBook {
private final List<Student> students = new ArrayList<>();
void add(Student s) { students.add(s); }
double classAverage() {
double avg = students.stream().mapToDouble(Student::average).average().orElse(0);
return Math.round(avg * 10) / 10.0;
}
void printReport() {
students.stream()
.sorted(Comparator.comparingDouble(Student::average).reversed())
.forEach(s -> System.out.println(s.name() + ": " + s.average() + " (" + s.letter() + ")"));
}
}Skipped: Elvin has score 120 Aysel: 91.7 (A) Murad: 82.3 (B) Nigar: 69.0 (D) Leyla: 56.0 (F) Class average: 74.8
| In the project | From which topic |
|---|---|
record Student, Student { ... } | records, constructors |
throw new IllegalArgumentException, try/catch | exceptions |
switch ((int) average() / 10) | switch expressions, type casting |
private final List<Student> | encapsulation, collections, generics |
stream(), Student::average, s -> ... | streams, method references, lambdas |
Student { ... } is the record's compact constructor: it lets you validate the data without repeating the parameters, and the fields are filled in automatically afterwards. Elvin's score of 120 was rejected and he did not get into the grade book, yet the program kept going — just as in the exceptions lesson.
Key points
- A
recordcreates an immutable data class in one line: fields, constructor, getters,equalsandtoStringcome for free. varlets the compiler infer a local variable's type, but the type is still fixed.- A lambda (
x -> ...) is a short implementation of a functional interface;Type::methodis a method reference. - A stream:
stream()→filter/map/sorted→toList,count,sum,average; the source collection is not changed. - A real project brings every part of the course together: classes, exceptions, collections and modern Java.
Check yourself
10 questions. Every correct answer earns XP.
record Point(int x, int y), how do you read the x coordinate?