Eric said:
Please explain how the compiler can figure out which class'
method to call in
Number n = (Math.random() < 0.5)
? new Integer(42) : new Double(42.0);
String s = n.toString(); // Integer's toString, or Double's?
The best way to learn is to write programs and examine the results.
Here I created foo.java:
class foo {
public static void main (String[]args) {
Number n = (Math.random() < 0.5)
? new Integer(42) : new Double(42.0);
String s = n.toString();
}
}
Compiled with "javac foo.java", then running "javap -s -c foo"
produces:
Compiled from "foo.java"
class foo extends java.lang.Object{
foo();
Signature: ()V
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>"
)V
4: return
public static void main(java.lang.String[]);
Signature: ([Ljava/lang/String
V
Code:
0: invokestatic #2; //Method java/lang/Math.random
)D
3: ldc2_w #3; //double 0.5d
6: dcmpg
7: ifge 26
10: new #5; //class java/lang/Integer
13: dup
14: bipush 42
16: invokespecial #6; //Method java/lang/Integer."<init>"
I)V
19: invokevirtual #7; //Method java/lang/Integer.intValue
)I
22: i2d
23: goto 39
26: new #8; //class java/lang/Double
29: dup
30: ldc2_w #9; //double 42.0d
33: invokespecial #11; //Method java/lang/Double."<init>"
D)V
36: invokevirtual #12; //Method java/lang/Double.doubleValue
)D
39: invokestatic #13; //Method java/lang/Double.valueOf
D)Ljava/lang/Double;
42: astore_1
43: aload_1
44: invokevirtual #14; //Method java/lang/Object.toString
)Ljava/lang/String;
47: astore_2
48: return
}
===================================================
Even if you don't understand all of the JVM instructions you should be
able to follow this code: call Math.random, compare the result to 0.5,
if less then create a new Integer (lines 10-23), else create a new
Double (lines 26-38). The value is converted to a String in lines 39-47.
It should be clear from line 39 the type of the expression you questioned
is...irrelevant! Note the "i2d" instruction on line 22. The compiler
is pulling the primitive int from the Integer (line 19), converting to a
double (line 22). On the other hand, if a Double is created, its value
is extracted to a primitive (line 36).
Then in either case a third object, a Double, is created from that
double (line 39). However the compiler invokes Object.toString, not
Double.toString (line 44) for reasons that elude me; perhaps an
optimizing compiler would use the more specific method.
When in doubt you can check the JVM specs (2nd ed is available
on-line). Playing around like this is a terrific way to learn.
When calling a static method, what is the `this' object whose
actual nature would determine the method to be called?
The keyword "this" is normally bound to the Object reference the
method is invoked on. But for static methods, "this" keyword isn't
set at all and can't be used.
A mental model can simplify your thinking here (even if it is incorrect).
Every class is represented by an Object, created in a special way by the
ClassLoader, of type java.lang.Class. You can think of any methods
and fields declared as static as methods and fields of that class Object
(again, that's not really correct). Thus, when you create a class Foo
and some Foo objects you also have a Class object named "Foo", where
the system keeps the Foo class static fields (a.k.a. "class variables")
and methods, along with the ability to create objects of type Foo.
So when you invoke a static method the "this" reference, if it existed,
would not refer to any Foo type object but rather to the Class object
named Foo. Thus such a method has access to static fields and other
static methods, but this Class object named Foo doesn't contain any
instance methods or fields. (This mental model is too simple to
explain how it really works but it should help with understanding
this one aspect of static versus instance. Please don't shoot me.)
Consider the method main: when starting the JVM, you specify a
class, not a class and method. The JVM will create the Class
object for the specified class but no other objects. So only
a static method could be invoked at that time, which is why
the main method must be static.
While some languages only have (the equivalent of) instance fields
and methods, Java provides you the choice. The annoying part
is that Java uses the same operator to access static and instance
fields and methods leading to some confusion. No doubt keeping
the number of operators low(er) made the compilers easier to write.
So if you want per-object properties use instance variables ("fields").
If you want single "global" or "shared" properties use static
variables (or fields). If you want polymorphism use instance methods.
If you don't and you don't need access to instance fields, use
static methods.
From your post I'm guessing you question the wisdom of providing
non-polymorphic methods. You are not alone but personally I often
find them useful, especially for collections of "utility" methods
that don't need access to any instance fields. (Take a look at the
utility classes in java.lang and java.util packages. You'll find
most of the methods of these classes are declared static.)
Hope this helps!
-Wayne