Objectos Weekly #007: Using var with non-accessible types

Marcio Endo
December 26, 2022

Welcome to Objectos Weekly issue #007.

I had hopes of sending out this issue on Sunday, December 25th. It turns out it is hard to get anything done after you had a very large meal and stayed up late with your family the night before. To those who celebrate it, I hope you had a happy Christmas.

I will probably take a small break this week. So the next issue might only come out on January 8th or later.

To all subscribers and readers, thank you! As someone (re-)building a business, it means a lot. I wish you all a Happy New Year.

Let's begin.

Local-variable type inference

Java 10 introduced local-variable type inference to the language. It allows you to declare a local variable using the var contextual keyword:

var outputStream = new ByteArrayOutputStream();

The explicit type information on the left-hand side of the declaration is not required; it is inferred by the compiler instead.

Non-denotable types

The JEP responsible for delivering this language feature was JEP 286. It mentions that, using var to declare local variables, might increase the programmer's exposure to non-denotable types.

I first learned about non-denotable types in issue #065 of Sip of Java by Billy Korando.

To understand non-denotable types, consider the following example. Note that it does not compile:

// does not compile!

Object o = new Object() {
  public void canYouSeeMe() {
    System.out.println("I can see you!");
  }
};

o.canYouSeeMe();

It declares an anonymous class having a canYouSeeMe() method. An instance is created and assigned to the variable o of type Object. Lastly, it tries to invoke the canYouSeeMe() method.

The class is anonymous and, therefore, we cannot refer to it using a name. In other words, the class' type is non-denotable. This is the reason why we declared the variable o with the type of its immediate superclass instead: Object. The information that the class declares a canYouSeeMe method is, therefore, lost.

Retaining type information with var

Let's change the local variable declaration to use the var keyword:

// compiles without errors

var o = new Object() {
  public void canYouSeeMe() {
    System.out.println("I can see you!");
  }
};

o.canYouSeeMe();

The code now compiles without errors. It prints I can see you! to the console.

So, by using the var keyword, we were able to retain the information regarding the canYouSeeMe method existence.

Non-accessible types

I recently realized that using var allows you to partially escape an access control restriction. More specifically, you can declare a variable of a type having package access outside the package in which it is defined.

It is somewhat similar to using it to refer to a non-denotable type.

A hypothetical library

Suppose a Box class declared with the package access:

package escape.api;

// no access modifiers
class Box {
  private final String value;

  Box(String value) {
    this.value = value;
  }

  public String get() {
    return value;
  }
}

Please bear with me. This particular example is not meant to be practical.

Our Box class is used in a Widget abstract class:

package escape.api;

public abstract class Widget {
  public abstract void execute();

  protected final void consume(Box box) {
    System.out.println(box.get());
  }

  protected final Box produce(String value) {
    return new Box(value);
  }
}

It declares two protected methods: produce and consume. The methods are meant to be used in tandem. The first one returns a Box instance while the second one accepts a Box instance.

Let's use the Widget class. We must implement the execute method to do so:

package escape.app;

import escape.api.Widget;

public class Example01 extends Widget {
  @Override
  public void execute() {
    consume(produce("Hello world!"));
  }
  
  public static void main(String[] args) {
    new Example01().execute();
  }
}

The Example01 class is defined at a different package than our Box class. The former, therefore, is not accessible from the latter.

But we do not mention the Box class directly in the source code. So the class compiles without errors.

When executed, it prints Hello world! to the console.

Introducing a local variable

As mentioned, Box is not accessible from our example class. So, if we were to introduce a local variable of type Box, our code should fail to compile:

package escape.app;

import escape.api.Box;
import escape.api.Widget;

// does not compile!
public class Example02 extends Widget {
  @Override
  public void execute() {
    Box box = produce("Hello world!");
    
    consume(box);
  }
}

Compilation fails with the following message:

