I went through the Crafting Interpreters book with modern Java. I posted this on reddit, but I basically used records and sealed interfaces. With modern Java there's no need for visitor pattern.
private void execute(Stmt statement) {
switch (statement) {
case Stmt.Expression expression -> evaluate(expression.expr());
case Stmt.Block block -> executeBlock(block.statements(),
new Environment(environment));
...
public sealed interface Expr permits
Expr.Assign,
Expr.Binary,
Expr.Call,
Expr.Function,
.... more exprs here
Interesting. Almost all my actual experience writing Java is in Java 8, so I'm not super familiar with the modern stuff (other then a few things like Records). I'll have to take a look.
Most of the people I know who write Java would have an aneurysm at seeing a switch statement.
To be clear, that's a critique of the Java mindset, not your code. Lol