6

JEP draft: Implicit Classes and Enhanced Main Methods (Preview)

 1 year ago
source link: https://openjdk.org/jeps/8302326
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
OwnerRon Pressler
TypeFeature
ScopeSE
StatusDraft
Componentspecification / language
EffortS
Reviewed byBrian Goetz
Created2023/02/13 13:58
Updated2023/02/18 13:25
Issue8302326

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 the main 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. The String[] 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 that main can call and use, the learner must either declare them all as static 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 with java --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 javaexecutable. It operates by loading a specified class, initializing it, and invoking a particular main method of that class.

Omitting the 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.

Instance 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!");
    }
}

mainmethod 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:

  1. public static void main(String[] args) declared in the launched class or a superclass, this is the standard behavior before the change

  2. static void main(String[] args) of protected or package access declared in the launched class

  3. static void main() of any non-private access (i.e. public, protected or package) declared in the launched class

  4. void main(String[] args) of any non-private access declared in the launched class or inherited from a superclass

  5. void 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.

Implicit 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 unnamed implicit class.

An implicit class has no name (more precisely, an internal, implementation-specific name will be chosen for it), and 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 implicit 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 implicit class by name, instances of the implicit class cannot be directly constructed. Implicit classes are useful only as standalone programs or as entry points to a program. Therefore, implicit classes must have a main method that can be launched as described above.

Even though an implicit 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 implicit classes in the unnamed module. Because every implicit class must contain a main method and so represents a program, multiple implicit classes in the unnamed package correspond to multiple programs.

The members of an implicit 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 implicit 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 implicit 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 implicit class has an instance main entry point (rather than a static main) -- say, void main() -- launching such a file is equivalent to:

new Object() {
    // contents of the file, not including import statements
}.main();

A source file named HelloWorld.java containing an implicit 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 importstatements) 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 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: In Java, methods and fields are non-static by default, and interpreting "top-level" members in an implicit 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 implicit 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 the main 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 a main 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.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK