In this installment of The Open Road, Elliotte Rusty
Harold looks at how JSR 294 superpackages, intended for inclusion
in Java SE 7, will allow for the creation of hierarchical packages,
allowing you to organize your code more cleanly and
effectively.
First though, here's an update on the status of the OpenJDK
project. The latest JDK 7 release is b24,
released December 4, 2007, which is no different from b23,
except that b24 was built from Mercurial sources.
Now, on to JDK 7 and our first feature preview:
superpackages. Here's Elliotte.
Since Java 1.0, packages have offered a convenient means of
organizing code into different namespaces at the source code level.
Classes in the same package can see non-public, non-protected,
and non-private methods and fields in other classes in the same package
that classes in other packages cannot see.
Unfortunately, there's no standard adjective for members that
have no explicit access specifier. The most common, though by no
means standard, name for these fields and methods seems to be
"default" access. They're also sometimes referred to as "package
protected" or "package private." The Java Language Specification
uses "default (package)," and that is what I'll use in this
article.
How Packages Affect Visibility
For example, consider the three classes in Examples 1, 2, and 3.
A cat can make a dog bark and a dog can make a cat meow, but
neither a cat nor a dog can get the bacon out of the refrigerator.
The refrigerator can make a dog salivate, but it can't make either
animal speak. This is because Cat and Dog
are in the same package as each other but different packages than
Refrigerator, while the speak() and
getBacon() methods have default (package) access.
Example 1. A Dog class in the
com.elharo.animals package
package com.elharo.animals;
public class Dog {
void speak() {
System.out.println("bark");
}
public void salivate() {
System.out.println("drool");
}
}
Example 2. A Cat class also in the
com.elharo.animals package
package com.elharo.animals;
public class Cat {
void speak() {
System.out.println("meow");
}
}
Example 3. A class in a new package
package org.cafeaulait.kitchen;
public class Refrigerator {
String getBacon() {
return "bacon";
}
}
Hierarchical Names, Flat Namespaces
The package naming convention is hierarchical, but the namespace
division isn't. The package namespace structure is flat. A class in
java.util.zip has no more access to the internals of
java.util.HashMap than does a class in
org.apache.xerces. Even if the
Refrigerator class had been in the
com.elharo.animals.appliances subpackage, it still
wouldn't have been able to make the dog bark or the cat meow.
Despite the apparent hierarchy of the packages, there's no actual
hierarchy in the access protection.
Ninety percent or more of the time, this doesn't matter all that much, but
occasionally there are problems. For example, the JDOM XML API put its core model classes
like Element and Attribute in
org.jdom and its parser classes in
org.jdom.input. This meant the builder classes that
read XML documents and created objects from them could only use the
model classes' public API. However, this API duplicated checks the
underlying XML parser had already performed, thus doubling the
workload to build the in-memory representation of a document. The
core model classes could have provided special, non-verifying
methods that did not duplicate the work. However, because the
builder classes were in separate packages, these methods would have
to be public. Then anyone could use them to bypass the
checks, not just the known safe code from the input package.
Another common problem arises when writing unit tests. Test code
often needs to see parts of a class that the general public isn't
allowed to access. Sometimes test classes want to directly test
non-public methods. Other times they want to inject dependencies.
Usually you'd think test code belongs in a separate package.
However, many programmers put their tests in the same package as
the tested code precisely because they want to be able to access
the non-public parts. Alternatively, some programmers make methods
public just so they can be tested, even if that
pollutes the published API. Neither approach feels especially
palatable.
Workarounds
There are two common solutions to these problems in Java 6 and
earlier. The first is to make methods public that
really shouldn't be, and then document them as "non-published" and
"for internal use only." Of course, we know how much programmers
love to read documentation, and no developer anywhere would ever
use a method marked internal, and now that I've moved out of
Brooklyn, I've got a bridge for sale at the end of Flatbush Avenue,
cheap!
The other alternative is to place all related classes in the
same package. That's a little safer, and it's usually what I do--for instance, in my JDOM alternative library XOM, the input and output classes are in
the same package as the core classes precisely so they can see each
others' internal parts--but this loses the benefits of organizing
large code bases hierarchically.
Look! Up in the sky! It's a Bird! It's a Plane! No, It's
Superpackage!
JSR 294
proposes to improve the situation in Java 7 by introducing
superpackages. A superpackage is a new construct that,
like an interface or a class, has its own .java file. This
.java file lives in the usual file system hierarchy and is
always named super-package.java. For example, a superpackage
for com.elharo.pets would be defined by the file named
super-package.java in a com/elharo/pets directory.
This file would contain a list of all the members of the
superpackages and exported classes, as shown in Example 4.
Example 4. A superpackage for com.elharo.pets
superpackage com.elharo.pets {
// member packages
member package com.elharo.pets;
// member superpackages
member superpackage com.elharo.pets.avian, com.elharo.pets.aquatic;
// exported classes
export com.elharo.pets.Dog;
// exported superpackages
export superpackage com.elharo.pets.aquatic;
}
Once the superpackage is created, only exported classes are
visible outside the superpackage. For example, the
com.elharo.pets.Dog class is visible, but the
com.elharo.pets.Cat class is not. Classes in the
com.elharo.pets.aquatic package are visible, but
classes in com.elharo.pets.avian are not.
Inside the superpackage, everything is the same as before.
However, if a class is placed in a superpackage and is accessed
from outside the superpackage, everything changes. By default, all
access from outside the superpackage is blocked. It's as if all the
classes inside the superpackage had suddenly become private. The
superpackage barrier forms an event horizon from which information
can only escape if it's explicitly exported.
Note the difference between how packages are created and how
superpackages are created: packages are created internally when
classes declare themselves to be members of those packages.
Superpackages are created externally in a separate file.
Exporting and Hiding Methods
What has this bought us? Remember the issue is usually that you
have classes and methods you want to expose to other members of the
superpackage but not to the general public. For classes, the
problem is solved: make them public but don't export
them from the superpackage. For methods, the answer is a little
trickier and requires an additional level of indirection. In brief,
you have to tag the methods you want to publish within the
superpackage with default (package) access, just like you do now.
Then you define a new accessor class in the same package
that delegates to the default (package) methods. You make the
methods in this class public, but you don't export the
class from the superpackage.
For example, let's see how we'd use this to provide a test class
the ability to inject a set of mock system properties into a class.
Begin by assuming we have a class set up as in Example 5.
Example 5. A class that loads the system properties
package com.elharo.examples;
import java.util.Properties;
public class Stubborn {
private Properties props = System.getProperties();
// rest of class...
}
You want to test the stubborn class with various values for the
different system properties. However, that's going to be hard to do
because some of these properties, such as java.version
and java.vendor, are fixed by the virtual machine you
test with. They are not under your direct control.
To allow the test case in the
com.elharo.examples.test package to change the system
properties, you have to add an additional setter method for the
properties as shown in Example 6.
Example 6. A class that allows the system properties to be
injected
package com.elharo.examples;
import java.util.Properties;
public class Stubborn {
private Properties props = System.getProperties();
// public dependency injection method
public void setProperties(Properties props) {
this.props = props;
}
// rest of class...
}
Now the test code can inject the test properties directly into
the class. Unfortunately, so can everyone else, and that can be both
a security risk and a source of bugs. It also makes the API
needlessly complex and larger than it should be.
Instead, make the setProperties() method default
(package) as shown in Example 7.
Example 7. A class that allows the system properties to be
injected
package com.elharo.examples;
import java.util.Properties;
public class Stubborn {
private Properties props = System.getProperties();
// no longer public
void setProperties(Properties props) {
this.props = props;
}
// rest of class...
}
Then add a new public class in the same package that can see the
non-public setProperties() method, as shown in Example
8. The test class in the subpackage
com.elharo.examples.test can use this class to create
a Stubborn object with custom configured properties
rather than invoking the constructor directly. Note that this is
not the only way you could arrange this; there are several other
possibilities, but this seemed simplest.
Example 9. A class that allows the system properties to be
injected
package com.elharo.examples;
import java.util.Properties;
public class StubbornBuilder {
public static Stubborn build(Properties props) {
Stubborn s = new Stubborn();
s.setProperties(props);
return s;
}
}
Finally, define the superpackage for
com.elharo.examples as shown in Example 9. Notice that
com.elharo.examples.test is a member, but is not
exported. StubbornBuilder has not been exported
either. This means that to all the classes outside the
superpackage, they effectively don't exist. Presumably, there won't
even be JavaDoc for these classes.
Example 9. A superpackage for com.elharo.examples
superpackage com.elharo.examples {
// member packages
member package com.elharo.examples;
// member superpackages
member superpackage com.elharo.examples.test;
// exported classes
export com.elharo.examples.Stubborn;
}
Voila! We now have a public API that exists only in the
superpackage. The compiler and the virtual machine will enforce
this. This does mean that neither source nor byte code that uses
these features will be processable with Java 6 and earlier tools.
However there are enough other changes coming in Java 7 that this
was likely to be true with or without superpackages.
Summing Up
Superpackages are one of the better ideas being floated for Java
7. They address a real pain point for many developers. However, they
don't introduce a lot of confusing new syntax, and if you don't
need them you can fairly safely ignore them.
The one change I'd really like to see is better support for
working at the method level rather than the class level. I'd like
to be able to tag individual methods as exported or not, rather
than working with entire classes at a time. The spec is still in
early draft review, so time remains for changes to be made and the
syntax to be improved, and I'm told a new access specifier to
accomplish this is indeed under consideration for the next
draft.
Nonetheless, even in its current imperfect state, superpackages
are a significant addition to the Java programmer's toolbox.
Resources
JSR 294:
Improved Modularity Support in the Java Programming Language
Bad idea
2008-06-08 13:10:12 xenomancer
[Reply | View]
I don't see any benefits to using SuperPackages. In fact I will go as far as to say that it seems to break the very paradigm of Object Oriented Programming that Java implements so beautifully.
This has to be the lamest addition since the introduction of the FRIEND keyword in C++.
One of the biggest issues languages face is simplicity vs complexity. This adds unnecessary complexity to resolve an almost phantasmal problem.
We don't need it, is what I guess I'm trying to get at. It ain't broke so don't fix it.
Having worked in a module system with package-level module-membership, I can't say I feel a burning need for class-level membership (much less member). The one argument I've seen is to enable unit-tests to have special access to otherwise non-public class members. There are ways to solve that with package-granularity (some are ugly, but doable if your paranoid). But moreover, while it is somewhat common for external code to call a method such as your setProperties() method above, and that shouldn't be polluting the API, it's much less common for code in the same package in malicious or misbehaving ways, for the social reason it is much more likely to be maintained by the same people (and with the ability to have hidden packages, the probability of having packages with massive numbers of unrelated classes goes down because package-privacy is no longer the only means of hiding implementation). I worry about issues such as the public setProperties() example as well, which can be abused, and avoid this sort of thing. I just don't worry about classes from the same package abusing something package-private that's there for unit tests - it's just not probable enough.
<p/>
I'd say that for sheer reduction of the number of things I need to hold in my mind when developing, I'd much rather just know that the packages I can see are a subset of what exists.
<p/>
I could be grossly misunderstanding things here, and I'm definitely surmising a few things, so feel free to correct me.
<p/>
-Tim
Is this really necessary?
2008-03-24 02:52:20 rasss
[Reply | View]
I'm constantly wondering why this JSR is even needed. OSGi offered these features for a very long time. What further troubles me, the JSR doesn't even mention OSGi (http://jcp.org/en/jsr/detail?id=294). This specification feels a bit like Sun (and the others?) is saying: hey, we don't feel comfortable that we do not have a specification under the JSR umbrella. So, we just reinvent the wheel (or at least part of it, because OSGi is much more than JSR 294) and have complete control over this brand new (inferior) technology. As OSGi successfully demonstrates, this feature does not have to be part of the language. It can be implemented with features available in the Java language for a long time. For instance in the company where I work, it will take a looong time until we even start thinking about Java 7. So JSR 294 will be irrelevant for us.
So, can somebody explain me, why this JSR is needed? Why not just admit that there is an existing solution that can be used today? And please, leave your syntax preferences out of the discussion.
i think hierarchical packages support is *VERY* important.
i don't known well C# and GNU GCC versioning and packaging solutions, but i think Java must handle versioning and hierarchical scoping more seriously ...
The problem should be addressed at different levels: artifact packaging (the shared lib), api declaration (the include dir).
Any solution must not confound two related but different concepts:
free modularization, related to top/down decomposition of the internal implementation of a module (white box)
clear module api declaration (black box) without recurring to facade antipatterns.
Just one point ...
I do not think that the implementation proposed is the best one.
Ok, it lets you to introduce new language keywords in a very "special" places, but maybe a stronger connection with the "__export"-ed point shound be needed (linguistic annotations) ?
Added complexity for little gain.
2008-03-18 02:36:16 mattwest
[Reply | View]
I think you said it yourself:
Ninety percent or more of the time, this doesn't matter all that much, but occasionally there are problems...
I think it might be best to just concentrate on the ninety percent and not waste time on these marginal cases. Years ago I thought, and still think now, that the package naming convention was and is a really good idea. I think this notion might even cause confusion to the majority of people. Please don't implement this.
I really think this is a messy solution. Having to export each class individually from a superpackage is very inelegant.
Ada Hierarchical Units & C++ Friends
2008-03-13 14:08:41 sroberts2005
[Reply | View]
I am happy to see someone finally address this issue - bravo! The idea comes from Ada (as does much of Java) but I still think C++ friends are the most flexible and simple solution to this issue. Just make Java have friends and be done with it, eh?
OSGi detractors
2008-03-11 21:55:33 augusto
[Reply | View]
I like OSGi, but to those saying this is already there, don't you think this is a better way to determine package visibility than basically hacking it in a manifest file?
I hate the manifest file format, you can't even put comments in it and it is picky about freaking spacing. This looks to me to be doing effectively the same, but at least using a format that makes more sense and is not limited by the manifest fie format.
Are we reinventing the #include/header construct?
2008-03-11 06:53:14 ebresie
[Reply | View]
By having a "superpackage" which seems to contain all referenced dependency package, is this not the same thing as having some form of header file which is then "#included" or as many have indicated previously, a manifest of all used dependency packages?
export = public and protected = public?
2008-03-11 06:51:23 ebresie
[Reply | View]
While reviewing this article, I had a basic question...
Why have the "export"?
As I recall my basic java accessor keywords which are either public, protected, private, or <no accessor>.
In the context of superpackages, usage of "export" seems to be the equivalent of having a public method in basic packaging, while the superpackage "public" methods seem to be the equivalent of protected method that is suppose to be acceptable within a package but not outside. Is this kind of repetitive and adding something that is already present?
Close the shop
2008-03-10 22:37:52 paulusbenedictus
[Reply | View]
I think the JSR-294 expert group is wasting its own time, but please stop wasting ours. From what I can gather, it's the re-invention of OSGi with internal Java constructs. Is Java 7 going to the mess of messes? BGGA closures, OSGI-exports-but-not-OSGI oh my!
The way OSGi solves the same problem (see JSR-291 and e. g. Eclipse Equinox for a working implementation) is IMHO a much better solution.
A waste of time!
2008-03-09 21:47:25 diegof79
[Reply | View]
What's the problem that "superpackages" tries to solve?
Is very easy to "hide" private packages: just tell in the documentation that they are not intended to be use directly (for example com.sun.* packages in the JDK).
Developers are not stupid.
Superpackages adds an unnecessary complexity, because now you have to maintain:
- the XML for the build (maven, ant or whatever)
- the XMLs of your favorite framework
- the annotations of another framework
- the specific deployment descriptor for your application server
- ... and now the superpackages files!!
(plus add the complexity to the tools: the IDE now have to assist you with the "visibility" of classes in superpackages)
This is a nightmare!
Instead of "superpackages", what Java needs is the concept of "libraries". (like "libraries" in eclipse).
Now if you want to use a framework that has many .jars you have to worry to put each one (with the correct version) in the classpath... is a headache.
People at Sun and the OpenJDK: "Ponganse media pila!"
Inspired by Eclipse?
2008-03-07 22:36:07 paawak
[Reply | View]
This was in eclipse long back... you can do this stuff in the MANIFEST.MF file. Anyway, this is a long needed useful feature.
No hurry
2008-03-07 16:12:38 eskatos
[Reply | View]
I hope Java will take the time to find a really powerful and clever solution before developpers pay the price.
This article made me feel like reading a trial and error story. I don't know if it's good or if it's bad, just that it has been designed, reflected.
I hope this JSR will produce something pragmatic. I think that language changes need a lot of successfull complicated examples.
This allows tests to access default package level methods and/or fields but keeps them out of the way of the main classes. Tests should be seperate from the classes their testing but the package namespace is not where they should be seperated.
So I assume that there is no way 3rd party lib could create a jar with a file java/lang/super-package.java
and export the no copy string constructor?
Why not annotations
2008-03-06 10:30:13 lstroud
[Reply | View]
I haven't read the spec. So, this may be in there. However, this seems like it would be far better if done with annotations. Just add an @superpackage(name="blah", export="true") to the top of a class. It is not quite as centralized, but it would be easier to to maintain. You could then extend that to the method level by allowing methods to be exported or not. Am I missing something?
method sharing too clunky
2008-03-06 09:58:28 dog
[Reply | View]
It would be much better if all default access methods would just be shared in the superpackage by default.. after all isn't that the main objective here?
Another way for libraries to have more rights than end developers. I'm not sure that is nice. I already have problems in principle with the protected keyword, that encourages subclassing instead of encapsulation. This has the potential to be a lot worse... Only the "approved" classes can use the fast way, for example in the document class, the insert(ElementSpec[] a) method, that is currently protected (and that pisses me off also) could be written as private.