Extremely Serious

Category: Java 10

Exploring Java Non-Denotable Types

Java's evolving type system has brought powerful features like local variable type inference and lambda expressions — providing concise and expressive coding patterns. One nuanced concept that arises in this landscape is the idea of non-denotable types, which plays a subtle but important role in how type inference works and enables interesting use cases such as mutable state within lambdas without atomic types.

What Are Non-Denotable Types in Java?

Java programmers are familiar with declaring variables with explicit types like int, String, or class names. These are called denotable types — types you can write explicitly in your source code.

However, the Java compiler also works with non-denotable types behind the scenes:

  • These types cannot be explicitly named or expressed in Java source code.

  • Common examples include:

    • Anonymous class types: Each anonymous class has a unique generated subclass type.

    Example:

    var obj = new Runnable() {
        public void run() { System.out.println("Running..."); }
        public void runTwice() { run(); run(); }
    };
    obj.runTwice(); // This works because obj's type is the anonymous class, not just Runnable
    • Capture types: Types arising from generics with wildcards and the capture conversion process.
    import java.util.List;
    
    public class CaptureType {
    
        public static void main(String[] args) {
            List<?> unknownList = List.of("A", "B", "C");
    
            // Wildcard capture example: helper method with captured type
            printFirstElement(unknownList);
        }
    
        static <T> void printFirstElement(List<T> list) {
            if (!list.isEmpty()) {
                // 'T' here is the capture of '?'
                System.out.println(list.get(0));
            }
        }
    }

When declaring a variable with var (introduced in Java 10), the compiler sometimes infers one of these non-denotable types. This is why some variables declared with var cannot be assigned to or typed explicitly without losing precision or functionality.

Why Does This Matter?

Non-denotable types extend Java's expressiveness, especially for:

  • Anonymous class usage: The type inferred preserves the anonymous class's full structure, including methods beyond superclass/interface declarations.
  • Enhanced local variable inference: var lets the compiler deduce precise types not writable explicitly in source code, often improving code maintainability.

A Practical Example: Incrementing a Counter in a Lambda Without Atomic Types

Mutability inside lambdas is limited in Java. Variables captured by lambdas must be effectively final, so modifying local primitives directly isn't possible. The common approach is to use AtomicInteger or a one-element array for mutation.

However, by leveraging a non-denotable anonymous class type, it is possible to mutate state inside a lambda without using atomic types.

public class Main {
    public static void main(String[] args) {
        // counter is an anonymous class instance with mutable state (non-denotable type)
        var counter = new Object() {
            int count = 0;
            void increment() { count++; }
            int getCount() { return count; }
        };

        // Lambda expression capturing 'counter' and incrementing the count
        java.util.function.Consumer<Integer> incrementer = (i) -> counter.increment();

        // Invoking the lambda multiple times to increment the internal counter
        for (int i = 0; i < 5; i++) {
            incrementer.accept(i);
        }

        System.out.println("Counter value: " + counter.getCount());  // Outputs 5
    }
}

How this works:

  • The counter variable's type is inferred as the unique anonymous class type (non-denotable).
  • This anonymous class contains a mutable field (count) and methods to manipulate and access it.
  • The lambda (incrementer) invokes the method increment() on the anonymous class instance, modifying its internal state.
  • This avoids the need for an AtomicInteger or mutable containers like arrays.
  • Access to the additional method increment() (which is not part of regular interfaces) showcases the benefit of the precise anonymous class type inference.

Benefits of This Approach

  • Avoids clutter and complexity from atomic classes or array wrappers.
  • Keeps code safe within single-threaded or controlled contexts (not thread-safe, so take care in multithreaded scenarios).
  • Utilizes modern Java's type inference power to enable cleaner mutable state management.
  • Demonstrates practical use of non-denotable types, a somewhat abstract but powerful concept in Java's type system.

Final Thoughts

Non-denotable types may seem like compiler internals, but they shape everyday programming modern Java developers do, especially with var and lambdas. Ignoring them means missing out on some elegant solutions like mutable lambdas without extra overhead.

