DEV Community

Cover image for Objectos Weekly #005: Java's contextual keywords, class names and method names
Marcio Endo
Marcio Endo

Posted on • Originally published at objectos.com.br

Objectos Weekly #005: Java's contextual keywords, class names and method names

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() {
  ...
}
Enter fullscreen mode Exit fullscreen mode

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 {}
|             ^
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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!");
  }
}
Enter fullscreen mode Exit fullscreen mode

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();
|      ^---^
Enter fullscreen mode Exit fullscreen mode

So, to fix the compilation error, we must rewrite the callYield method to:

public void callYield() {
  this.yield();
}
Enter fullscreen mode Exit fullscreen mode

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!
Enter fullscreen mode Exit fullscreen mode

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] )
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

Top comments (0)