Objectos Weekly #008: The Java receiver parameter

Marcio Endo
January 8, 2023

Welcome to Objectos Weekly issue #008.

I hope you had a great Holiday season.

In the first issue of 2023 we will discuss Java's receiver parameter. A feature of the language I did not know about until very recently.

Let's begin.

The Java receiver parameter

Since Java 8 you can declare an optional and purely syntactic parameter in all instance methods. For example, the following is a valid toString method implementation:

public class Example {
  @Override
  public String toString(Example this) {
    return "I'm a receiver parameter example.";
  }
}

Let's verify that this is an actual toString method. The following code invokes it implicitly:

System.out.println(new Example());

It prints:

I'm a receiver parameter example

This kind of parameter is formally called the receiver parameter.

Type annotations

Java 8 introduced type annotations to the language. Section 9.7.4 of the JLS says the following about them:

A type annotation is an annotation that applies to a type (or any part of a type), and whose annotation interface is applicable in type contexts.

It can be used by static analysis tools to, for example, warn you of possible programming mistakes:

class MyList<E> implements @Unmodifiable List<E> {
  ...
}

In the example, the List interface is annotated with a hypothetical Unmodifiable annotation. If we use the MyList like the following, a hypothetical tool might raise a warning or error:

var list = MyList.of("A", "B", "C");
list.add("D");
//   ^^^
// tool might raise warning or error
// at compile time as
// list modification is disallowed

And what is their relation to receiver parameters?

The receiver parameter + type annotations

The receiver parameter exists so that one can annotate the type of the object for which the method is invoked. Here's an example:

public class Service {
  @Target(ElementType.TYPE_USE) public @interface Started {}
  
  @Target(ElementType.TYPE_USE) public @interface Configured {}

  public void configure() {}

  public void start(@Configured Service this) {}

  public void stop(@Started Service this) {}
}

It represents a generic service. Note that it defines two type annotations Started and Configured. To properly use it, you must invoke its methods in the correct order:

A hypothetical static tool could ensure the Service is used in the correct manner. In other words, the tool would allow the following:

var service = new Service();
service.configure();
...
service.start();
...
service.stop();

On the other hand, it would disallow the following:

var service = new Service();
service.start();
//      ^^^^^
// compilation error:
// must call configure first

Note that this would be done at compile time.

To be honest, I am not sure if a tool could do this kind of static analysis. In any case, this is the best example I could come up with.

A very well-designed solution

The thing to keep in mind is that the receiver parameter is purely a syntactic construct. Its sole purpose is to allow the type to be annotated.

In other words, it is not a formal parameter. It does not have a name that you can refer to. Its "name" is a reserved keyword. In the case of instance methods, the "name" must be the reserved keyword this.

This solution makes it impossible to misuse it (as far as I can tell).

A let's pretend game

Let's pretend the receiver is a formal parameter:

public class ToString {
  @Override
  public String toString(ToString this) {
    var self = this;
    var type = self.getClass();
    return "Simple name is: " + type.getSimpleName();
  }
}

See? We have declared a local variable self and assigned our "parameter" to it.

Now let's remove the parameter declaration and keep the method body the same:

public class ToString {
  @Override
  public String toString() {
    var self = this;
    var type = self.getClass();
    return "Simple name is: " + type.getSimpleName();
  }
}

Can you see that the method is still both:

Cool, isn't it?

Same signature

Again, receiver is purely a syntactic construct. If we try to compile the following class:

public class Signature {
  @Override
  public String toString() {
    return "Parameterless";
  }

  @Override
  public String toString(Signature this) {
    return "Receiver";
  }
}

Compilations fails as both methods have the same signature:

