Objectos Weekly #005: Java's contextual keywords, class names and method names
Marcio Endo
Posted on December 17, 2022
Welcome to Objectos Weekly issue #005.
This is the first issue sent via the email newsletter. If you have received this issue in your email and had any troubles opening it, I apologize in advance. I would really appreciate it if you sent me a few words explaining what went wrong. And thank you in advance.
Please know that I will be open for Java freelancing work in 2023. You can find my contacts on this page. All work will be provided via Objectos Software LTDA based in São Paulo, Brazil.
Contextual keywords
Java 9 introduced the concept of restricted keywords. Since Java 17 they are called contextual keywords.
Contextual keywords allow for the same token to be interpreted as either a keyword or as a different token, an identifier typically. It depends on where the token appears in a compilation unit.
Reserved keywords
To understand contextual keywords, let's first discuss reserved keywords.
In Java, identifiers cannot have the same character sequence of a reserved keyword. You cannot name a method try
because try
is a reserved keyword.
// does not compile!
// 'try' is a reserved keyword
public void try() {
...
}
The same rule applies to other declarations having one or more identifiers. Examples include package names, module names, field names, local variable names, class names and others.
Backward compatibility
Reserved keywords represent a problem when evolving the language. As an example, let's take the introduction of records in Java 14.
Suppose that you are responsible for an application that currently targets Java 11. It contains a class called Record
. So spread throughout your codebase you have many fields, methods and local variables named record
:
class Foo {
private Record record;
public static Record of(int value) {
var record = Record.create();
record.set(value);
return record;
}
public Record record() {
return record;
}
}
Suppose now that you want to upgrade to Java 17.
If the language designers decided to make record
a reserved keyword, your application would not compile anymore.
So they made record
a contextual keyword instead.
List of contextual keywords
As of Java 19, the contextual keywords are the following:
exports opens requires uses
module permits sealed var
non-sealed provides to with
open record transitive yield
We can use them in almost any place an identifier is required. As an example, consider the following:
package post.yield;
public class Contextual {
public static void main(String[] uses) {
var var = "Contextual keywords example";
exports(var);
}
private static void exports(String record) {
System.out.println(record);
}
}
The Java program above uses contextual keywords:
-
yield
in the package name; -
uses
in the main method parameter name; -
var
as the name of the local variable; -
exports
as the name of the private method; and -
record
as the name of the parameter of the private method.
The program compiles and runs. It prints:
Contextual keywords example
Contextual keywords and class names
In the last section I emphasized that contextual keywords can be used in almost any place an identifier is required.
A place where a few of the contextual keywords are restricted is class names. To be precise, by 'class name' I mean the name of any type declaration, i.e, a class, an interface (annotation included), an enum or a record.
The following jshell session illustrates all of the contextual keywords that are not allowed as class names. I have edited the output slightly so it is easier to read:
jshell> interface permits {}
| Error:
| 'permits' not allowed here
| as of release 15, 'permits'
| is a restricted type name
| and cannot be used for type declarations
| interface permits {}
| ^
jshell> record record(int value){}
| Error:
| 'record' not allowed here
| as of release 14, 'record'
| is a restricted type name
| and cannot be used for type declarations
| record record(int value){}
| ^
jshell> enum sealed {INSTANCE;}
| Error:
| 'sealed' not allowed here
| as of release 15, 'sealed'
| is a restricted type name
| and cannot be used for type declarations
| enum sealed {INSTANCE;}
| ^
jshell> class var {}
| Error:
| 'var' not allowed here
| as of release 10, 'var'
| is a restricted type name
| and cannot be used for type declarations
| class var {}
| ^
jshell> @interface yield {}
| Error:
| 'yield' not allowed here
| as of release 14, 'yield'
| is a restricted type name
| and cannot be used for type declarations
| @interface yield {}
| ^
So the following contextual keywords are not allowed: permits
, record
, sealed
, var
and yield
.
As usual, this is defined by the JLS section 3.8. It defines the production TypeIdentifier
:
TypeIdentifier:
Identifier but not permits, record, sealed, var, or yield
So class names can be any identifier except those five contextual keywords.
Let's try with contextual keywords not listed in the TypeIdentifier
production:
jshell> class opens {}
| created class opens
jshell> class exports {}
| created class exports
Other keywords work.
Contextual keywords and method names
Method names do not suffer from the same restriction of class names.
However, if you name a method yield
you must observe one thing. The following class does not compile:
// does not compile!
public class YieldMethod {
public void callYield() {
yield(); // <-- compilation error here
}
void yield() {
System.out.println("yield() called!");
}
}
The problem is in the method callYield
. Defining the class above in jshell causes the following error message:
| Error:
| invalid use of a restricted identifier 'yield'
| (to invoke a method called yield,
| qualify the yield with a receiver or type name)
| yield();
| ^---^
So, to fix the compilation error, we must rewrite the callYield
method to:
public void callYield() {
this.yield();
}
Let's test this fix using jshell:
jshell> public class YieldMethod {
...> public void callYield() {
...> this.yield();
...> }
...>
...> void yield() {
...> System.out.println("yield() called!");
...> }
...> }
...>
| created class YieldMethod
jshell> new YieldMethod().callYield();
yield() called!
Why though?
I don't know the reason why. But, as usual, this is defined in the Java Language Specification.
Section 15.12 defines method invocation expressions:
MethodInvocation:
MethodName ( [ArgumentList] )
TypeName . [TypeArguments] Identifier ( [ArgumentList] )
ExpressionName . [TypeArguments] Identifier ( [ArgumentList] )
Primary . [TypeArguments] Identifier ( [ArgumentList] )
super . [TypeArguments] Identifier ( [ArgumentList] )
TypeName . super . [TypeArguments] Identifier ( [ArgumentList] )
Notice that, except for the first form, the method name is given by an Identifier
token. The first is the form of the unqualified method invocation. In the unqualified form the method name is given by:
MethodName:
UnqualifiedMethodIdentifier
UnqualifiedMethodIdentifier:
Identifier but not yield
So you cannot invoke a method named yield
using the unqualified form.
This is the reason why, in our previous example, we had to qualify the yield
method invocation.
Until the next issue of Objectos Weekly
So that's it for today. I hope you enjoyed reading.
The source code of all of the examples are in this GitHub repository.
P.S. Thank you Dr. Heinz M. Kabutz for the encouragement to start the newsletter
Posted on December 17, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
December 17, 2022