overloaded methods

R

richnjones

Hi all

Consider the following code with two overloaded methods


public class Class1 {
public void method(Object o) {
System.out.println("Object");
}

public void method(String s) {
System.out.println("String");
}

public static void main(String args[]) {
Class1 c = new Class1);
c.method(null);
}
}

When I run this code it outputs "String". Why does it choose this
version of the overloaded method? If I add another method with an
Integer parameter then I need the cast.
Below I am casting to String

public class Class1 {
public void method(Object o){
System.out.println("object");
}
public void method(String s){
System.out.println("string");
}

public void method(Integer i){
System.out.println("integer");
}

public static void main(String args[]) {
Class1 c = new Class1);
c.method((String)null);
}
}


Why do I need the cast in the second example? Why does the first
example use the String method and not the object method when there is
no explicit cast

Thanks in advance
Richard
 
R

RedGrittyBrick

Hi all

Consider the following code with two overloaded methods


public class Class1 {
public void method(Object o) {
System.out.println("Object");
}

public void method(String s) {
System.out.println("String");
}

public static void main(String args[]) {
Class1 c = new Class1);
c.method(null);
}
}

When I run this code it outputs "String". Why does it choose this
version of the overloaded method? If I add another method with an
Integer parameter then I need the cast.
Below I am casting to String

public class Class1 {
public void method(Object o){
System.out.println("object");
}
public void method(String s){
System.out.println("string");
}

public void method(Integer i){
System.out.println("integer");
}

public static void main(String args[]) {
Class1 c = new Class1);
c.method((String)null);
}
}


Why do I need the cast in the second example?

To give Java a clue as to which method you want it to pick of the many
equally qualified methods available to it.
Why does the first
example use the String method and not the object method when there is
no explicit cast

This came up before in this newsgroup, I don't remember the reasons
given (try Google Groups search) but I guess the reason is that Java
chooses the most specific method of those that match the call signature.
String is more specific than Object.

Integer isn't any more specific than String in this context, but the
String method was defined first. Maybe this had some influence. I expect
it's specified in the Java language reference.

I expect it is a good idea to *explicitly* disambiguate any use of nulls
in method arguments, even if Java happens to be making the choice I want
at the moment.

Passing nulls to a method may be a sign that you need to define a
no-argument form of the method.
 
R

richnjones

Thanks for the reply
I agree that if I was passing null into a method I should redefine the
method. This is just something I was trying to understand from a
language point of view.

Richard
 
L

Lew

I agree that if I was passing null into a method I should redefine the
method. This is just something I was trying to understand from a
language point of view.

RedGrittyBrick explained it correctly. To "understand from a [Java] language
point of view", check out the Java language specification:
[method invocation resolution] uses the name of the method and the
types of the argument expressions to locate methods that are both
accessible and applicable, that is, declarations that can be correctly
invoked on the given arguments. There may be more than one such method,
in which case the most specific one is chosen.

and
If more than one member method is both accessible and applicable to a
method invocation, it is necessary to choose one to provide the descriptor
for the run-time method dispatch. The Java programming language uses the
rule that the most specific method is chosen. ....
It is possible that no method is the most specific, because there are
two or more methods that are maximally specific. In this case: ....
[if none are abstract], we say that the method invocation is ambiguous,
and a compile-time error occurs.

That should clear it up completely.
 
R

Roedy Green

public static void main(String args[]) {
Class1 c = new Class1);
c.method(null);

The choice of class implementing a method is dynamic based on the
runtime class of c, but the choice of which overloaded method depends
on the compile time declaration of types in the invocation. You
specified null which has no type information. So you gave Java no hint
as to which overloaded method to use. From the point of view of
maintainable code, this is a malformed program. It should not depend
on such fine points in the JLS as how it resolves this ambiguous case.
 
C

Curt Welch

Roedy Green said:
public static void main(String args[]) {
Class1 c = new Class1);
c.method(null);

The choice of class implementing a method is dynamic based on the
runtime class of c, but the choice of which overloaded method depends
on the compile time declaration of types in the invocation. You
specified null which has no type information. So you gave Java no hint
as to which overloaded method to use. From the point of view of
maintainable code, this is a malformed program. It should not depend
on such fine points in the JLS as how it resolves this ambiguous case.

