All entries for Monday 06 February 2006
February 06, 2006
Java Pet Peeves #3:Lack of use of the final keyword
Seeing as how I am on a roll ;)
The use of the final keyword is so important within Java; I fully believe that the function of the code should be readable without comments, even if the reason for the code needs commenting (this will be explained in another Pet Peeve ;)), and appropriate use of the "final" keyword is another way of documenting your code. The side effect of allowing the JDK to help you, is of course a bonus :) :)
Objects; how are they passed?
The "final" keyword in java doesn't mean (as some think) "do not change the state of this object", it means "do not change the variable containing the reference to this object". This is a subtle, but extremely important distinction. Generally speaking, objects can be passed in one of two ways, by value, or by reference. By value means a copy of the object is passed, leaving the original intact. By reference means a pointer to the same object is passed. Java manages to combine the two by passing a copy of the reference. For example:
public class ClassA {
public void doSomething(List myList) {
}
public static void main(String args[]) {
List myList = new ArrayList();
doSomething(l);
}
}
When doSomething() is called with a reference to myList the JDK will provide a copy of the reference to myList. This means that the doSomething method can manipulate myList, but it cannot reassign the reference to myList:
public class ClassA {
public void doSomething(List myList) {
myList = new ArrayList();
myList.add("hi");
System.out.println("doSomething contains " + myList.size() + " elements");
}
public static void main(String args[]) {
List myList = new ArrayList();
doSomething(l);
System.out.println("MyList still points to the same list as before, with " + myList.size() + " elements");
}
}
will result in the following statements:
doSomething contains 1 elements
MyList still points to the same list as before, with 0 elements
This is because the myList variable that doSomething method is modifying is a copy of the myList variable in the main method. Hence; Java passed objects by a copy of the reference!
The two copies of myList reference the same bit of memory though, so:
public class ClassA {
public void doSomething(List myList) {
myList.add("hi");
System.out.println("doSomething contains " + myList.size() + " elements");
}
public static void main(String args[]) {
List myList = new ArrayList();
doSomething(l);
System.out.println("MyList still points to the same list as before, with " + myList.size() + " elements");
}
}
will result in the following statements:
doSomething contains 1 elements
MyList still points to the same list as before, with 1 elements
So if you wanted to prevent access to myList, how would you do it? In short; you can't. Making the method parameter final prevents you modifying the the reference, not the object the reference is to:
public class ClassA {
public void doSomething(final List myList) {
myList.add("hi"); // I can still modify myList
myList = new ArrayList(); // won't compile. Cannot change where myList points to
}
public static void main(String args[]) {
List myList = new ArrayList();
doSomething(l);
System.out.println("MyList still points to the same list as before, with " + myList.size() + " elements");
}
}
Final; so what does it do?
What does making a method or a class final do? It prevents you extending the class:
public class ClassCanBeExtended {
public void methodWhichCanBeExtended() {
}
public final void methodCannotBeExtended() {
}
}
public final class ClassCannotBeExtended {
}
results the following scenarios:
public class ExtendingClass extends ClassCanBeExtended { // fine.
public void methodWhichCanBeExtended() { // fine
}
public final void methodCannotBeExtended() { // fails because the super class is final.
}
}
public class WontCompileClass extends ClassCannotBeExtended { // won't compile.
}
Making a static, member, method or local variable final (as discussed) prevents you assigning more than one copy to it.
So why use it?
The reason you would use the final depends upon where you would use it:
[classes, methods]
Use it to prevent others from changing the behaviour of your code.
[member variables]
Use it to indicate that the class requires access to a collaborator and the class cannot handle changing that collaborator once it has been instantiated:
public class ClassWithCollaborator {
private final Collaborator collaborator;
public ClassWithCollaborator(Collaborator myCollaborator) {
super();
this.collaborator = myCollaborator;
}
public void setCollaborator(final Collaborator a) {
this.collaborator = a; // won't compile!
}
}
[method or local parameters]
Use this if you want to prevent name space clashes:
public void doSomething(final List myList) {
myList = new ArrayList(); // won't compile!
final List anotherList = new ArrayList();
anotherList = new ArrayList(); // won't compile either.
}
So; if you are still reading ;) Please think carefully about:
a) Can your class handle changing the reference to something once it has been initialised? If not, make the initialisation final.
b) Are you trying to prevent access to an object passed into a method? If so, don't use final because it won't do what you think :)
HTH someone ;)
Java Pet Peeves #2:Useless assignments
Boy; am I moaning today :)
So my second pet peeve is unnecessary assignments within Java.
Essentially; all member variables will be assigned to a default value (Object=null, int=0, boolean=false), whilst all local variables will have no initialisation.
Thus, the following two classes will be equivalent:
public class ClassThatUnderstandsHowJavaWorks {
private Object o;
private boolean b;
private int i;
}
public class ClassThatDoesntUnderstandHowJavaWorks {
private Object o = null;
private boolean b = false;
private int i = 0;
}
I have heard the argument that the second form is more explicit. I don't but this because it means every time you write a member variable you need to write extra crud. Every time! It would be far better to educate people once and write neat code, than write bad code over and over.
The other aspect of initialisation is allowing the compiler to help you. For example, if you have a variable which should always be initialised within an if clause, how many people would write:
String message = null;
if (someCondition) {
message = "a message";
} else (someOtherCondition) {
message = "some other message";
}
This code will compile, and work; but will allow the situation for message to be null (if neither condition is used). If the assumption is that one of the two conditions will always be true, the compiler cannot help you.
Remove the initial initialisation and the code won't compile:
String message;
if (someCondition) {
message = "a message";
} else (someOtherCondition) {
message = "some other message";
}
because the compiler will catch the fact that there is a route through your code that will leave message in an uninitialised state. Forcing you to rethink your if condition.
Java Pet Peeves #1:Pointless Constructors
So I was looking through some code (the author shall remain nameless :)) and spotted a shed load of my number #1 pet peeve; useless constructors:
public class Something {
public void aMethod() {
…
}
}
In the above circumstance there is absolutely no point (as far as the JRE is concerned) to put a default constructor. The following will result in exactly the same byte code (I imagine; I cannot verify this :)):
public class Something {
public Something() {
super();
}
public void aMethod() {
…
}
}
or even worse:
public class Something {
public class Something() {
}
public void aMethod() {
…
}
}
Why, oh why must people do it?
Admittedly; stupidly Eclipse provides a default constructor (with the super() call) by default, but that is no excuse.
It is quite simple; if you have no constructors, the JDK will provide a default constructor which contains a call to super() for you. You must provide a constructor if either you do not wish to have a default constructor, or the super class does not include one.
For example, if you have a class which requires a number of collaborators to work, then you should create a constructor which takes in those collaborators. Because you have provided a constructor, the JDK won't put a default one in for you, so:
public class Something {
public Something(final String s) {
}
public void aMethod() {
…
}
}
can be instantiated with:
new Something("ssss"); // good
new Something(); // bad
The JDK cannot create a default constructor if the super class doesn't itself contain a default constructor:
public class BaseClass {
}
public class ExtendedClass extends BaseClass {
// no need to put a constructor
}
public class BaseClass {
public BaseClass() {
// stupid, but used for illustrative purposes.
}
}
public class ExtendedClass extends BaseClass {
// still no need to put a constructor
}
however:
public class BaseClass {
public BaseClass(final String s) {
}
}
public class ExtendedClass extends BaseClass {
// absolutely must put a constructor in, even if it is a no-args constructor
public ExtendedClass() {
super("some value");
}
}
So please; when you create the next class, ask yourself whether it requires any collaborators, if it does, provide a single constructor with those collaborators (which should be marked final BTW :)). If it doesn't, don't provide any constructors; let the JDK do it's job.
And if you must provide a constructor; make sure you call super(...) :)
Please, for my sanity :)