Qualified exports

In the previous section, we looked at transitive dependencies that let us tweak the readability relationship between modules to handle some special use cases. In this section, you'll be introduced to a way you can tweak the accessibility relationships in some special cases. This can be done using a feature called qualified exports. Let's learn what they are.

You've already learned that the exports keyword lets you specify which packages in a module are allowed to be used outside the module. The exported packages form the public contract of the module, and any module that reads such a module automatically gets accessibility to those exported packages.

But there is a catch here! Ideally, you'd like to design your modules and APIs as standalone entities and you always have a clear idea about what the modules should export. But you might encounter real-world scenarios where that isn't the case. Sometimes you'll have to design modules to make them work well with others, and that incurs an interesting cost.

Consider that you have built a library module called B that is being used by a consumer module A:

The developer of module A can obviously call the exported APIs of B now. But then, it so happens that they also need this one other API in module B that isn't exported yet. You didn't initially want to export that private package from module B because it's not something that's commonly needed outside B, but it turns out there's just one other module, module A, that really needs it! So, to make the developer of module A happy, you add that private package to the exports list from module B:

    module B { 
      exports moduleb.public; 
      exports moduleb.privateA; // required only for module A 
    } 

After a while, a new module, module C, depends on module B. It too has an interesting use case where it needs another private API from module B. It's very likely that it's only C that'll ever need that API outside of B, but in order to make module C work, you have no option but to add that package to the exported packages of module B:

    module B { 
      exports moduleb.public; 
      exports moduleb.privateA; // required only for module A 
      exports moduleb.privateC; // required only for module C 
    } 

I hope you notice the problem already. Now two originally private packages in module B are now public for use by every module that reads B, although the intent for exporting those packages was to satisfy two very small and specific use cases. If this goes on, the exported APIs in your module end up being the greatest common set of APIs that are sure to keep every consumer module happy. In the process, you've lost the advantages of encapsulation. Now that an internal package is exported, albeit with the intention to satisfy one module, it is available for any module to use. Wouldn't it be great if when exporting packages to a module, you could selectively specify which modules the packages need to be exported to? If so, then only those selected modules could access those specially exported packages. All other modules would only get the publicly exported packages.

This is possible using qualified exports. The exports clause in the module definition optionally lets you specify which module you need to export the package to. If you do that, the export is not public anymore. Only the module you specify has access to it. The syntax is:

    exports <package-name> to <module1>, <module2>,... ;

Applying this concept to our example module B, we can still have better encapsulation of our private packages by selectively giving modules A and C access to what they alone need:

    module B { 
      exports moduleb.public;        // Public access to every module 
that reads me exports moduleb.privateA to A; // Exported only to module A exports moduleb.privateC to C; // Exported only to module C }

With this change, package moduleb.privateA is accessible to A, but not to B or any other module that reads B. Similarly, moduleb.privateC is accessible only by C. Now, while the private APIs are still not fully encapsulated, you at least know for sure what modules they are accessed by, and so any changes are easier to manage.

An example usage of this feature in the Java platform is in the java.base module. This module contains a lot of core internal packages that have been deemed internal and that we Java developers are ideally not supposed to use. Unfortunately, other platform modules still need to use them, and encapsulating these internal packages prevents access to those platform modules too! Thus, you'll see a lot of these qualified exports where the internal APIs are exported just to the platform modules that need them. You can run the java -d command on java.base to see many instances of these:

$ java -d java.base 
  module java.base@9
  ... 
  exports jdk.internal.ref to java.desktop, javafx.media 
  exports jdk.internal.math to java.desktop 
  exports sun.net.ext to jdk.net 
  exports jdk.internal.loader to java.desktop, java.logging,
java.instrument, jdk.jlink

Remember that using qualified exports is generally not recommended. The principles of modularity recommend that a module should not be aware of who the consumers are. Qualified exports, by definition, add a certain level of coupling between two modules. The coupling is not forced--if you have a qualified export to a certain module, and that module isn't even in the module path to take advantage of it, there are no errors. But the coupling is there nevertheless, and so it's not a good idea to use qualified exports unless it's absolutely required.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.222.184.200