Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8323335

Implicitly Declared Classes and Instance Main Methods (Third Preview)

    XMLWordPrintable

Details

    • JEP
    • Resolution: Unresolved
    • P3
    • None
    • specification
    • None
    • Ron Pressler, Jim Laskey & Gavin Bierman
    • Feature
    • Open
    • SE
    • amber dash dev at openjdk dot org
    • S
    • S

    Description

      Summary

      Evolve the Java programming language so that simple, small programs can be written without using language features designed for large programs. Without using a separate dialect of the language, developers can write streamlined declarations for single-class programs and then seamlessly grow their small programs to use more advanced features as needed. This will also allow for a smoother introduction to Java programming for beginners so that instructors can introduce concepts in a gradual manner. This is a

      preview language<br /> feature

      .

      History

      Implicitly declared classes and instance main methods were first proposed as a preview feature by JEP 445, delivered in JDK 21 (with a different title). Following feedback, the feature was previewed again by JEP 463, delivered in JDK 22.

      We here propose to preview the feature for a third time with two changes:

      1. Within every simple compilation unit the three static methods declared in the new class Basic that is nested in the class java.io.Console, can be used using a simple name, as if they have been imported, to enable the compact expression of simple textual I/O with the console.

      2. Within every simple compilation unit the public top level classes and interfaces from all the packages exported by the module java.base can be used using a simple name, as if they have all been imported on demand.

      Goals

      • Help developers to write simple, small programs in a concise manner and grow their code gracefully as needed.

      • Reduce the ceremony of writing simple programs such as scripts and command-line utilities.

      • Offer a smooth on-ramp to Java programming for beginners so that instructors can introduce concepts in a gradual manner.

      • Do not introduce a separate 'small program' dialect of the Java language.

      • Do not introduce a separate 'small program' toolchain; small programs should be compiled and run with the same tools that compile and run large Java programs.

      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<br /> programming

      .

      But often developers need to write, by themselves, small programs and not large ones in a team. They have no need for encapsulation and namespaces, useful to separately evolve components written by different people. There is often even no need for other programming-in-the-large concepts such as classes, packages, and modules; all that is needed is the basic programming-in-the-small concepts of variables, control flow, and subroutines.

      The same situation is true of beginner Java programmers. They are writing simple, small programs alone without any need of the powerful programming-in-the-large tools that Java offers. Consider the classic

      [Hello,
      World!
      |https://en.wikipedia.org/wiki/%22Hello,_World!%22_program]

      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 class declaration and the mandatory public access modifier are programming-in-the-large constructs. They are useful when encapsulating a code unit with a well-defined interface to external components, but pointless in this little example.

      • The String[] args parameter also exists to interface the code with an external component, in this case the operating system's shell. It is mysterious and unhelpful here, especially since it is not used in simple programs like HelloWorld.

      • The static modifier is part of the language's class-and-object model. For the novice, static is not just mysterious but harmful: To add more methods or fields that main can call and use the student must either declare them all as static — thereby propagating an idiom which is neither common nor a good habit — or else confront the difference between static and instance members and learn how to instantiate an object.

      • The beginner may further be puzzled by the mysterious System.out.println incantation and what it might mean, rather than being able to use a simple function call. Even in first-week programs, the beginner may be forced to learn how to import basic utility classes for essential functionality, and wonder why they could not automatically be provided.

      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 all Java developers who need to write a small program. One should start with the fundamental programming-in-the-small tools, and then proceed to gradually apply more advanced programming-in-the-large concepts if and when they are needed and actually beneficial. This applies equally to systems developers wanting to write a command-line utility in Java, to veteran Java developers prototyping a core algorithm that may eventually be needed in the heart of an enterprise-scale software system, and to beginner Java programmers wishing to learn about, for example, processing an array using a for loop.

      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. Developers can experiment, or learn, with a small program, before needing to fit it into a larger codebase.

      The changes we offer here are just first steps in making the Java language easier for programming-in-the-small. We may address further pains in future JEPs.

      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<br /> unit

      , i.e., a source file, to implicitly declare a class:

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

      Third, we automatically declare and import useful APIs for input and output, avoiding the need for System.out.println magic:

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

      This is a preview language feature, disabled by default

      To try the examples below in JDK 23 you must enable preview features:

      The launch protocol

      Developers often just want to write and run a computer program. The

      <em>Java Language<br /> Specification</em>

      (JLS), however, sidesteps the idea of a "program" and focuses on a "compilation unit": a source file with a package declaration, import declarations, and class declarations. All the JLS 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:

      • Allow the main method of a launched class to have public, protected, or default (i.e., package) access.

      • If the launched class contains a main method with a String[] parameter then choose that method.

      • Otherwise, if the class contains a main method with no parameters then choose that method.

      • In either case, if the chosen method is static then simply invoke it.

      • Otherwise, the chosen method is an instance method and the launched class must have a zero-parameter, non-private constructor (i.e., of public, protected, or package access). Invoke that constructor and then invoke the main method of the resulting object. If there is no such constructor then report an error and terminate.

      • If there is no suitable main method then report an error and terminate.

      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.

      In simple, small programs, often classes do not serve their main purpose as templates for the construction of objects, but merely as namespaces for methods and fields. 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 (along with other unenclosed methods, fields and classes in the file) to form the body of an implicitly declared class.

      An implicitly declared class (or implicit class for short) is top level and final. It neither implements any interfaces nor extends 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.

      Interacting with the console

      Almost every small program needs to interact with the console. Writing to the console ought to be a simple method invocation, but actually requires using the qualified name System.out.println, which is a pain for experienced developers, but is deeply mysterious to the beginner Java programmer (What is System? What is out?)

      Even worse is reading from the console which, again, feels like it ought to be a simple method invocation. Consider, for example, trying to read a line of input as a String from the console. As writing to the console involves System.out, it seems reasonable to take as a starting point, System.in. But using this to read a line from the console as a String ends up as the following code:

      try {
          BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
          String line = reader.readLine();
          ...
      } catch (IOException ioe) {
          ...
      }

      Again, what felt like a simple operation, has ended up with quite a bit of code for the experienced developer, and for the beginner Java programmer contains even more mysterious concepts (What is InputStreamReader? Why BufferedReader? Why try-catch? What's an IOException?) There are alternative approaches, but none are significantly better, especially for the beginner.

      To simplify writing simple interactive small programs, we propose that the following three methods are available for use in the body of an implicit class:

      public static void println(Object obj) ...
      public static void print(Object obj) ...
      public static String input(String prompt) ...

      The beginner programmer can now write Hello, World! as:

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

      The simplest of interactive programs can be written as follows:

      void main() {
          String name = input("Please enter your name: ");
          print("Pleased to meet you, ");
          println(name);
      }

      This will be achieved by adding a new class called Basic, nested in the java.io.Console class, with these three static methods for textual I/O with the console. Every implicitly declared class can be thought to automatically import these static methods, as if the declaration import static java.io.Console.Basic.*; appears at the start of every source file containing an implicit class.

      This new class java.io.Console.Basic is a preview API for JDK 23.

      Automatic import of the java.base module

      But there are a host of other useful classes declared in the Java API that are very useful when developing a small program. Of course, they can be explicitly imported at the start of the source file, e.g.

      import java.util.List;
      
      void main() {
          var authors = List.of("James", "Bill", "Guy", "Alex", "Dan", "Gavin");
          for (var name : authors) {
              println(name + ": " + name.length());
          }
      }

      But these imports can often stack up. Again, for beginner Java programmers they are a little mysterious, especially as they require an understanding of the package hierarchy of the Java API.

      To help the developer, all top level classes and interfaces from every package exported by the module java.base can be used directly, as if they have all been imported automatically. This means that the top level classes and interfaces from all 54 packages exported by module java.base will be implicitly imported on demand, including those from commonly used packages such as java.io, java.math, and java.util.

      For example, in the previous small program the import java.util.List; declaration can be removed, as it will be automatically imported.

      JEP 476 proposes adding a new module import declaration, import module M; that imports on demand all the public top level classes and interfaces of every exported package in module M. Thus every implicitly declared class can be thought to implicitly import the module java.base, as if the declaration import module java.base; appears at the start of every source file containing an implicit class.

      Growing a program

      A small 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 any import declarations, inside an explicit class declaration, and add the automatic imports. For example, this implicit class:

      void main() {
          var authors = List.of("James", "Bill", "Guy", "Alex", "Dan", "Gavin");
          for (var name : authors) {
              println(name + ": " + name.length());
          }
      }

      can be grown into an ordinary top level class:

      import static java.io.Console.Basic.*;
      
      import java.util.List;
      // Or alternatively:
      // import module java.base;
      
      class MyProgram {
          void main() {
              var authors = List.of("James", "Bill", "Guy", "Alex", "Dan", "Gavin");
              for (var name : authors) {
                  println(name + ": " + name.length());
              }
          }
      }

      What is important to note here is that the main method is unchanged; it is very simple to start the process of turning a small program into one that can serve as a component in a larger program.

      Alternatives

      • _Use blank" rel="nofollow noopener" data-shared-secret="1713988967030-0.3489360135613163">JShell for introductory programming — A JShell session is not a program but a sequence of code snippets. Declarations typed into jshell are implicitly viewed as static members of some unspecified class, with some unspecified access level, and statements execute in a context in which all previous declarations are in scope.

        This is convenient for experimentation — which is the primary use case for JShell — but not a good model for learning to write Java programs. Evolving a batch of working declarations in JShell into a real Java program leads to a non-idiomatic style of code because it declares each method, class, and variable as static. JShell is a great tool for exploration and debugging, but it is not the on-ramp programming model we are looking for.

      • Interpret code units as static members — Methods and fields are non-static by default. Interpreting top-level members in an implicit class as static would change the meaning of the code units in such a class — introducing, in effect, a distinct Java dialect. To preserve the meaning of such members when we evolve an implicit class into an ordinary class we would have to add explicit static modifiers. This is not what we want as we scale up from a handful of methods to a simple class. We want to start using classes as classes, not as containers of static members.

      • Interpret code units as locals — We can already declare local variables within methods. Assume that we could also declare local methods, i.e., methods within other methods. Then we could interpret the body of a simple program as the body of a main method, with variables interpreted as local variables rather than fields, and methods interpreted as local methods rather than class members. This would allow us to eschew the main method altogether and write top-level statements.

        The problem with this approach is that, in the Java language, locals behave differently from fields, and in a more restricted way to boot: Locals can only be accessed from inside lambda bodies or inner classes when they are effectively final. The proposed design allows us to separate locals and fields in the same manner as they have always been. The burden of writing a main method is not onerous, even for new students.

      • Introduce package-level methods and fields — A user experience similar to that shown above could be achieved by allowing package-level methods and fields to be declared in a file without an explicit package or class declaration. However, such a feature would have a far wider impact on how Java code is written in general.

      • Different automatic imports — Rather than implicit classes importing all 54 packages in the module java.base, we could perhaps instead import some subset of packages. Every reader will have suggestions for which packages to auto-import : java.io and java.util would be near-universal suggestions; java.util.stream and java.util.function would be common; and java.math, java.net, and java.time would each have supporters. In the JShell tool, it was possible to find 10 java.* packages which are broadly useful when experimenting with one-off Java code, but it is difficult to see which subset of java.* packages deserves to be permanently automatically imported into every Java program. In addition, the list would change as the Java Platform evolves (e.g., Java 8 introduced the useful java.util.stream and java.util.function packages), so developers would become reliant on an IDE to remind them of the automatic imports in effect -- an undesirable outcome. Importing all the packages exported by the module java.base is a consistent and reasonable choice for implicit classes.

      Attachments

        Issue Links

          Activity

            People

              gbierman Gavin Bierman
              jlaskey Jim Laskey
              Gavin Bierman Gavin Bierman
              Brian Goetz
              Votes:
              0 Vote for this issue
              Watchers:
              7 Start watching this issue

              Dates

                Created:
                Updated: