6 Compact Source Files and Instance main Methods
Compact 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.
For background information about compact source files and instance
main
methods, see JEP 512.
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.
main
methods enhance the
Java programming language's support for programming in the small as follows:
- Enable students and developers to create compact source files, which are source files with fields and methods that aren't enclosed in a class declaration. A compact source file implicitly declares a class whose members are the unenclosed fields and methods. See 8.1.8 Implicitly Declared Classes in the Java Language Specification and Creating Compact Source Files.
- Automatically import public top-level classes and interfaces of the packages exported by the java.base module. See Automatic Import of the java.base Module.
- Enhance the protocol by which Java programs are launched by allowing
instance
main
methods that are notstatic
, need not bepublic
, and need not have aString[]
parameter. See Running Compact Source Files with the java Launcher and Instance Main Methods.
In addition, the java.lang.IO class provides basic line-oriented I/O methods, which are simpler alternatives to the System.out.println method. See The java.lang.IO Class for Basic Console I/O.
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 mandatorypublic
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 likeHelloWorld
. - 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 thatmain
can call and use, the student must either declare them all asstatic
(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.
Compact 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
compact source files and instance main
methods are useful features when
writing simple Java programs that do not require the programming-in-the-large features
of the Java language. The introduction of programming-in-the-large constructs can be
postponed by instructors until they are needed.
Creating Compact 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 compact source file.
An implicitly declared class of a compact 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 compact source file.
- Must have a launchable main method, which can be an instance
main
method. See Running Compact 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 a compact 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 Compact 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.
HelloWorld
program
as a compact source
file:void main() {
System.out.println("Hello, World!");
}
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());
}
String greeting = "Hello, World!";
void main() {
System.out.println(greeting);
}
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 Compact 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.
The java.lang.IO Class for Basic Console I/O
The java.lang.IO class provides basic line-oriented I/O methods, which are simpler alternatives to the System.out.println method:
- 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.
Consequently, you can further simplify the HelloWorld
program as:
String greeting = "Hello, World!";
void main() {
IO.println(greeting);
}
Although the IO class is in the java.lang package, which means that it's implicitly imported
by every source file (and not just compact source files), the static
methods of the IO class aren't implicitly imported. To
invoke these methods, you must name the class, for example, IO.println("Hello,
world!")
, unless you explicitly import them.
Automatic Import of the java.base Module
Every compact 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 compact source file. See Module Import Declarations for more information.
The following example is a compact 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 Compact Source Files with the java Launcher and Instance Main Methods
You can run a single-file, source-code program, including one that's a
compact source file, with the java
command-line tool without first
explicitly compiling it.
For example, if HelloWorld.java
is a compact source
file, you can run it directly at the command line:
java HelloWorld.java
Alternatively, like any other Java source file, you can explicitly compile a
compact source file with the javac
command-line tool and then run the
compiled compact source file with the java
command-line tool:
javac HelloWorld.java
java HelloWorld
In both cases, the java
tool finds and invokes a
main
method as described by the following protocol:
-
If the compact source file contains an accessible
main
method with a singleString[]
parameter and avoid
result, then this method is chosen.Otherwise, if the compact source file contains an accessible
main
method with no parameters and avoid
result, then this method is chosen.Otherwise, the
java
tool reports an error and terminates. -
If the method chosen was a
static main
method, then it's invoked along with any arguments from the command line passed as elements of an array, as appropriate.Otherwise, an instance method was chosen. In this case, an instance of the compact source file's implicitly declared class must be created first. This class instance is the result of invoking the default constructor of the implicitly declared class. The
main
method is then invoked on the resulting instance with any arguments from the command line passed as elements of an array, as appropriate.
Any main
method that can be invoked under this protocol is
called a launchable main
method. For example, consider again the
compact source file HelloWorld.java
:
void main() {
IO.println("Hello, World!");
}
It has one launchable main
method, namely void main()
,
which is an instance main
method.
The following compact source file example contains an instance
main
method with a String[]
parameter:
void main(String[] args) {
IO.println("Hello, " + args[0]);
}
Growing a Program
By omitting the concepts and constructs it doesn't need, a
HelloWorld
program written as a compact source file is more focused on
what the program actually does. Even so, all of the members of the implicitly declared class
in a compact source file continue to be interpreted just as they are in an ordinary class.
Concepts and constructs can easily be added to a compact source file as
needed by the program. To evolve the implicitly declared class in a compact source file
into an ordinary class, all we need to do is wrap its declaration, excluding
import
statements, inside an explicit class
declaration.