JEP 463: Implicitly Declared Classes and Instance Main Methods (Second Preview)

AuthorRon Pressler & Jim Laskey
OwnerJim Laskey
TypeFeature
ScopeSE
StatusClosed / Delivered
Release22
Componentspecification / language
Discussionamber dash dev at openjdk dot org
EffortS
DurationS
Relates toJEP 445: Unnamed Classes and Instance Main Methods (Preview)
Reviewed byGavin Bierman
Endorsed byBrian Goetz
Created2023/08/30 18:07
Updated2024/01/30 14:40
Issue8315398

Summary

Evolve the Java programming language so that students can write their first programs without needing to understand language features designed for large programs. Far from using a separate dialect of the language, students can write streamlined declarations for single-class programs and then seamlessly expand their programs to use more advanced features as their skills grow. This is a preview language feature.

History

JEP 445 proposed Unnamed Classes and Instance main Methods, which previewed in JDK 21. Feedback has suggested that the feature should preview for a second time in JDK 22 with the following significant changes and, therefore, a revised title.

Goals

Motivation

The Java programming language excels for large, complex applications developed and maintained over many years by large teams. It has rich features for data hiding, reuse, access control, namespace management, and modularity which allow components to be cleanly composed while being developed and maintained independently. With these features, components can expose well-defined interfaces for their interaction with other components while hiding internal implementation details so as to permit the independent evolution of each. Indeed, the object-oriented paradigm itself is designed for plugging together pieces that interact through well-defined protocols and abstract away implementation details. This composition of large components is called programming in the large. The language also offers many constructs useful for programming in the small — everything that is internal to a component. In recent years, we have enhanced both its programming-in-the-large capabilities with modules and its programming-in-the-small capabilities with data-oriented programming.

The Java programming language is also, however, intended to be a first language. When programmers first start out they do not write large programs, in a team — they write small programs, alone. They have no need for encapsulation and namespaces, useful to separately evolve components written by different people. When teaching programming, instructors start with the basic programming-in-the-small concepts of variables, control flow, and subroutines. At that stage there is no need for the programming-in-the-large concepts of classes, packages, and modules. Making the language more welcoming to newcomers is in the interest of Java veterans but they, too, may find it pleasurable to write simple programs more concisely, without any programming-in-the-large scaffolding.

Consider the classic Hello, World! program that is often used as the first program for Java students:

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}

There is too much clutter here — too much code, too many concepts, too many constructs — for what the program does.

The new programmer encounters these concepts at the worst possible time, before they learn about variables and control flow, and when they cannot appreciate the utility of programming-in-the-large constructs for keeping a large program well organized. Instructors often offer the admonition, "don't worry about that, you'll understand it later." This is unsatisfying to them and their students alike, and leaves students with the enduring impression that the language is complicated.

The motivation for this JEP is not merely to reduce ceremony. We aim to help programmers that are new to the Java language, or to programming in general, learn the language in a manner that introduces concepts in the right order: Start with the fundamental programming-in-the-small concepts, then proceed to advanced programming-in-the-large concepts when they are actually beneficial and can be more easily grasped.

We propose to do this not by changing the structure of the Java language — code is still enclosed in methods, which are enclosed in classes, which are enclosed in packages, which are enclosed in modules — but by hiding these details until they are useful in larger programs. We offer an on-ramp, a gradual incline that gracefully merges onto the highway. When students move on to larger programs they need not discard what they learned in the early stages, but rather they see how it all fits within the larger picture.

The changes we offer here are just one step in making the Java language easier to learn. They do not even address all the speed bumps in the Hello, World! program: The beginner may still be puzzled by the mysterious System.out.println incantation, and still needs to import basic utility classes for essential functionality even in first-week programs. We may address these pains in a future JEP.

Description

First, we enhance the protocol by which Java programs are launched to allow instance main methods. Such methods are not static, need not be public, and need not have a String[] parameter. Then we can simplify the Hello, World! program to:

class HelloWorld {
    void main() {
        System.out.println("Hello, World!");
    }
}

Second, we allow a compilation unit, i.e., a source file, to implicitly declare a class:

void main() {
    System.out.println("Hello, World!");
}

This is preview language feature, disabled by default

To try the examples below in JDK 22 you must enable preview features as follows:

The launch protocol

New programmers just want to write and run a computer program. The Java Language Specification, however, focuses on defining the core Java unit of the class and the basic compilation unit, namely a source file comprised of a package declaration, followed by some import declarations, followed by one or more class declarations. All it has to say about a Java program is this:

The Java Virtual Machine starts execution by invoking the method main of some specified class or interface, passing it a single argument which is an array of strings.

The JLS further says:

