Two Key JDK 10 Features: Supplement To Java: A Beginner's Guide, Seventh Edition
Two Key JDK 10 Features: Supplement To Java: A Beginner's Guide, Seventh Edition
Two Key
JDK 10 Features
T his supplement to Java: A Beginner’s Guide, Seventh Edition discusses two key features
added by JDK 10. It is provided because a significant change has occurred to the way Java
releases are scheduled. In the past, major Java releases were typically separated by two or more
years. However, subsequent to the release of Java SE 9 (JDK 9), the time between major Java
releases has been decreased. Beginning with Java SE 10 (JDK 10), a major release will occur
on a strict, time-based schedule, with the time between major releases being just six months.
Each major release, now called a feature release, will include those features ready at the
time of the release. This increased release cadence enables new features and enhancements to
be available to Java programmers in a timely fashion. Furthermore, it allows Java to respond
quickly to the demands of an ever-changing programming environment. Simply put, the faster
release schedule promises to be a very positive development for Java programmers.
At the time of this writing, feature releases are scheduled for March and September of
each year. As a result, JDK 10 was released in March 2018, which is six months after the
release of JDK 9. The next release will be in September 2018. Again, every six months a new
feature release will take place. Because of this more rapid release schedule, some releases will
be specified as long-term support (LTS). This means that such a release will be supported for
a certain length of time. Other feature releases are considered short term. At the time of this
writing, both JDK 9 and JDK 10 are indicated as short term, and the September 2018 release is
expected to be LTS. Consult the Java documentation for the latest information.
Java: A Beginner’s Guide, Seventh Edition was updated for JDK 9. As you can probably
guess, revising the book can involve a lengthy process. However, prior to the publication of the
next edition of the book, of the many new features added by JDK 10, two will be of immediate
interest to all Java programmers. For this reason, they are the subjects of this supplement. The
first feature is called local variable type inference. Local variable type inference is especially
important because it affects both the syntax and semantics of the Java language. The second
new JDK 10 feature described here involves the changes to the JDK version number scheme.
These changes support the time-based release schedule and alter the meaning of the version
number elements. The version number changes also affect the Runtime.Version class (which
encapsulates version information).
Beyond the two key features described here, JDK 10 includes other enhancements and
changes, including several to the Java API. You will want to review the information and release
notes provided by it in detail. Furthermore, you should examine each new six-month release
carefully. There are a number of new features on the horizon. It is truly an exciting time to be
a Java programmer!
to discern or cannot be denoted. (An example of a type that cannot be denoted is the type of an
anonymous class.) Furthermore, local variable type inference has become a common part of the
contemporary programming environment. Its inclusion in Java helps keep Java up-to-date with
evolving trends in language design.
To use local variable type inference, the variable must be declared with var as the type
name and it must include an initializer. For example, in the past you would declare a local int
variable called counter that is initialized with the value 10 as shown here:
int counter = 10;
Using type inference, this declaration can now also be written like this:
var counter = 10;
In both cases, counter will be of type int. In the first case, its type is explicitly specified. In the
second, its type is inferred as int because the initializer 10 is of type int.
As mentioned, var was added as a context-sensitive identifier. When it is used as the type
name in the context of a local variable declaration, it tells the compiler to use type inference
to determine the type of the variable being declared based on the type of the initializer. Thus,
in a local variable declaration, var is a placeholder for the actual, inferred type. However,
when used in most other places, var is simply a user-defined identifier with no special
meaning. For example, the following declaration is still valid:
int var = 1; // Here, var is simply a user-defined identifier.
In this case, the type is explicitly specified as int and var is the name of the variable being
declared. Even though it is a context-sensitive identifier, there are a few places in which the use
of var is illegal: It cannot be used as the name of a class, interface, enumeration, or annotation.
The following program puts the preceding discussion into action:
// A simple demonstration of local variable type inference.
class VarDemo {
public static void main(String args[]) {
The preceding example uses var to declare only simple variables, but you can also use
var to declare an array. For example:
var myArray = new int[10]; // This is valid.
Notice that neither var nor myArray has brackets. Instead, the type of myArray is inferred to
be int[ ]. Furthermore, you cannot use brackets on the left side of a var declaration. Thus, both
of these declarations are invalid:
var[] myArray = new int[10]; // Wrong
var myArray[] = new int[10]; // Wrong
In the first line, an attempt is made to bracket var. In the second, an attempt is made to bracket
myArray. In both cases, the use of the brackets is wrong because the type is inferred from the
type of the initializer.
It is important to emphasize that var can be used to declare a variable only when that
variable is initialized. For example, the following statement is wrong:
var counter; // Wrong! Initializer required.
Also, remember that var can be used only to declare local variables. It cannot be used when
declaring fields, parameters, or return types, for example.
As mentioned, one of the benefits of local variable type inference is its ability to streamline
code, and it is with reference types where such streamlining is most apparent. For example,
consider this declaration written the traditional way:
FileInputStream fin = new FileInputStream("test.txt");
Here, fin is inferred to be of type FileInputStream because that is the type of its initializer.
There is no need to explicitly repeat the type name. As a result, this declaration of fin is
substantially shorter than writing it the traditional way. Thus, the use of var streamlines the
declaration. In general, the streamlining attribute of local variable type inference helps lessen
the tedium of entering long type names into your program.
Of course, you can also use local variable type inference with user-defined classes, as the
following program illustrates:
// Local variable type inference with a user-defined class type.
class MyClass {
private int i;
MyClass(int k) { i = k;}
class VarDemo2 {
public static void main(String args[]) {
var mc = new MyClass(10); // Notice the use of var here.
Here, the type of mc will be MyClass because that is the type of the initializer. The output of
the program is shown here:
Value of i in mc is 10
Value of i in mc is now 19
class MyClass {
// ...
}
class VarDemo3 {
In the program, a hierarchy is created that consists of three classes, at the top of which
is MyClass. FirstDerivedClass is a subclass of MyClass, and SecondDerivedClass is a
subclass of FirstDerivedClass. The program then uses type inference to create three variables,
called mc, mc2, and mc3 by calling getObj( ). The getObj( ) method has a return type
of MyClass (the superclass), but returns objects of type MyClass, FirstDerivedClass, or
SecondDerivedClass, depending on the argument that it is passed. As the output shows, the
inferred type is determined by the return type of getObj( ) and not by the actual type of the
object obtained.
In this case, the type of mc is inferred to be MyClass<Integer>. Also notice that the use
of var results in a shorter declaration than would be the case otherwise. In general, generic
type names can often be quite long and (in some cases) complicated. The use of var will
substantially shorten such declarations.
One other point: You cannot use var as the name of a type parameter. For example, the
following is invalid:
class MyClass<var> { // Wrong
System.out.println();
System.out.println();
}
}
In this example, loop control variable x is inferred to be type double because that is the type of
its initializer. Iteration variable v is inferred to be of type int because that is the element type
of the array nums.
In a try-with-resources statement, the type of the resource can be inferred from its
initializer. For example, the following statement is now valid:
Here, fin is inferred to be of type FileInputStream because that is the type of its initializer.
Local variable type inference cannot be used to declare the exception type caught by a catch
statement.
JDK feature set. The update release counter indicates the number of a release that addresses
security, and possibly other problems. The patch release counter specifies a number of a
release that addresses a serious flaw that must be fixed as soon as possible. With each new
feature release, the interim, update, and patch counters are reset to zero.
It is useful to point out that the version number just described is a necessary component
of the version string, but optional elements may also be included in the string. For example, a
version string may include information for a pre-release version. Optional elements follow the
version number in the version string.
Runtime.Version was added to the Java API by JDK 9. Although this class is not described
within the book, the following discussion is included for the benefit of the interested reader.
The purpose of Runtime.Version is to encapsulate version information pertaining to the Java
runtime environment. Beginning with JDK 10, Runtime.Version was updated to include the
following methods that support the new feature, interim, update, and patch counter values:
int feature( )
int interim( )
int update( )
int patch( )
Each returns an integer value that represents the indicated value. Here is a short program that
demonstrates their use:
// Demonstrate Runtime.Version release counters.
class VerDemo {
public static void main(String args[]) {
Runtime.Version ver = Runtime.version();
Runtime.Version also includes several other methods. Three are of special interest because
they return optional version data, if present. They are pre( ), build( ), and optional( ), and
they obtain pre-release information, the build number, and other optional data, respectively.
If your program requires access to the Java version string, then you will want to explore
Runtime.Version in detail.
As a result of the change to time-based releases, the following methods in Runtime.Version
have been deprecated: major( ), minor( ), and security( ). Previously, these returned the major
version number, the minor version number, and the security update number. These values have
been superseded by the feature, interim, and update numbers, as just described.