$ javac -d /tmp src/main/java/iter3/Signature.java 
src/main/java/iter3/Signature.java:25: error: 
    method toString() is already defined in class Signature
  public String toString(Signature this) {
                ^
1 error

Same bytecode

Let's compare the bytecode generated by javac of the two ToString classes of the previous section. Please note I've edited the output slightly so it is easier to read.

This is the javap output for the one with the receiver parameter:

public class iter3.yea.ToString
(...)
  public java.lang.String toString();
    descriptor: ()Ljava/lang/String;
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=3, args_size=1
         0: aload_0
         1: astore_1
         2: aload_1
         3: invokevirtual #22
         6: astore_2
         7: aload_2
         8: invokevirtual #26
        11: invokedynamic #32,  0
        16: areturn
      LineNumberTable:
        line 25: 0
        line 26: 2
        line 27: 7

And here's the output for the one without the receiver parameter:

public class iter3.nay.ToString
(...)
  public java.lang.String toString();
    descriptor: ()Ljava/lang/String;
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=3, args_size=1
         0: aload_0
         1: astore_1
         2: aload_1
         3: invokevirtual #22
         6: astore_2
         7: aload_2
         8: invokevirtual #26
        11: invokedynamic #32,  0
        16: areturn
      LineNumberTable:
        line 25: 0
        line 26: 2
        line 27: 7

They are the same.

Of course, if we were to annotate the receiver parameter with a non source-retention annotation, the following gets added to the bottom of the output:

    RuntimeVisibleTypeAnnotations:
      0: #42(): METHOD_RECEIVER
        iter4.yea.Anno$Runtime
    RuntimeInvisibleTypeAnnotations:
      0: #44(): METHOD_RECEIVER
        iter4.yea.Anno$Class

But the method body remains the same.

It must be declared before the formal parameters

You can declare a receiver parameter along with formal parameters:

public class Arity {
  public void m0(Arity this) {}
  public void m1(Arity this, int a) {}
  public void m2(Arity this, int a, int b) {}
}

The only catch is that it must be the first one. If you try to compile the following (wrong) class:

public class Wrong {
  public void m2(int a, Wrong this, int b) {}
}

Compilation fails with the following error:

$ javac -d /tmp src/main/java/iter5/Wrong.java
src/main/java/iter5/Wrong.java:9: error: 
    as of release 8, 
    'this' is allowed as the parameter name
    for the receiver type only
  public void m2(int a, Wrong this, int b) {}
                              ^
  which has to be the first parameter, 
  and cannot be a lambda parameter
1 error

Type annotations only

Remember that the receiver parameter was introduced to the language because of type annotations.

So, even though its formal name is receiver parameter, you can only annotate it with type annotations. In other words, you cannot annotate it with an annotation that targets parameters exclusively.

The following class does not compile:

public class TypeUseOnly {
  @Target(ElementType.PARAMETER)
  public @interface Param {}

  @Target(ElementType.TYPE_USE)
  public @interface Type {}

  @Override
  public String toString(@Param @Type TypeUseOnly this) {
    // compilation error ^^^^^^
    return "Only TYPE_USE annotations";
  }
}

Compilation fails with the following message:

$ javac -d /tmp src/main/java/iter5/TypeUseOnly.java 
src/main/java/iter5/TypeUseOnly.java:29: error: 
    annotation @Param not applicable in this type context
  public String toString(@Param @Type TypeUseOnly this) {
                         ^
1 error

To fix the compilation error we need to either:

Inner classes' constructors

As a final note, you can also declare a receiver parameter in the constructor of a inner class. Just to be clear, by inner class I mean a non-static nested class.

In the constructor of a inner class the receiver parameter denotes the type of the immediately enclosing type. The syntax is slightly different when compared to instance methods.

The following example illustrates it:

public class Outer {  
  class Inner {
    final Outer outer;

    Inner(Outer Outer.this) {
      outer = Outer.this;
    }
  }
}

The "name" of the parameter is Outer.this. In other words, it is the name of the enclosing class followed by .this.

Note that the syntax, once again, makes it impossible to misuse it (as far as I can tell). The previous example is equivalent to the following:

public class Outer {  
  class Inner {
    final Outer outer;

    Inner() {
      outer = Outer.this;
    }
  }
}

Note, once again, that the constructor without the receiver parameter is:

Cool, isn't it?

Let's work together

Do you have a legacy Java application that needs some looking at? Perhaps I can help. Let's get in touch.

You can find my contacts on this page. All work will be provided via Objectos Software LTDA based in São Paulo, Brazil.

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.

Please send me an e-mail if you have comments, questions or corrections regarding this post.