$ javac -d /tmp src/main/java/escape/{api,app}/*
src/main/java/escape/app/Example02.java:8: error: 
    Box is not public in escape.api;
    cannot be accessed from outside package
    
import escape.api.Box;
                 ^
src/main/java/escape/app/Example02.java:18: error:
    Box is not public in escape.api;
    cannot be accessed from outside package
    
    Box box = produce("Hello world!");
    ^
2 errors

This is expected. Remember, Box was declared with the package access.

But what if we declare the local variable using the var keyword instead?

Using var to declare the local variable

If we replace the Box type with the var keyword:

package escape.app;

import escape.api.Widget;

// compiles without errors!
public class Example03 extends Widget {
  @Override
  public void execute() {
    var box = produce("Hello world!");

    consume(box);
  }
  
  public static void main(String[] args) {
    new Example03().execute();
  }
}

The code now compiles without errors! So, by using the var keyword, we are able to declare a local variable of a non-accessible type.

When executed, it prints Hello world!.

Invoking a Box method

What if we try to invoke the get method declared in Box?

Well, the following does not compile:

package escape.app;

import escape.api.Widget;

public class Example04 extends Widget {
  @Override
  public void execute() {
    var box = produce("Hello world!");

    // compilation error!
    // cannot invoke method get()
    System.out.println(box.get());
  }
}

Compilation with javac fails with the following message:

$ javac -d /tmp src/main/java/escape/{api,app}/*
src/main/java/escape/app/Example04.java:19: error:
    Box.get() is defined in an
    inaccessible class or interface
    
    System.out.println(box.get());
                          ^
1 error

Invoking a Object method

Perhaps more interestingly the following does not compile either:

package escape.app;

import escape.api.Widget;

public class Example05 extends Widget {
  @Override
  public void execute() {
    var box = produce("Hello world!");

    // compilation error!
    System.out.println(box.toString());
  }
}

Here's the error message from javac:

$ javac -d /tmp src/main/java/escape/{api,app}/*
src/main/java/escape/app/Example05.java:15: error:
    Object.toString() is defined in an
    inaccessible class or interface
    
    System.out.println(box.toString());
                          ^
1 error

So it seems we cannot invoke any method at all. The only thing we can do with it, it seems, is use it as the argument of a method.

An actual library

The hypothetical library example is not so hypothetical after all.

I am currently working on Objectos Code. It is an open-source Java library for generating Java source code. I use it internally at Objectos. The first public alpha release should be out in Q1 of 2023.

Re-thinking the API

I am experimenting with a new API and considering a re-balance of tradeoffs. My idea is for the following Objectos Code:

import objectos.code.JavaTemplate;

public class Example extends JavaTemplate {
  @Override
  protected final void definition() {
    _package("com.example");
    
    _public(); _class("Box"); body(
      _private(), _final(), t(String.class), id("value"),
      
      _public(), id("Box"), t(String.class), id("value"), block(
        assign(n(_this(), "value"), n("value"))
      ),
      
      _public(), t(String.class), id("get"), block(
        _return(n("value"))
      )
    );
  }   
}

To generate the following Java code:

package com.example;

public class Box {
  private final String value;
  
  public Box(String value) {
    this.value = value;
  }
  
  public String get() {
    return value;  
  }
}

Please note this is still in the realm of the ideas. And as I mentioned before: there are tradeoffs.

Caveat Emptor

You might have noticed in the previous example that the t method declares a type. And the same t(String.class) invocation is used to declare the type of:

As those declarations have the same type, one might be tempted to do the following:

import objectos.code.JavaModel.ClassType;
// not accessible -> ^^^^^^^^^
import objectos.code.JavaTemplate;

public class WrongDoNotDoThis extends JavaTemplate {
  @Override
  protected final void definition() {
    _package("com.example");
    
    // do not do this!!!
    ClassType str = t(String.class);
    
    _public(); _class("Box"); body(
      _private(), _final(), str, id("value"),
      
      _public(), id("Box"), str, id("value"), block(
        assign(n(_this(), "value"), n("value"))
      ),
      
      _public(), str, id("get"), block(
        _return(n("value"))
      )
    );
  }   
}

Well, the API must not be used like that. The return value of any of the methods must not be "re-used".

The reason is the API "records" all of the method invocations and then "plays them back" again to generate the source code in the correct order.

So, if you have three locations where the type String occurs, you need three t(String.class) method invocations.

The previous example would not compile though. ClassType is nested in a type with package access and it cannot be accessed outside the objectos.code package.

But, as we have seen, if we use var instead, then the following will compile without errors:

import objectos.code.JavaTemplate;

public class WrongDoNotDoThis extends JavaTemplate {
  @Override
  protected final void definition() {
    _package("com.example");
    
    // `var` compiles without errors
    var str = t(String.class);
    
    _public(); _class("Box"); body(
      _private(), _final(), str, id("value"),
      
      _public(), id("Box"), str, id("value"), block(
        assign(n(_this(), "value"), n("value"))
      ),
      
      _public(), str, id("get"), block(
        _return(n("value"))
      )
    );
  }   
}

So, in this case, there's not that can be done:

The second option is not as bad as it sounds: the invalid generated code will be eventually compiled by a Java compiler.

So, either way, a compiler will catch the error.

Let's work together

Please know that I will be open for freelance consulting work in 2023. 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.