I'm new to Java and though I have lots of experience with C and and other
languages like Smalltalk, there are are a few things in Java that are just
not obvious that I need to understand to really learn the language and the
system of selecting between redundant overloaded methods is exactly the
type of thing I don't yet understand.

Because of this thread, I found a little time yesterday to play with it and
realized as you say that the selection is based on compile time hints and
not run time reality. Because when I do something like:

Object o
o = "string";
method(o);

The method used is method(Object) and not method(String) even though the
String method would be the obvious choice if it was a run time decision.
So it's using the compile time information (o is type Object) to make the
choice instead of the actual type of the object at run time.

But I'm having a hard time grasping how this is all actually implemented
and without a good model of how it's implemented I'm feeling at a loss to
really understand what it does in all cases.

I understand that on method calls, arguments can be automatically converted
to different types, such as an int to a double, or a int to an Integer.
But is the decision about the conversion always resolved at compile time so
the conversion will always happen as it was determined to happen at compile
time? Or is this a decision that is sometimes put off to run time so it
has to produce conditional conversions?

The issue it seems to me would come up when different possible objects are
the target of the method, and that the different objects have different
sets of redundant methods to choose from.

For example we have these sorts of classes and methods:

Class Superclass
method(double)

Class Subclass extends Superclass
method(double)
method(int)

And code like this:

Superclass s;
s = (either a Superclass object or a Subclass object);
s.method(int);

So, at run time, s might be either type of object. So depending on the
type of object, either the Superclass or the Subclass methods will be
called.

But if it's a Subclass object, it can use the int method and the argument
doesn't need to be converted. But if it's the Superclass, there is no int
method, so it will want to use the double method, and convert the argument
from int to double before passing it.

So what actually happens? Does the compiler prevent such a thing from
being coded? That is, you can't add extra overloaded methods in a
subclass? Or, since the type of the s variable is Superclass, does it only
use the methods defined in the Superclass to make the decision at compile
time, and produce the code to convert the int to a double, and always call
the double method even if the receiving object is a Subclass object? i.e.,
in that case, the int method would just never be used unless the declared
type of the s variable was Subclass?

Or does it figure it all out at run time, and call the method with the int
arg when the object is a Subclass and do the conversion and call the double
method if the object is a Superclass object?

It seems the above example is exactly the type of thing that could catch
you off guard and create some unexpected bugs in your code depending on
what Java allows. That is, you try to add a new overloaded method in a
subclass to add some special handling of a specific type argument, and the
method never gets called because you were trying to apply it to an object
typed as Superclass in some sort of collection. In which case, the whole
point of polymorphism and overloading of methods would fail you, and to
make it work you would have to resort to producing code to hand check for
the object type before sending the method with something like this:

Superclass s = (something unspecified);
if (s.getClass() == Subclass.class)
((Subclass)s).method(int);
else
s.method(int);

Or you could modify Superclass and add the method(int) as a stub to call
method(double) - which would not be an option if you couldn't modify the
superclass you were trying to use.

So, without me having to actually test the above special case, can someone
give me a better idea of how java actually implements method calls so that
I can feel like I have the power to predict how Java will deal with these
sorts of special cases without having to test each special case I dream up?
Most important, how much is it forced to decide at compile time, and how
much does it leave to run time in the selection of overloaded methods and
the auto-conversion of argument types?
 
C

Curt Welch

Roedy Green said:
public static void main(String args[]) {
Class1 c = new Class1);
c.method(null);

The choice of class implementing a method is dynamic based on the
runtime class of c, but the choice of which overloaded method depends
on the compile time declaration of types in the invocation. You
specified null which has no type information. So you gave Java no hint
as to which overloaded method to use. From the point of view of
maintainable code, this is a malformed program. It should not depend
on such fine points in the JLS as how it resolves this ambiguous case.

I'm new to Java and though I have lots of experience with C and and other
languages like Smalltalk, there are are a few things in Java that are
just not obvious that I need to understand to really learn the language
and the system of selecting between redundant overloaded methods is
exactly the type of thing I don't yet understand.

