Categories
Java

Hidden Classes in Java 14

This feature was introduced in Java 14, and it allows you to create classes at runtime and not load them into the classpath. These classes are created using the Lookup.defineHiddenClass method and it can only be accessed using the Lookup object that created the class. This feature can be used to improve the performance and security of the application, as it allows you to load classes only when they are needed and also it can be used to hide the implementation details of the classes from the users.

Here’s an example of how to use Hidden Classes:

Lookup lookup = MethodHandles.lookup();
byte[] classBytes = // bytecode of the class
Class<?> hiddenClass = lookup.defineHiddenClass(classBytes, true).lookupClass();

In this example, we are creating a hidden class using the Lookup.defineHiddenClass method and passing the bytecode of the class, and the class is not loaded into the classpath and it can only be accessed using the Lookup object that created the class.

This feature can be useful in situations where you want to improve the performance of the application and also to hide the implementation details of the classes from the users, but it’s important to note that hidden classes are not supported by all JVMs and it’s a new feature that’s not widely used yet.

Practical example

Let’s say you have an application that allows users to install and run different plugins, and each plugin is implemented as a separate Java class.

Normally, with traditional Java classes, all of the plugin classes would need to be loaded at the same time and linked with the application’s main class, even if the user is only using a small subset of the available plugins. This can lead to increased startup time and memory usage.

With the hidden classes feature, you can now create an “interface” class and a “factory” class that are both visible to the main application. The plugin classes themselves can be hidden, so they are not loaded and linked with the main class until they are actually needed. This allows for lazy loading of the plugins, which can improve startup time and reduce memory usage.

In this case, the hidden classes feature allows the developer to only load the classes that are required by the user, reducing the memory usage and initial loading time, making the application more efficient and responsive.

Source code:

interface Plugin {
    void run();
}

class PluginFactory {
    private final Map<String, Class<? extends Plugin>> hiddenPlugins = new HashMap<>();

    void registerPlugin(String name, byte[] bytecode) {
        Class<? extends Plugin> pluginClass = defineHiddenClass(bytecode, Plugin.class).asSubclass(Plugin.class);
        hiddenPlugins.put(name, pluginClass);
    }

    Plugin createPlugin(String name) {
        Class<? extends Plugin> pluginClass = hiddenPlugins.get(name);
        if (pluginClass == null) {
            throw new IllegalArgumentException("Unknown plugin: " + name);
        }
        try {
            return pluginClass.getConstructor().newInstance();
        } catch (ReflectiveOperationException e) {
            throw new IllegalStateException("Failed to create plugin instance", e);
        }
    }
}

class Main {
    public static void main(String[] args) {
        PluginFactory factory = new PluginFactory();
        factory.registerPlugin("example", getBytecodeForExamplePlugin());
        Plugin plugin = factory.createPlugin("example");
        plugin.run();
    }
}

In this example, the Plugin interface and PluginFactory class are visible to the main application. The individual plugin classes are hidden, and their bytecode is passed to the registerPlugin method of the PluginFactory class. The createPlugin method can then instantiate a new instance of the hidden class by calling the getConstructor().newInstance() method, and then invoke its run() method.

Leave a Reply

Your email address will not be published. Required fields are marked *