|
The Java Specialists' Newsletter
Issue 025 2001-07-12
Category:
Language
Java version: Final Newsletterby Dr. Heinz M. Kabutz
Welcome to the 25th issue of "The Java(tm) Specialists'
Newsletter". I hope that for at least *some* of you, your heart
sank when you saw the title of this newsletter. No, your mailbox
is not getting lighter, I just thought I'd write a bit about how
I use the "final" keyword. Incidentally, on Monday we broke
through the 1000th reader barrier (on an upward trend) so thanks
to all of you who promoted this newsletter and sent it to friends
and colleagues. Please remember to forward this newsletter to as
many Java enthusiasts as you know who might be interested in
receiving such a newsletter.
The last few newsletters where quite heavy, so this week I would
like to look at style, specifically on uses of the "final"
keyword. It is not a newsletter on how to abuse the "final"
keyword, which might surprise some of the more loyal readers of
this newsletter.
I would like to thank Carl Smoritcz from Germany for hosting an
archive of my newsletters at http://www.smotricz.com/kabutz.
Please let me know if you would like to include an archive on
your website. I am currently in discussion with a designer to
put together a website for my company, which will include the
newsletters and information about the type of work my company
does.
Thanks for reading this newsletter on our website. We also have a mailing list. That is where the real action takes place (webinars, free reports, etc.). Maybe subscribe today?
Advanced Java Courses on Crete:Java Specialists Master Course 18-21 June 2013 and
Concurrency Specialists Course 6-9 August 2013.
Final
The keyword "final" in Java is used in different ways depending
on the context. We can have final methods, final classes, final
data members, final local variables and final parameters. A final
class implicitely has all the methods as final, but not
necessarily the data members. A final class may not be extended,
neither may a final method be overridden.
Final primitive data members cannot be changed once they are
assigned, neither may final object handle data members (Vector,
String, JFrame, etc.) be reassigned to new instances, but if they
are mutable (meaning they've got methods that allow us to change
their state), their contents may be changed. Since String is
immutable, once a handle to it is final, we *could* consider it
as a constant, if we ignore the effects of newsletter 14. So how
do we use this construct in the real world?
Final Methods
I personally try to avoid making a method final, unless there is
a very good reason for it to be final. There are typically two
reasons to make a method final, performance and design. Let's
look at performance first:
When a method is final, it may be inlined. Before HotSpot
compiling (JDK 1.1.x), these methods were usually inlined at
compile time, whereas with HotSpot they are inlined at runtime,
unless the compiler can guarantee that the inlined method will
always be compiled together with the code that uses it.
// somebody else's class
public class A {
public static final void f() {
System.out.println("A's f()");
}
}
// our class
public class B {
public void g() {
A.f();
}
}
In the past, at compile time our class would be turned into:
// compiled class
public class B {
public void g() {
System.out.println("A's f()");
}
}
The effect of this was that we had to make one less method call,
and since method calls produce extra overhead, we saved some
clock cycles. The disadvantage of this, made clear to me by an
old (ok, experienced) COBOL programmer during one of my courses,
was that whenever somebody else's class changed we would have to
remember to recompile our class!
In JDK 1.[234].x with HotSpot(tm), Sun changed this so that the
methods were no longer inlined at compile time, but rather by the
HotSpot compiler at run time, IFF the performance measurements
suggested that it would improve the overall performance of our
code.
There are quite a few factors which will affect whether a method
will be inlined or not, and we cannot assume that just because
we make something final that it will definitely be inlined. As
we saw in newsletter 21, it is a good idea anyway to always
recompile all your code when you get a new version of someone
else's library, so this is not necessarily a reason to NOT use
final methods.
When you make a method final, no-one else will be able to
override it again. You thus limit extensibility of your code by
choosing to make the method or, even worse, your class final. I
have been utterly frustrated in the past when I wanted to extend
code where the developer had tried to add optimizations in the
form of final. I thus never make a method or class final unless
I specifically want to stop do others from overriding them.
So, when do I use final methods? If I have performance values
that prove that final makes a difference then I would consider
using it for performance reasons, otherwise I would only ever use
it for design reasons.
This is all old hat for you I'm sure, so let's look at final
data members:
Final data members
One of the difficulties in programming is coming up with good
names for "things". (there, that just proves my point, doesn't
it?) I remember an experienced (ok, old) C programmer who was
programming in Java and decided to use very long names for
everything, for example:
public class SessionConnectorWithRetryAtLeastThreeTimes {
private String connectionNameReceivedFromInternet;
private int numberOfTimesThatWeShouldRetryAtLeast;
}
Alright, I'm exaggerating a little bit, but I hope you get the
idea. The beauty of good names is that comments become very easy
to write, sometimes even partly redundant. In Java, we can then
write a constructor that takes the state for the object and
assigns the correct data members. For example:
public class SessionConnectorWithRetryAtLeastThreeTimes {
private String connectionNameReceivedFromInternet;
private int numberOfTimesThatWeShouldRetryAtLeast;
public SessionConnectorWithRetryAtLeastThreeTimes(
String c, int n) {
connectionNameReceivedFromInternet = c;
numberOfTimesThatWeShouldRetryAtLeast = n;
}
}
The problem with our constructor is that we have to explain in
our documentation what c and n represent. It would be much
better to use the same names in the parameters of the constructor
as we use for the data members, as it reduces the confusion. The
standard way in Java of solving this problem is to use the same
names for the parameters as we do for the data members and then
to explicitely specify what we are referring to, using the "this"
keyword.
public class SessionConnectorWithRetryAtLeastThreeTimes {
private String connectionNameReceivedFromInternet;
private int numberOfTimesThatWeShouldRetryAtLeast;
public SessionConnectorWithRetryAtLeastThreeTimes(
String connectionNameReoeivedFromInternet,
int numberOfTimesThatWeShouldRetryAtLeast) {
this.connectionNameReceivedFromInternet =
connectionNameReceivedFromInternet;
this.numberOfTimesThatWeShouldRetryAtLeast =
numberOfTimesThatWeShouldRetryAtLeast;
}
}
The above code will compile and run, but not correctly. Take a
few minutes to figure out what could possible be wrong with it...
.
.
.
.
I hope you didn't find the mistake. The first parameter of the
constructor is spelt differently to the data member, thanks to a
simple spelling mistake. When we thus say
this.connectionNameReceivedFromInternet =
connectionNameReceivedFromInternet;
both the names refer to the data member, so the data member
will always null!
This is the reason why some companies try to pursuade their staff
to augment their data members with strange characters (m_ or _)
to differentiate them from parameters, rather than use the "this"
trick. I know of at least two companies where such coding
standards are used. The effect is that either the data members
look ugly, or the parameters look ugly.
A simple way of preventing such mistakes, besides learning to
touch type 100% correctly, is to make the data members final,
where possible. When we do that, the code below will no longer
compile, and we can find our mistakes much easier.
public class SessionConnectorWithRetryAtLeastThreeTimes {
private final String connectionNameReceivedFromInternet;
private final int numberOfTimesThatWeShouldRetryAtLeast;
public SessionConnectorWithRetryAtLeastThreeTimes(
String connectionNameReoeivedFromInternet,
int numberOfTimesThatWeShouldRetryAtLeast) {
this.connectionNameReceivedFromInternet =
connectionNameReceivedFromInternet;
this.numberOfTimesThatWeShouldRetryAtLeast =
numberOfTimesThatWeShouldRetryAtLeast;
}
}
As a matter or habit, I make all data members final wherever that
is possible. Mistakes that would have taken me days to find now
pop out at the next compile.
Final local variables
There are two reasons I know for making a local variable or a
parameter final. The first reason is that you don't want your
code changing the local variable or parameter. It is considered
by many to be bad style to change a parameter inside a method as
it makes the code unclear. As a habit, some programmers make all
their parameters "final" to prevent themselves from changing them.
I don't do that, since I find it makes my method signature a bit
ugly.
The second reason comes in when we want to access a local
variable or parameter from within an inner class. This is the
actual reason, as far as I know, that final local variables and
parameters were introduced into the Java language in JDK 1.1.
public class Access1 {
public void f() {
final int i = 3;
Runnable runnable = new Runnable() {
public void run() {
System.out.println(i);
}
};
}
}
Inside the run() method we can only access i if we make it final
in the outer class. To understand the reasoning, we have to look
at what the compiler does. It produces two files, Access1.class
and Access1$1.class. When we decompile them with JAD, we get:
public class Access1 {
public Access1() {}
public void f() {
Access1$1 access1$1 = new Access1$1(this);
}
}
and
class Access1$1 implements Runnable {
Access1$1(Access1 access1) {
this$0 = access1;
}
public void run() {
System.out.println(3);
}
private final Access1 this$0;
}
Since the value of i is final, the compiler can "inline" it into
the inner class. It perturbed me that the local variables had to
be final to be accessed by the inner class until I saw the above.
When the value of the local variable can change for different
instances of the inner class, the compiler adds it as a data
member of the inner class and lets it be initialised in the
constructor. The underlying reason behind this is that Java does
not have pointers, the way that C has.
Consider the following class:
public class Access2 {
public void f() {
for (int i=0; i<10; i++) {
final int value = i;
Runnable runnable = new Runnable() {
public void run() {
System.out.println(value);
}
};
}
}
}
The problem here is that we have to make a new local data member
each time we go through the for loop, so a thought I had today
while coding, was to change the above code to the following:
public class Access3 {
public void f() {
Runnable[] runners = new Runnable[10];
for (final int[] i={0}; i[0]<runners.length; i[0]++) {
runners[i[0]] = new Runnable() {
private int counter = i[0];
public void run() {
System.out.println(counter);
}
};
}
for (int i=0; i<runners.length; i++)
runners[i].run();
}
public static void main(String[] args) {
new Access3().f();
}
}
We now don't have to declare an additional final local variable.
In fact, is it not perhaps true that int[] i is like a common C
pointer to an int? It took me 4 years to see this, but I'd like
to hear from you if you have heard this idea somewhere else.
I always appreciate any feedback, both positive and negative, so
please keep sending your ideas and suggestions. Please also
remember to take the time to send this newsletter to others who
are interested in Java.
Heinz
Errata
In newsletter 23, I relied on hearsay for some information without
checking it out myself. Yes, the amount of memory used by each
Thread for its stack was incorrect. According to new measurements,
it seems that each stack takes up approximately 20KB, not the 2MB stated in the original newsletter,
so for 10000
threads we will need about 200MB. Most operating systems cannot
handle that many threads very well anyway, so we want to avoid
creating that many. Thanks to Josh Rehman for pointing out this
mistake.
It seems that with JDK 1.1.8 each thread takes up 145KB, I'm not
sure whether the stacks grow dynamically. Under the JDK 1.2.2
that comes with JBuilder 3, the stacks grow, so I managed to have
100 threads take up roughly 100 MB of memory, or 1MB each. After
1 MB is used up, the VM mysteriously returns without any message
so I had to experiment a bit to get this information. Under JDK
1.3.0 I got DrWatsons when I tried to make the stack of each
thread grow to any real size. It is unrealistic to expect our
program to have stack depths of 10000 method calls, so we could
probably quite safely use 50KB as a realistic stack size for each
thread.
Language Articles
Related Java Course
Discuss at The Java Specialist Club
|