Because of this thread, I found a little time yesterday to play with it
and realized as you say that the selection is based on compile time hints
and not run time reality. Because when I do something like:

Object o
o = "string";
method(o);

The method used is method(Object) and not method(String) even though the
String method would be the obvious choice if it was a run time decision.
So it's using the compile time information (o is type Object) to make the
choice instead of the actual type of the object at run time.

But I'm having a hard time grasping how this is all actually implemented
and without a good model of how it's implemented I'm feeling at a loss to
really understand what it does in all cases.

I understand that on method calls, arguments can be automatically
converted to different types, such as an int to a double, or a int to an
Integer. But is the decision about the conversion always resolved at
compile time so the conversion will always happen as it was determined to
happen at compile time? Or is this a decision that is sometimes put off
to run time so it has to produce conditional conversions?

The issue it seems to me would come up when different possible objects
are the target of the method, and that the different objects have
different sets of redundant methods to choose from.

For example we have these sorts of classes and methods:

Class Superclass
method(double)

Class Subclass extends Superclass
method(double)
method(int)

And code like this:

Superclass s;
s = (either a Superclass object or a Subclass object);
s.method(int);

So, at run time, s might be either type of object. So depending on the
type of object, either the Superclass or the Subclass methods will be
called.

But if it's a Subclass object, it can use the int method and the argument
doesn't need to be converted. But if it's the Superclass, there is no
int method, so it will want to use the double method, and convert the
argument from int to double before passing it.

So what actually happens? Does the compiler prevent such a thing from
being coded? That is, you can't add extra overloaded methods in a
subclass? Or, since the type of the s variable is Superclass, does it
only use the methods defined in the Superclass to make the decision at
compile time, and produce the code to convert the int to a double, and
always call the double method even if the receiving object is a Subclass
object? i.e., in that case, the int method would just never be used
unless the declared type of the s variable was Subclass?

Or does it figure it all out at run time, and call the method with the
int arg when the object is a Subclass and do the conversion and call the
double method if the object is a Superclass object?

It seems the above example is exactly the type of thing that could catch
you off guard and create some unexpected bugs in your code depending on
what Java allows. That is, you try to add a new overloaded method in a
subclass to add some special handling of a specific type argument, and
the method never gets called because you were trying to apply it to an
object typed as Superclass in some sort of collection. In which case,
the whole point of polymorphism and overloading of methods would fail
you, and to make it work you would have to resort to producing code to
hand check for the object type before sending the method with something
like this:

Superclass s = (something unspecified);
if (s.getClass() == Subclass.class)
((Subclass)s).method(int);
else
s.method(int);

Or you could modify Superclass and add the method(int) as a stub to call
method(double) - which would not be an option if you couldn't modify the
superclass you were trying to use.

So, without me having to actually test the above special case, can
someone give me a better idea of how java actually implements method
calls so that I can feel like I have the power to predict how Java will
deal with these sorts of special cases without having to test each
special case I dream up? Most important, how much is it forced to decide
at compile time, and how much does it leave to run time in the selection
of overloaded methods and the auto-conversion of argument types?

Ok, I had to test the code after I posted the question to see what Java
does.

It allows you to add new overloaded methods in a subclass, so the above
type of code complies just fine. But, it seems to use the compile time
type specification of the object you are sending the method to, to make all
the compile time decisions about what type of conversion to do, and which
overloaded method to use. So in the above example, it would make the
conversion from int to double and call method(double).

So, at run time, when you have classes like this:

Class Super
method(double)

Class Sub extends Super
method(double) // a new version of this method for doubles
method(int) // overload method() to add special code for ints

And code like this:

Super array[] = new Super[2];

array[0] = new Super();
array[1] = new Sub();

for (Super s : array)
s.method(1);

The method(int) in Subclass never gets called. Only Super.method(double)
and Sub.method(double) are called.

So I think the answer to my question about how Java implements all this, is
that it uses compile time type information to determine the class, and then
uses only the methods available in that class (and it's superclasses of
course) to pick which overloaded method to use, and then hard-codes the
conversion code as needed, and hard-codes the call to the selected method
at compile time. Which means code like the above example, simply doesn't
work as you might want it to. You can't add new overloaded methods in a
subclass if you are going to try and use them through a superclass object
reference.

