Abstract: No, this is not our "final" newsletter, but rather a newsletter about the "final" keyword.
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. 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.
javaspecialists.teachable.com: Please visit our new self-study course catalog to see how you can upskill your Java knowledge.
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?
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:
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 explicitly 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.
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.
We are always happy to receive comments from our readers. Feel free to send me a comment via email or discuss the newsletter in our JavaSpecialists Slack Channel (Get an invite here)
We deliver relevant courses, by top Java developers to produce more resourceful and efficient programmers within their organisations.