The manner in which the initial class or interface is specified to the Java Virtual Machine is beyond the scope of this specification, but it is typical, in host environments that use command lines, for the fully qualified name of the class or interface to be specified as a command line argument and for following command line arguments to be used as strings to be provided as the argument to the method main.

The actions of choosing the class containing the main method, assembling its dependencies in the form of a module path or a class path (or both), loading the class, initializing it, and invoking the main method with its arguments constitute the launch protocol. In the JDK it is implemented by the launcher, i.e., the java executable.

A flexible launch protocol

We enhance the launch protocol to offer more flexibility in the declaration of a program's entry point and, in particular, to allow instance main methods, as follows:

These changes allow us to write Hello, World! with no access modifiers, no static modifiers, and no String[] parameter, so the introduction of these constructs can be postponed until they are needed:

class HelloWorld {
    void main() {
        System.out.println("Hello, World!");
    }
}

Implicitly declared classes

In the Java language, every class resides in a package and every package resides in a module. These namespacing and encapsulation constructs apply to all code, but small programs that do not need them can omit them. A program that does not need class namespaces can omit the package statement, making its classes implicit members of the unnamed package; classes in the unnamed package cannot be referenced explicitly by classes in named packages. A program that does not need to encapsulate its packages can omit the module declaration, making its packages implicit members of the unnamed module; packages in the unnamed module cannot be referenced explicitly by packages in named modules.

Before classes serve their main purpose as templates for the construction of objects, they serve only as namespaces for methods and fields. We should not require students to confront the concept of classes before they are comfortable with the more basic building blocks of variables, control flow, and subroutines, before they embark on learning object orientation, and when they are still writing simple, single-file programs. Even though every method resides in a class, we can stop requiring explicit class declarations for code that does not need it — just as we do not require explicit package or module declarations for code that does not need them.

Henceforth, if the Java compiler encounters a source file with a method that is not enclosed in a class declaration then it will consider that method, any other such methods, and any unenclosed fields and any classes in the file to form the body of an implicitly declared top-level class.

Such an implicitly declared class (or implicit class for short) is always a member of the unnamed package. It is also final and does not implement any interface nor extend any class other than Object. An implicit class cannot be referenced by name, so there can be no method references to its static methods; the this keyword can still be used, however, and so can method references to instance methods.

The code of an implicit class cannot refer to the implicit class by name, so instances of an implicit class cannot be constructed directly. Such a class is useful only as a standalone program or as an entry point to a program. Therefore, an implicit class must have a main method that can be launched as described above. This requirement is enforced by the Java compiler.

An implicit class resides in the unnamed package, and the unnamed package resides in the unnamed module. While there can be only one unnamed package (barring multiple class loaders) and only one unnamed module, there can be multiple implicit classes in the unnamed module. Every implicit class contains a main method and so represents a program, thus multiple such classes in the unnamed package represent multiple programs.

An implicit class is almost exactly like an explicitly declared class. Its members can have the same modifiers (e.g., private and static) and the modifiers have the same defaults (e.g., package access and instance membership). One key difference is that while an implicit class has a default zero-parameter constructor, it can have no other constructor.

With these changes we can now write Hello, World! as:

void main() {
    System.out.println("Hello, World!");
}

Top-level members are interpreted as members of the implicit class, so we can also write the program as:

String greeting() { return "Hello, World!"; }

void main() {
    System.out.println(greeting());
}

or, using a field, as:

String greeting = "Hello, World!";

void main() {
    System.out.println(greeting);
}

If an implicit class has an instance main method rather than a static main method then launching it is equivalent to the following, which employs the existing anonymous class declaration construct:

new Object() {
    // the implicit class's body
}.main();

A source file named HelloWorld.java containing an implicit class can be launched with the source-code launcher, like so:

$ java HelloWorld.java

The Java compiler will compile that file to the launchable class file HelloWorld.class. In this case the compiler chooses HelloWorld for the class name as an implementation detail, but that name still cannot be used directly in Java source code.

The javadoc tool cannot generate API documentation for an implicit class, as implicit classes do not define any API accessible from other classes, but the fields and methods of an implicit class can generate API documentation.

Growing a program

A Hello, World! program written as an implicit class is much more focused on what the program actually does, omitting concepts and constructs it does not need. Even so, all members are interpreted just as they are in an ordinary class. To evolve an implicit class into an ordinary class, all we need to do is wrap its declaration, excluding import statements, inside an explicit class declaration.

Eliminating the main method altogether may seem like the natural next step, but it would work against the goal of gracefully evolving a first Java program to a larger one and would impose some non-obvious restrictions (see below). Dropping the void modifier would similarly create a distinct Java dialect.

Alternatives