Details
-
JEP
-
Status: Submitted
-
P3
-
Resolution: Unresolved
-
None
-
None
-
Ron Pressler
-
Feature
-
Open
-
SE
-
S
Description
Summary
Evolve the Java 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 Java, these first programs utilize streamlined declarations for single-class programs, and can be seamlessly expanded to incorporate more advanced features as needed. This is a preview language feature.
Goals
- Offer a smooth on-ramp to Java so that educators can introduce programming concepts in a gradual manner.
- Help students to write basic programs in a concise manner and grow the code gracefully as their skills grow.
- Reduce the ceremony of writing simple programs such as scripts and command-line utilities.
Non-Goals
- It is not a goal to introduce a separate "beginner's dialect" of Java or a new tool to run student programs. Indeed, that is an anti-goal to be avoided.
- Similarly, it is not a goal to introduce a separate beginners' toolchain; student programs should be compiled and run with all the same tools that compile and run any Java program.
Motivation
Java is a multi-paradigm language that excels at writing 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 to allow components to be cleanly composed while being developed and maintained independently. Those features expose well-defined interfaces for their interaction with other components and hide internal implementation details to allow 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. Java also offers many constructs useful for programming in the small – everything that is internal to a component. In recent years, Java has enhanced both its programming-in-the-large capabilities with modules as well as its programming-in-the-small capabilities with data-oriented programming.
But Java is also intended to be a first programming language. When programmers first start out they don't write large programs in a team but small programs alone. They have no need for encapsulation and namespaces, useful to separately evolve components written by different people. Moreover, when teaching programming, many instructors prefer starting with programming-in-the-small and first teach basic constructs such as variables, control flow, and subroutines. At that stage, there is no need for the larger class units or any other programming-in-the-large construct. While making the language more welcoming to newcomers is, in itself, in the best interest of Java veterans they, too, may find it pleasurable to write simple programs more concisely. Even expert programmers write small programs that don't require the programming-in-the-large scaffolding.
The classic “Hello World” program that is often used as the first program for Java learners, looks like this:
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
Most educators feel that there's too much clutter here – too much code, too many concepts, too many constructs – for what the program does. The clutter falls into two categories:
- In one category we have the class declaration and the
public
access modifier (Java requires the one on themain
method). Those are clearly programming-in-the-large constructs that are useful when encapsulating our code unit when we want to define an interface between it and other components. TheString[] args
parameter, which is clearly mysterious and unhelpful here, especially as it is ignored, could be considered among the general ceremony Java requires, but really it, too, exists to interface the program with an external component, in this case the operating system's shell. - In a second category we have
static
. It is part of Java's class-and-object model, and its presence here may make sense to the knowledgeable programmer. But for the Java novice,static
is not only mysterious but also harmful: To add more methods or fields thatmain
can call and use, the learner must either declare them all asstatic
spreading the incantation -- which is neither a common nor a good habit in "real" Java programs -- or learn how to instantiate an object and confront the difference between static and instance members.
The new programmer encounters these concepts at the worst time: before even learning about control flow and variables, and when they certainly cannot appreciate the utility that programming-in-the-large constructs have for keeping a large program well-organized. Java foists those concepts on the learner in an order that is not conducive to learning how to program. Educators often offer the admonition "Don’t worry about that, you’ll understand it later”, but this is unsatisfying to them and their students alike, and gives the students the impression that Java is complicated.
The motivation for this JEP is not merely to reduce ceremony but to help programmers that are new to Java or to programming in general learn Java in a manner that introduces concepts in the right order: advanced concepts intended for programming in the large can be postponed until they are actually beneficial and can be more easily grasped, after learning the more basic, programming in the small, concepts. This is done not by changing Java's internal structure -- code still lives inside methods that, in turn, live inside classes -- 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 learners 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 offered here are just a step in making Java easier to learn. They don't even address all the speed bumps in the Hello, World program: The beginner may still be puzzled by the mysterious System.out.println
, and still needs to import basic utility classes and methods for essential functionality even in first-week programs. Alleviating these pains may be the subject of a separate JEP.
Description
This is preview language feature, disabled by default
To try the the examples below in JDK 21 you must enable preview features as follows:
- Compile the program with
javac --release 21 --enable-preview Main.java
and run it withjava --enable-preview Main
; or, - When using the source code launcher, run the program with
java --source 21 --enable-preview Main.java
An Enhanced Launch Protocol
New programmers want to write and run a computer program, but the Java Language Specification focuses on defining the core Java unit of the class and the basic compilation unit -- 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 role of picking the class whose main
method is invoked, as well as assembling its dependencies in the form of a module path and/or a class path is assigned to the Java launcher, the JDK's java
executable. It operates by loading a specified class, initializing it, and invoking a particular main
method of that class.
String[]
parameter and public
modifier
We enhance the protocol so that if the main
method of the specified class has default (package) access it will also be invoked. Furthermore, if no method with a String[]
parameter exists and, instead, a main
method that takes no arguments is present, then it will be invoked. This allows postponing the introduction of access modifiers, as well as the processing of command-line arguments, until they are needed.
main
The aforementioned enhancement of the launch protocol still leaves us with the problem of a static main
, which further infects methods called from main
with "staticness", or requires object instantiation.
We, therefore, further enhance the launch protocol so that if no static main
method is present but the launched class has a non-private
zero-args constructor (i.e. of public
, protected
or package access), and a non-private
instance main
method, then the launcher will construct an instance of the class and invoke the main
method on that instance. As before, if no static main
method is present, the launcher will first look for an instance main method that has a String[]
parameter, and if one isn't present, it will invoke one that takes no arguments.
This would allow Hello, World to be written as follows, with no access and static
modifiers, and without the String[]
parameter:
class HelloWorld {
void main() {
System.out.println("Hello, World!");
}
}
main
method selection
When multiple main
methods are available, the launch protocol chooses among them according to a fixed priority, picking one with a higher priority over those with a lower priority. Selecting the one that would have been picked before the change first ensures that the extension of the launch protocol preserves the behavior of existing "launchable" classes.
The order by which the main
method is selected among available candidates is as follows:
public static void main(String[] args)
declared in the launched class or a superclass, this is the standard behavior before the changestatic void main(String[] args)
ofprotected
or package access declared in the launched classstatic void main()
of any non-private access (i.e.public
,protected
or package) declared in the launched classvoid main(String[] args)
of any non-private access declared in the launched class or inherited from a superclassvoid main()
of any non-private access declared in the launched class or inherited from a superclass
Additionally, we deprecate the existing behavior that a public static void main(String[] args)
is searched in a superclass if not found in the launched class by issuing a runtime warning when such a class is launched.
Anonymous Main Classes
Every Java class resides in a package and every package resides in a module -- they serve as namespacing or encapsulation constructs that the Java platform applies to all code. But Java allows smaller programs that don't need class namespaces to omit the package
statement, making the class an implicit member of the unnamed package (similarly for module encapsulation). Classes in the unnamed package cannot be explicitly referenced by classes in named packages.
Before classes serve their main purpose of templates for the construction of objects, they serve only as namespaces for methods and fields. But we should not require learners to confront the class concept 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 Java method resides in a class, we can stop requiring an explicit class declaration for code that doesn't need it just as we don't require an explicit package declaration for code that doesn't need it.
When the Java compiler encounters a source file with a method that isn't enclosed in a class declaration, it will implicitly consider such methods, as well as any unenclosed field and any class declared in the file, as members of an anonymous main class.
An anonymous main class is always a member of the unnamed package. It is also final
and cannot implement any interface nor subclass any class (other than Object
). Note that because an anonymous main class cannot be referenced by name, it cannot use method references to its static methods; this
can still be used, and so can method references to instance methods.
Because no class can refer to an anonymous class by name, instances of the anonymous main class cannot be directly constructed; they are useful only as standalone programs or as entry points to a program. Therefore, anonymous main classes must have a main
method that can be launched as described above.
Even though an anonymous main class must reside in an unnamed package and an unnamed package must reside in the unnamed module, note that while there can only be one unnamed package (barring multiple classloaders) and only one unnamed module, there can be multiple anonymous main classes in the unnamed module. Because every anonymous main class must contain a main
method and so represents a program, multiple such classes in the unnamed package correspond to multiple programs.
The members of an anonymous main class support the same modifiers as in ordinary named classes (such as static
or volatile
) and with the same defaults (e.g. default package
access and instance membership), and generally behave in the same way as members of an explicitly-declared class. An anonymous main class has the default no-args constructor, and can have no other. It may, however, have a static initializer as well as an instance initializer.
Hello, World can now be written as:
void main() {
System.out.println("Hello, World!");
}
As "top-level members" are interpreted as members of the anonymous class, 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 anonymous main class has an instance main
entry point (rather than a static main
) -- say, void main()
-- launching such a file is equivalent to the following (which employs the existing anonymous class expression):
new Object() {
// contents of the file, not including import statements
}.main();
A source file named HelloWorld.java
containing an anonymous main class could be launched with the source-code launcher, like so: java HelloWorld.java
. javac
will compile that file source file to the launchable classfile HelloWorld.class
(in this case javac chooses HelloWorld
for the class name as an implementation detail, but that name still cannot be directly referenced by Java source code).
A Hello, World program written in this manner is much more focused on what the program actually does, and doesn't carry concepts and constructs it doesn't need. Eliminating the main method altogether may seem like a 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 Alternatives). Dropping the void
would similarly create a distinct Java dialect.
Growing a program
Because all members are interpreted just as they are in ordinary classes, if the need arises to evolve an explicit class to an ordinary class, all we need to do wrap everything in the file (except import
statements) inside a class
declaration.
Alternatives
Rely on JShell to offer an "introductory Java": A JShell session is not a program but a sequence of code snippets. When we type declarations into
jshell
, they are implicitly viewed as static members of some unspecified class, with access level ignored completely, and statements execute in a context where all previous declarations are in scope. This is convenient for experimentation -- the primary goal of JShell -- but not good model for learning to write Java programs. Evolving a batch of working declarations in JShell into a real Java program would not be sufficiently straightforward, and would lead to a non-idiomatic style of code, because a direct transformation would have us redeclaring each method, class, and variable declaration asstatic
. 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: In Java, methods and fields are non-static by default, and interpreting "top-level" members in an anonymous main class as static would change the meaning of Java code units in such a class, introducing, in effect, a distinct Java dialect. It would also require adding explicit
static
modifiers when we evolve an anonymous main class to an ordinary class to preserve their meaning. 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 local: Assuming the existence of a feature that would allow declaring local methods nested in other methods, we could interpret the body of the program as the body of a
main
method, with variables interpreted as locals rather than fields, and methods as local methods rather than class members. This would allow us to eschew themain
method altogether, and write top-level statements. The problem with that is that in Java, 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 current design allows to separate between locals and fields in the same manner it is always done in Java, and the burden of requring amain
method is not onerous, even on new learners.Introduce package level methods and fields: A similar user experience to the one proposed could be achieved by adding package-level methods and fields declared in a file without an explicit class declaration in any package. However, such a feature would have a far wider impact on how Java code is written in general, and we are not prepared to consider that impact at this time.