Sponsored Link •
Ken Arnold, the original lead architect of JavaSpaces, talks with Bill Venners about whether to prohibit subclassing, whether to use
Cloneableor copy constructors, and when to use marker interfaces.
Ken Arnold has done a lot of design in his day. While at Sun
Microsystems, Arnold was one of the original architects of Jini
technology and the original lead architect of JavaSpaces. Prior
to joining Sun, Arnold participated in the original
Hewlett-Packard architectural team that designed CORBA. While at
University of California, Berkeley, he created the Curses library
for terminal-independent, screen-oriented programs. In Part I of this interview, which has been
published in six weekly installments, Arnold explains why there's
no such thing as a perfect design, suggests questions you should
ask yourself when you design, and proposes the radical notion
that programmers are people. In Part II,
Arnold discusses the role of taste and arrogance in design, the
value of other people's problems, and the virtue of simplicity.
In Part III, Arnold addresses the
concerns of distributed systems design, including the need to
expect failure, avoid state, and plan for recovery. In Part IV, Arnold describes the basic idea of a
JavaSpace, explains why fields in entries are public, why entries
are passive, and how decoupling leads to reliability. In Part V, Arnold discusses the data-driven
nature of JavaSpaces, how JavaSpaces lets you "throw in a grain
and watch it grow," and why iteration isn't supported in the
JavaSpace interface. In this sixth and final
installment, Arnold discusses whether to prohibit subclassing,
whether to use
Cloneable or copy constructors, and
when to use marker interfaces.
Bill Venners: The book The Java Programming Language, which you coauthored with James Gosling, contains this paragraph:
Marking a method or class
finalis a serious restriction on the use of the class. If you make a method
final, you should really intend that its behavior be completely fixed. You restrict the flexibility of your class for other programmers who might want to use it as a basis to add functionality to their code. Marking an entire class
finalprevents anyone else from extending your class, limiting its usefulness to others. If you make anything
final, be sure you want to create these restrictions.
By contrast, in his book Effective Java, Josh
Bloch suggests we should design and document classes for
inheritance or prohibit inheritance, either by providing no
accessible constructor or marking the class
What is your opinion?
Ken Arnold: My take is rather different.
final is a radical thing to do.
Marking a method
final asserts that changing the
method in a subclass is improper. Marking the whole class
final asserts that even having a subclass is
improper, because you made it impossible.
For example, an instance that many people bump into in Java is
final. I know many people who would have no problem
if all of
String's methods were
but they want to add some methods in a subclass that are relevant
Strings in their environment.
However, they can't extend the notion of
that way because
String is final, and that
frustrates them. I think that is a fair frustration.
You should mark things
final only if you have a
good reason. If you mark three classes
final a year,
you might be doing too much. For the most part, you don't know
enough about the future to know if proper and legitimate
Now you may more commonly mark methods
String's case, you may not want certain methods
overridden under any circumstances because, let's say, security
code depends on
String comparison to operate in a
certain way. You want the
equals method to always
means exactly that; therefore, you make that method
final so it can't be overridden and changed. Marking
final is still rare, but more likely than
marking entire classes
On the other hand, many classes in the Java language core, especially among the older classes, have all sorts of protected data members. But nobody sat down and asked: If I were to subclass this, what would I need to do? My personal feeling is that the only difference between protected and public is a question of target audience. Protected is a variant of public, because anybody can access a protected member if he or she wants to, without much difficulty.
Something protected is designed for a certain set of people, for whom it is public. So I never make protected data members, for the same reason I never make public data members. The same rule applies: You don't want to expose your internals. You ask yourself: What does a subclasser need special access to? You wouldn't give that special access to other people, but subclassers should have it. And you want to design protected members carefully, because just like any public exposure, protected exposure limits you in the future. It is a contract. You can't change it tomorrow, because people won't accept it; in principle, you are wrong to change it. So I am against making things protected just because subclasses might want them. You should design your protected interface as carefully as you design your public interface. You ask the same questions: If I am a subclasser, what kinds of things will I want to affect? What kinds of things shouldn't I touch?
You can assume that people creating subclasses will invest
more in understanding the superclass than those just using the
public interface. So you can expose sharp edges in the protected
interface you wouldn't want to expose in a public interface --
but you have to design it. You should not mark anything protected
unless you design the system to be subclassed. But I wouldn't say
everything should be
final, unless you know it can
be subclassed. That is like saying the only things you can do are
the things we tell you you can do. How do you know that
much about the world? Where did you get the ability to predict
other people's needs?
Bill Venners: In
Effective Java, Bloch offered a good example in which the
addAll method in class
add on itself. A subclass attempts to track the
total number of objects added to the set by counting the objects
passed to both
add, but it
doesn't work as expected because of
self-use. If someone passes three objects to
the total number of objects increments by six, not three, because
add three times.
Bloch suggests that the
addAll method should
document its use of the
add method. Therefore,
designing for inheritance presents the problem: if I document a
method's self-use, then that method must always use itself in
that way. Documenting self-use restricts future implementation
Ken Arnold: You can say the method has the option to do something without saying it will definitely do it, in some cases. But in other cases, you want to say: When I need a certain piece of internal data, I will call this method to get it. You want to document that, because you want subclasses to override the method and return a subtype of what you expect. That method's purpose is enabling subclasses to return subtypes. But, again, that is a matter of protected methods.
You certainly don't want to do things that set up sharp edges
for subclassers. Yes, you can set things up so that if someone
overrides something, an unexpected event occurs. Say you are the
designer and implementer of a class, and at some point in the
code you call the method
foo. If you are
unpleasantly surprised when a subclass overrides
and does something unexpected, it seems to me there are two
The first possibility is that the person who subclassed
foo didn't obey
Instead, he overrode
foo and did something wrong; in
which case, that is the subclasser's problem. He shouldn't
equals to return true only if the square
root of one is the same as the other. That is just not what the
equals method means. You can't prevent people from
making that kind of mistake, so it is not your fault.
The other possibility is that your use of
isn't properly defined in its contract. You might be using
foo in a way that relies on something about your
internal details that you don't describe, so other people
interpret it incorrectly. In principle, I can see that kind of
problem happening. But in either case, one party or the other has
done something wrong.
This will also happen when methods are called during the construction phase. From the constructor, you are in a state where various things haven't been initialized. So you probably have to note which methods the class calls on itself during construction, because the methods have to be prepared for the list never having been created as opposed to having assumed it was created.
Bill Venners: Or as you said, saying that these methods may be called by the constructor, so subclassers need to make sure calling the methods during construction will work.
Ken Arnold: Or the constructor needs to make sure that the normal preconditions for calling are, in fact, set up. Although you can't do that with the subclass's own data, which is where things get really complex.
Bill Venners: In The Java
Programming Language, you discuss
suggest how to use it. You have said to me previously that you
clone is broken.
Ken Arnold: I think
clone is a
Bill Venners: But given
clone's current state, how would you recommend
people use it? What do you think of Bloch's suggestion in
Effective Java to create copy constructors rather than
Ken Arnold: The problem with a copy
constructor is that you have to know the type of thing you're
cloning or copying. You have to make a
Foo given an
Foo that is passed into the copy constructor.
But if the object the client holds is not a
Foo subtype, then you could end up doing type
truncation, which you don't want to do.
Bill Venners: What do you mean by type truncation?
Ken Arnold: Suppose you have a
Foo, which has a subclass
Bar. If you
Foo to clone itself and you have a
Foo will do all the
Bar stuff. But if you use this copy constructor
mechanism and you tell
Foo to create a new
Foo and pass in the old
Bar, you will
get a new
Foo instead of a
lose the bottom of the type.
Bill Venners: You're fubar.
Ken Arnold: You're definitely fubar. So using a copy constructor implies a mechanism where you ask the passed object its type. You get its class object, and invoke its copy constructor by reflection. When you do that, you are way into ugliness.
If I were to be God at this point, and many people are
probably glad I am not, I would say deprecate
Cloneable and have a
Cloneable has problems. Besides the fact that it's
Cloneable doesn't contain the
clone method. That means you can't test if something
is an instance of
Cloneable, cast it to
Cloneable, and invoke
clone. You have
to use reflection again, which is awful. That is only one
problem, but one I'd certainly solve.
Bill Venners: So I'm writing a class today and I want to make it easy to clone or copy. What do I do?
Ken Arnold: If you are writing a class today,
you have only a few reasonable interaction patterns. One is to go
all the way. You implement the
clone method; you
declare it public, without throwing a
CloneNotSupportedException if possible; and you
clone. I say "if possible" because you may
have to be able to throw
clone method of the container classes, for
example, needs to be able to throw
CloneNotSupportedException, because although the
container might be
Cloneable, the objects it
references might not be.
There is also a question of deep versus shallow copies. Do you
want to copy the contents, or just the collection that refers to
the same underlying objects? A shallow copy would give you two
collections, each of which refers to the same underlying objects,
rather than referring to new underlying objects. If your clone is
deep, you might still have to allow
CloneNotSupportedException in your
Another approach, which I don't prefer, is to pretend
Cloneable don't exist.
However, you can't do that in industrial-strength classes. In
industrial-strength classes, you would either make
clone public, without throwing the exception if you
can avoid it. Or you implement
clone so that it will
work if invoked, but you let each subclass decide if it should be
public. Or you override
clone with something that
CloneNotSupportedException; in effect,
stating you cannot clone this even in subclasses. The problem
clone is that nothing can stop a
subclass from making it public and indirectly invoking your
clone method. Then, if your
method is inherited from
Object -- in other words,
if you have never written one -- and
clone implementation does the wrong thing, the user
will have a corrupt class. It is better to notify users of that
CloneNotSupportedException. So in
industrial-strength classes, I would pick one of these
approaches. There is also this weird, almost surreal, approach
where you can implement
Cloneable and not make your
clone method public, but I think you should never do
Bill Venners: So what about copy constructors? Do they have a time and place?
Ken Arnold: I am not fond of copy constructors. In fact, I'm not very fond of constructors at all. The problem is that the code that creates the object with a constructor is defining the object's type. In all other operations, the code that uses an object effectively only defines that the object is at least a certain type. Constructors are an exception to that rule. I don't think that exception should exist.
You can also think of it this way:
new Foo should
turn into an invocation of a static method that might create a
subclass. The static method could look at the parameters and say:
I will create a
Foo subclass that is efficient for
these kinds of parameters. At the point where you want a
Foo, there are myriad reasons why the implementer of
Foo may know you want a subclass, but you don't know
it. And maybe you shouldn't know it, because next week a
different set of decisions might make sense. The actual class of
object that gets created is an implementation detail. You need
something that is at least a
Foo. You should go to
Foo class and say: I need something that is at
least what you are, and here are the initialization parameters.
Other languages will do that; of all things, Perl objects do
that. I think this is a better solution. By calling a copy
Foo, you are asking the
Foo class to get a copy of this object that is at
Foo, which is not the
Bill Venners: When are marker interfaces -- interfaces that lack methods -- appropriate?
Ken Arnold: I have this weird deep object view of that whole thing. I mentioned the term contract a couple times in this discussion. To me, object design is a design of contracts. Contracts define what you are allowed to rely on. Contracts are not expressible in anything less than a human language. You need to have someone sit down and say, if this is true then that happens. You have to spell out all the details.
Java has types as well as methods. There are contracts for
entire types and contracts for methods. If you look at a method
contract, you'll see that not everything can be spelled out in
the programming language. Some things expressed in the contract
must be expressed in human language.
equalIgnoreCase is a method, but nothing in the
programming language enforces that case is ignored; that
requirement is only expressed in the human language text. The
contract for the whole class or interface, then, says if you
implement this interface, you will have, in general, the
following kinds of behaviors.
To me, a marker interface isn't wrong. It is a general case in the sense that it has no methods, so the whole contract is associated with the class and says what things of that type do. As a degenerate case, a marker interface probably shouldn't occur often. But on the other hand, there are times when a marker interface is a legitimate way to express that if you implement this interface, then you are have certain relationships.
Serializable might be a good example. None of the
methods related to serialization belong as methods in the
Serializable interface itself. For example,
readObject should not be a public method in the
Serializable interface. It is an implementation
detail. At the same time,
Serializable is a
behavioral contract that says you do expect your state shipped up
in little bits and then those bits deconstructed into another
copy of you.
Serializable says that you have written
the associated code to make sure serialization works right. So
now I know I can use the object in a certain way--I can
serialize and deserialize it. That is a real contract about an
object that has a
Serializable marker interface. I
do think marker interfaces are rarely correct. But I don't think
they are incorrect in principle.
I know people who are profligate with marker interfaces. They say: If the object you pass in has this marker interface, I will treat it in the following way. But I think that down that path lies darkness. Nobody knows the behavior of objects when they get their 17th marker interface. What if the object implements this marker interface and not this one, or this one and that one but not that other one? You'll send an object to me and I'll invoke a method, but because of some odd marker interface behavior, it won't do what I expect.
Nevertheless, contracts do exist where everything is outside the programming language. In those situations, you still want to let people know that the object behaves in a certain way. Marker interfaces let you do that.
Perfection and Simplicity, A Conversation with Ken
Arnold, Part I:
Taste and Aesthetics, A Conversation with Ken Arnold,
Designing Distributed Systems, A Conversation with
Ken Arnold, Part III:
Sway with JavaSpaces, A Conversation with Ken Arnold,
JavaSpaces: Data, Decoupling, and Iteration, A
Conversation with Ken Arnold, Part V:
You can obtain information about Linda from here:
Ken Arnold first mentioned idempotency in Part III of this
JavaSpaces: Principles, Patterns, and Practice by
Eric Freeman, Susanne Hupfer, and Ken Arnold, the book from which
Bill Venners reads quotes in this article, is at Amazon.com
The Jini Community, the central site for signers of the Jini
Sun Community Source License to interact:
Download JavaSpaces from:
Design objects for people, not for computers:
Make Room for JavaSpaces, Part I - An introduction to
JavaSpaces, a simple and powerful distributed programming
Make Room for JavaSpaces, Part II - Build a compute server
with JavaSpaces, Jini's coordination service:
Make Room for JavaSpaces, Part III - Coordinate your Jini
applications with JavaSpaces:
Make Room for JavaSpaces, Part IV - Explore Jini transactions
Make Room for JavaSpaces, Part V - Make your compute server
robust and scalable with Jini and JavaSpaces:
Make Room for JavaSpaces, Part VI - Build and use distributed
data structures in your JavaSpaces programs: