Skip to content
Educora
Advanced22 min12 / 18

Modern Java and a project: a grade book

Meet records, var, lambdas and streams, then combine everything from the course to build a student grade book.

Check yourself
In this lesson you will learn
  • Write a simple data class as a record and use var sensibly
  • 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.

Java
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);
    }
}
Expected output
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.

Java
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);
    }
}
Expected output
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.

Java
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);
    }
}
Expected output
[62, 78, 88, 91, 95]
459 76.5
B-G-S
3

Project: a grade book

  1. 1
    Model the data

    A student is a record with a name and a list of scores. Its compact constructor rejects scores outside 0–100 with an IllegalArgumentException.

  2. 2
    Add the calculations

    average() finds the mean score with a stream and rounds it to one decimal place, and letter() gives a letter grade with a switch expression.

  3. 3
    Write the grade book class

    GradeBook keeps the students in a private ArrayList, prints a sorted report and calculates the class average.

  4. 4
    Put it all together in main

    Add the students, catch bad data with try/catch and print the report.

Java
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() + ")"));
    }
}
Expected output
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
The whole project in one file — Main.java
In the projectFrom which topic
record Student, Student { ... }records, constructors
throw new IllegalArgumentException, try/catchexceptions
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 record creates an immutable data class in one line: fields, constructor, getters, equals and toString come for free.
  • var lets 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::method is 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.

1 / 10
For record Point(int x, int y), how do you read the x coordinate?