Understanding and utilizing non-denotable types open up possibilities to leverage Java's type system sophistication while writing concise, expressive, and idiomatic code.

Java UTF-16LE Base64 CODEC

The UTF-16LE base64 encoding is compatible to be used with powershell's encoded command.

Encoding

//The text to encode.
var command = "Write-Output \"Hello World\"";
var encodedString = Base64.getEncoder().encodeToString(command.getBytes(StandardCharsets.UTF_16LE));
System.out.printf("Base64: %s%n", encodedString);

Output

Base64: VwByAGkAdABlAC0ATwB1AHQAcAB1AHQAIAAiAEgAZQBsAGwAbwAgAFcAbwByAGwAZAAiAA==

The preceding output can be used with powershell like the following:

powershell -encodedcommand VwByAGkAdABlAC0ATwB1AHQAcAB1AHQAIAAiAEgAZQBsAGwAbwAgAFcAbwByAGwAZAAiAA==

Decoding

//The base64 text to decode.
var base64="VwByAGkAdABlAC0ATwB1AHQAcAB1AHQAIAAiAEgAZQBsAGwAbwAgAFcAbwByAGwAZAAiAA==";
byte[] decodedBytes = Base64.getDecoder().decode(base64);
String decodedString = new String(decodedBytes, StandardCharsets.UTF_16LE);
System.out.printf("Decoded: %s%n", decodedString);

Output

Decoded: Write-Output "Hello World"

Regex Capture Groups with Java

The following java code extracts the group, artifact and version using regex capture groups:

import java.util.regex.Pattern;

public class Main {

    public static void main(String ... args) {
        //Text to extract the group, artifact and version
        var text = "org.junit.jupiter:junit-jupiter-api:5.7.0";

        //Regex capture groups for Group:Artifact:Version
        var pattern = "(.*):(.*):(.*)"; 

        var compiledPattern = Pattern.compile(pattern);
        var matcher = compiledPattern.matcher(text);
        if (matcher.find( )) {
            System.out.println("Whole text: " + matcher.group(0) );
            System.out.println("Group: " + matcher.group(1) );
            System.out.println("Artifact: " + matcher.group(2) );
            System.out.println("Version: " + matcher.group(3) );
        } else {
            System.out.println("NO MATCH");
        }
    }
}

Output

Whole text: org.junit.jupiter:junit-jupiter-api:5.7.0
Group: org.junit.jupiter
Artifact: junit-jupiter-api
Version: 5.7.0

ThreadPoolExecutor with ArrayBlockingQueue and Custom Thread Name

Create an implementation of ThreadFactory to create a thread with custom name for ThreadPoolExecutor as follows:

class MyThreadFactory implements ThreadFactory {
    private AtomicLong threadCounter;

    private MyThreadFactory() {
        threadCounter = new AtomicLong();
    }

    @Override
    public Thread newThread(Runnable runnable) {
        var thread=new Thread(runnable);
        thread.setName(String.join("-","my-thread",
                String.valueOf(threadCounter.incrementAndGet())));
        return thread;
    }
}

The preceding class will generate a thread with the name starting with my-thread. Use the instance of this class in constructing the ThreadPoolExecutor as follows:

var executor = new ThreadPoolExecutor(2, 4, 60L, TimeUnit.SECONDS,
        new ArrayBlockingQueue<>(100), new MyThreadFactory(), 
        new ThreadPoolExecutor.CallerRunsPolicy());

The preceding declaration creates an instance of ThreadPoolExecutor with 2 core threads, 4 maximum threads, 60 seconds keep alive and supports 100 items in the queue. The queue size is defined by the instance of ArrayBlockingQueue class.

Start all the core threads as follows:

executor.prestartAllCoreThreads();

Using a profiling tool we can search for all the threads whose names starts with my-thread like the following screenshot:

Don't forget to call any shutdown/terminate methods when done using the executor.