|
The Java Specialists' Newsletter
Issue 021 2001-05-31
Category:
Language
Java version: Non-virtual Methods in Javaby Dr. Heinz M. Kabutz
Welcome to the 21st issue of "The Java(tm) Specialists'
Newsletter". In South Africa the age of adulthood is 21, so I
hereby declare this newsletter to be "grown up". No more
childish jokes, running in the streets or teasing Sun for writing
such a stupid language ;-)
This newsletter serves to illustrate why you should always
recompile ALL your code when you get a new build of someone's
library. It also, as a side issue, demonstrates how you can
write non-virtual methods in Java.
Before I get started, a word of thanks to Dr. Jung for giving me
this idea on Monday. I persuaded him to promise me another of
his excellent newsletters, which should have been due at least
this week, but he managed to deflect my attention with an idea
that led to this newsletter.
Please take a few minutes to think of who you know that would be
interested in receiving this newsletter and forward it to them.
Non-virtual Methods in Java
In C++ you can mark a method to be virtual", which tells the
compiler that you will want to use the most derived method in the
object hierarchy. Virtual therefore means that if you have a
class A with method f() and a subclass B with the method f(), and
you call the method f() on a handle of A pointing to a B, then
B's f() gets called. If you left out the "virtual" keyword, it
would cause A's f() to get called, i.e. it is bound at compile
time, rather than runtime.
In Java, on the other hand, ALL methods are virtual, i.e. the
most derived method is always called, unless of course (read on).
During many of the Java courses I presented, I was faced with the
question from hardened criminals (oh no, I meant C++ programmers)
of why Java does not support non-virtual methods (these questions
usually get asked by the same guys who ask why Java doesn't
support multiple implementation inheritance and operator
overloading *groan*).
Let's look at some code:
//: A.java
public class A {
public void f() { System.out.println("A's f()"); }
}
//: B.java
public class B extends A {
public void f() { System.out.println("B's f()"); }
}
//: C.java
public class C {
public static void main(String[] args) {
A a = new A();
a.f();
((A)new B()).f();
B b = new B();
b.f();
}
}
When we run this, we get the result of:
A's f()
B's f()
B's f()
A typical question for the Sun Certified Java Programmer exam,
and quite obvious to most of us.
The question is, if I have an object of instance B, is it
possible to call its parent's f()? Consider class D:
//: D.java
import java.lang.reflect.*;
public class D {
public static void main(String[] args) throws Exception {
Method f = A.class.getDeclaredMethod("f", new Class[0]);
f.invoke(new A(), new Object[0]);
f.invoke((A)new B(), new Object[0]);
f.invoke(new B(), new Object[0]);
}
}
We are calling f() of class A, but still, the output remains:
A's f()
B's f()
B's f()
A few months ago, I was asked about this and after battling for
a while, gave up and declared that it is not possible to run
class C and get the output of:
A's f()
A's f()
B's f()
Now for a little trick that changes all that, we simply make f()
private in A and recompile ONLY A:
//: A.java
public class A {
private void f() { System.out.println("A's f()"); }
}
We have not recompiled B, C or D, so when we run C, we expect to
get some warning or error, but alas, our output for C becomes:
A's f()
A's f()
B's f()
I tried this out using the JDK 1.3.0_01 and JBuilder 3.0 JDK 1.2
and it worked without problems. Please send me a note if you find
a version of Java that somehow gives back a VerifyError or an
AccessException.
Our class D does not work, but gives us a runtime error, because
A.f() is now private. This is the first time where I've seen
reflection resulting in safer code!
When should you use this idea? Please don't, but please at the
same time be aware that if you get a new library, even if it's
the same version number, you have to recompile every line of your
Java code, just to be sure.
A better way of achieving the same goal of non-virtual methods is
to use static methods which we then pass a handle to an instance
of the class. This much clearer approach was suggested by a
founding member of The Contractor's Guild.
//: A.java
public class A {
public static void f(A a) { System.out.println("A's f()"); }
}
//: B.java
public class B extends A {
public static void f(B b) { System.out.println("B's f()"); }
}
//: C.java
public class C {
public static void main(String[] args) {
A a = new A();
a.f(a);
a = new B();
a.f(a);
B b = new B();
b.f(b);
}
}
In summary, always remember to recompile all your classes, since
you don't know which ones will be affected by someone else's
change!
---
Warning Advanced:
In JDK 1.1.x, final methods were inlined at compile-time, further
necessitating a complete compile if a library was changed.
---
Thanks for taking the time to read this newsletter, please also
take the time to give me feedback, I always appreciate it.
Heinz
Language Articles
Related Java Course
Discuss at The Java Specialist Club
|