6 Simple Source Files and Instance main Methods

Simple source files and instance main methods enable students to write their first programs without needing to understand the full set of language features designed for large programs.

Note:

This is a preview feature. A preview feature is a feature whose design, specification, and implementation are complete, but is not permanent. A preview feature may exist in a different form or not at all in future Java SE releases. To compile and run code that contains preview features, you must specify additional command-line options. See Preview Language and VM Features.

For background information about simple source files and instance main methods, see JEP 495.

The Java programming language excels in developing 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. The composition of large components is called programming-in-the-large.

However, the Java programming language is also intended to be a first language and offers many constructs that are useful for programming-in-the-small (everything that is internal to a component). When programmers first start out, they do not write large programs in a team — they write small programs by themselves. At this stage, there is no need for the programming-in-the-large concepts of classes, packages, and modules.

When teaching programming, instructors start with the basic programming-in-the-small concepts of variables, control flow, and subroutines. There is no need for the programming-in-the-large concepts of classes, packages, and modules. Students who are learning to program have no need for encapsulation and namespaces which are useful later to separately evolve components written by different people.

Simple source files and instance main methods enhance the Java programming language's support for programming in the small as follows:
Consider the classic HelloWorld 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!");
  }
} 

In this first program:

  • 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 rather 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 Java's class-and-object model. For the novice, static is not just mysterious but also harmful. To add more methods or fields that main can call and use, the student must either declare them all as static (propagating an idiom which is neither a common nor a good habit) or else confront the difference between static and instance members and learn how to instantiate an object.
The new programmer encounters these programming-in-the-large constructs before they learn about variables and control flow and before they can appreciate the utility of programming-in-the-large constructs for keeping a large program well organized. Educators often offer the admonition, "Don’t worry about that. You’ll understand it later." This is unsatisfying to them and to their students. It leaves students with the enduring impression that the Java language is overly complicated.

Simple source files and instance main methods reduce the complexity of writing simple programs such as HelloWorld by enabling programmers to write programs without using access modifiers, static modifiers, or the String[] parameter. Far from being a separate dialect, students can now use the Java language to write streamlined declarations for single-class programs and then later seamlessly expand their beginning programs to include more advanced features as their skills grow. Java veterans might also find that simple source files and instance main methods are useful features when writing simple Java programs that do not require the programming-in-the-large scaffolding of the Java language. The introduction of programming-in-the-large constructs can be postponed by instructors until they are needed.

Creating Simple Source Files

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. However, small programs that don't need them can omit them.

A program that doesn't 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 doesn't 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 as namespaces for methods and fields. We should not require students to confront the concept of classes:

  • Before they are comfortable with the 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 doesn't need it — just as we don't require explicit package or module declarations for code that don't need them.

When the Java compiler encounters a source file containing a method not enclosed in a class declaration, it considers that method, any similar methods, and any unenclosed fields and classes in the file to form the body of an implicitly declared top-level class. A source file containing an implicitly declared class is called a simple source file.

An implicitly declared class of a simple source file:

  • Is a final top level class in the unnamed package.
  • Extends java.lang.Object and does not implement any interfaces.
  • Has a default constructor with no parameters, and no other constructors.
  • Has, as its members, the fields and methods in the simple source file.
  • Must have a launchable main method, which can be an instance main method. See Running Simple Source Files with the java Launcher and Instance Main Methods.

An implicitly declared class can't be referenced by name, so there can be no method references to its static methods. However, the this keyword can still be used, as well as method references to instance methods.

The code of an simple source file can't refer to its implicitly declared class by name, so instances of an implicitly declared class can't be constructed directly. Such a class is useful only as a standalone program or as an entry point to a program. Therefore, an implicitly declared class must have a main method that can be launched as described in Running Simple Source Files with the java Launcher and Instance Main Methods. This requirement is enforced by the Java compiler.

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