This however, works exactly as expected:

Subclass sub = new Subclass();
sub.method(1); // calls the method(int) in the subclass

If any of the experts here see something wrong with this, please correct
me....
 
M

Martin Gregorie

Curt said:
For example we have these sorts of classes and methods:

Class Superclass
method(double)

Class Subclass extends Superclass
method(double)
method(int)

And code like this:

Superclass s;
s = (either a Superclass object or a Subclass object);
s.method(int);

So, at run time, s might be either type of object. So depending on the
type of object, either the Superclass or the Subclass methods will be
called.
s.method(int)'s argument will be widened to a double and
Superclass.method(double) is called because an int can be converted to a
double.

BUT if the subclass has a method which can't be converted, then you get
a compile-time error. The following classes (which took about 5 minutes
to write, compile and test) illustrates exactly what happens:

public class Parent
{
public void method(double d)
{
System.out.println("Parent.method(double) = " + d);
}
}

public class Child extends Parent
{
public void method(double d)
{
System.out.println("Child.method(double) = " + d);
}

public void method(int i)
{
System.out.println("Child.method(int) = " + i);
}


public void method(String s)
{
System.out.println("Child.method(String) = " + s);
}
}

public class Family
{
public static void main(String args[])
{
Parent p = new Child();


p.method(2);
p.method("string");
System.exit(0);
}
}


The code as shown will throw a compilation error because there is no
superclass method that matches method(String) even after argument
conversion:

$ javac Family.java
Family.java:8: method(double) in Parent cannot be applied to
(java.lang.String)
p.method("string");
^
1 error

BUT if you comment out line 8 the code will compile and run:

$ javac Family.java
$ java Family
Child.method(double) = 2.0

What happens is:
(1) the argument is converted at compile time to match the superclass
method
(2) the subclass method is called because it overrides the superclass
method with the matching interface.

If you add method(int) to the superclass then no type conversion occurs
and subclass.method(int) is called because it overrides the superclass
declaration.

HTH
 
R

Roedy Green

Because of this thread, I found a little time yesterday to play with it and
realized as you say that the selection is based on compile time hints and
not run time reality.

There is a variant of Java called Nice that runs on the JVM that makes
the matching decision at run time.

You can fake Nice behaviour in Java with a method that expects an
Object parameter, then does a string of instanceofs to select at run
time based on the type of the object which variant method to actually
use.

see http://mindprod.com/jgloss/nice.html
 
B

Ben Phillips

Lew said:
Same chapter that I mentioned on this subject. This chapter, Curt,
really digs into the rules. Any javac + JVM will have to implement
these rules. Different Java engines may implement them differently, but
the rules are the rules.

In practise, to keep code maintainable it's best to limit overloads to
differing numbers of arguments, and maybe sparingly overloads with only
varying argument types where none are related to one another (and the
primitive types and Number subclasses count as all related here), aside
from static methods where you might one for every primitive type and one
for Object, say. Static methods don't present these potential sources of
confusion since they don't get overridden.

Of course there's even trickier issues. For example:

class Parent {
public int method (String foo) { ... }
public int method (List foo) { ... }
}

class Child extends Parent {
public int method (Object foo) { ... }
}

Which is Child's method?
a) A compile error
b) An override of method(String)
c) An override of method(List)
d) An overload that overrides neither

The answer is far from obvious, since it has the same return type and an
argument type contravariant to both superclass methods'. In particular
it's far from obvious what calling super in Child.method() will do
without experimenting or reading the JLS in detail; likewise what
changes if one or both superclass methods are declared "final"...the
cleanest solution for the Java designers would clearly be to make
ambiguous possible-overrides always just overloads, or not even allow
contravariant argument types to override, so argument types have to be
identical to override, but is this what they did (and if so, which)? No
doubt the answer's in the JLS, but code whose behavior you can't predict
without consulting the legalese is unmaintainable code.

This is yet another reason why having same-number-of-arguments overloads
of non-static methods in non-final classes is simply begging for trouble.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,997
Messages
2,570,241
Members
46,832
Latest member
UtaHetrick

Latest Threads

Top