Java records and the single class per file limit

2.4k views Asked by At

I'm trying out the new records feature with java 14, currently in preview.
I know that in Java one has to have exactly one public class per file but with the new record syntax being so nice and short it just seem wasteful to have a bunch of files each with a single line of code in them.

In particular I wanted to try to model a simple AST like this, and I think that having everything together in one file really improves readability and comprehension.

package com.company;

public interface Expression {
}

public record IntExp(int value) implements Expression {
}

public record AddExp(Expression left, Expression right) implements Expression {
}

public record SubtractExp(Expression left, Expression right) implements Expression {
}

// Etc..

But unfortunately this will not compile.
So my question is:

Is there any way around this limitation or some way to keep code like this all in one place?

2

There are 2 answers

6
fps On BEST ANSWER

Inner classes, along with sealed types seem to be what you're after. Java 15 has introduced sealed types as a preview feature.

You've stated that you wanted to model an AST in one single file. An AST is exhaustive. And this exhaustiveness can be modeled with inner classes and sealed types.

In your particular example, if you define the super type along with all its permitted subtypes in the same compilation unit (with the subtypes being inner classes), the compiler will automatically infer the permits for you:

public sealed interface Expression {

    record IntExp(int value) implements Expression { }

    record AddExp(Expression left, Expression right) implements Expression { }

    record SubtractExp(Expression left, Expression right) implements Expression { }
}

Here, the compiler will automatically infer IntExp, AddExp and SubtractExp as permits and won't allow any other types (declared in any other compilation unit) to neither extend nor implement the Expression interface.

1
stridecolossus On

One obvious approach is to move the 'pre-defined' record classes into the interface:

package com.company;

public interface Expression {
    record IntExp(int value) implements Expression {
    }

    record AddExp(Expression left, Expression right) implements Expression {
    }

    record SubtractExp(Expression left, Expression right) implements Expression {
    }
}