An implicitly declared class is similar to an explicitly declared class. Its members can have the same modifiers (such as private and static) and the modifiers have the same defaults (such as package access and instance membership). One key difference is that while an implicitly declared class has a default zero-parameter constructor, it can have no other constructor.

With these changes, we can now write the HelloWorld program as a simple source file:
void main() {
    System.out.println("Hello, World!");
}
It has one launchable main method, namely void main(), which is an instance main method. Because top-level members are interpreted as members of the implicitly declared class, we can also write the program as:
String greeting() { return "Hello, World!"; }

void main() {
    System.out.println(greeting());
}
Or, by using a field, we can write the program as:
String greeting = "Hello, World!";

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

You can launch a simple source file named HelloWorld.java with the java command-line tool as follows:
java HelloWorld.java

The Java compiler compiles that file to the launchable class file HelloWorld.class. In this case, the compiler chooses HelloWorld for the class name as an implementation detail. However, that name still can't be used directly in Java source code. See Running Simple Source Files with the java Launcher and Instance Main Methods.

At this time, the javadoc tool can't generate API documentation for an implicitly declared class because implicitly declared classes don't define an API that is accessible from other classes. However, fields and methods of an implicitly declared class can generate API documentation.

Automatic Import of the Static Methods of java.io.IO and the Module java.base

Every simple source file automatically imports the static methods of the class java.io.IO:

  • println(Object): Writes a string representation of the specified object to the system console, and then flushes that console.
  • print(Object): Writes a string representation of the specified object to the system console, terminates the line, and then flushes that console.
  • readln(String): Writes a prompt as if calling print, and then reads a single line of text from the system console.

It is as if the static-import-on-demand declaration import java.io.IO.* appears at the beginning of every simple source file.

Consequently, you can further simplify the HelloWorld program as:

String greeting = "Hello, World!";

void main() {
    println(greeting);
}

In addition, every simple source file imports, on demand, all public top-level classes and interfaces in all packages exported by the java.base module. It is as if the module import declaration import module java.base appears at the beginning of every simple source file. See Module Import Declarations for more information.

The following example is a simple source file that requires no import declarations for Map, Stream, Collectors, or Function as they are contained in packages exported by the java.base module.

void main() {
    String[] fruits = new String[] { "apple", "berry", "citrus" };
    Map<String, String> m = Stream
        .of(fruits)
        .collect(Collectors.toMap(
            s -> s.toUpperCase().substring(0,1),
                Function.identity()));
    m.forEach((k, v) -> println(k + " " + v));
}

Running Simple Source Files with the java Launcher and Instance Main Methods

You can run a program supplied as a single file of Java source code, which includes simple source files, with the java launcher.

For example, if HelloWorld.java is a simple source file, you can run it with the following command:

java HelloWorld.java

The java launcher compiles the simple source file in memory, then finds and invokes a main method as described in the following protocol:

  • If the simple source file contains a main method with a String[] parameter, then java launcher chooses that method.

    Otherwise, if the simple source file contains a main method with no parameters, then the java launcher chooses that method.

    Otherwise, the java launcher reports an error and terminates.

  • If the java launcher chose a static main method, it invokes it.

    Otherwise, the java launcher chose an instance main method. The java launcher invokes the default constructor of the simple source file's implicitly declared class. It then invokes the main method of the resulting object.

Any main method that can be invoked under this protocol is called a launchable main method. For example, consider again the simple source file HelloWorld.java:

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

It has one launchable main method, namely void main(), which is an instance main method.

The following simple source file example contains an instance main method with a String[] parameter:

void main(String[] args) {
    System.out.println("Hello, " + args[0]);
}

Growing a Program

By omitting the concepts and constructs it doesn't need, a HelloWorld program written as a simple source file is more focused on what the program actually does. Even so, all of the members of the implicitly declared class in a simple source file continue to be interpreted just as they are in an ordinary class.

Concepts and constructs can easily be added to a simple source file as needed by the program. To evolve the implicitly declared class in a simple source file into an ordinary class, all we need to do is wrap its declaration, excluding import statements, inside an explicit class declaration.