problem with method overloading

V

Vikram

Hi,

If we have two methods in a class as below:

public void test(String s){
.......
......
}

public void test(Object o){
........
.....
}

And we invoke the method as : test(null) : from another class, the
method with the String parameter is executed. Any particular reason
for this?
 
L

Lew

Vikram said:
If we have two methods in a class as below:

public void test(String s){
......
.....

}

public void test(Object o){
.......
....

}

And we invoke the method as : test(null) : from another class, the
method with the String parameter is executed. Any particular reason
for this?

Yes.

See the JLS:
<http://java.sun.com/docs/books/jls/third_edition/html/
expressions.html#292575>
Nutshell version: most specific argument-type matching wins.

In general if you have a question about Java syntax and semantics like
this one, the answer is in the JLS.

Your method overload question is quite important, actually. It's a
common mistake to overload 'equals()' when the intent is to override,
e.g.,

public class Foo
{
...
public boolean equals( Foo other )
{
...
}
}

This method will appear to work if passed a 'Foo' argument because the
'Foo' argument matches more specifically than the 'Object' argument to
the other 'equals()' method in the class, but it will fail in general
when clients pass an 'Object' argument and bypass the specific
overload, getting the parent-class version instead.
 
S

Simon Brooke

Your method overload question is quite important, actually. It's a
common mistake to overload 'equals()' when the intent is to override,
e.g.,

public class Foo
{
...
public boolean equals( Foo other )
{
...
}
}

This method will appear to work if passed a 'Foo' argument because the
'Foo' argument matches more specifically than the 'Object' argument to
the other 'equals()' method in the class, but it will fail in general
when clients pass an 'Object' argument and bypass the specific overload,
getting the parent-class version instead.

Sorry, why is this a mistake? If a 'Bar' argument is passed, equals must
return false anyway. Does it matter which implementation of equals is
invoked?

I can see how this might matter for other methods with different
semantics, but why equals, particularly?
 
L

Lew

Simon said:
Sorry, why is this a mistake? If a 'Bar' argument is passed, equals must
return false anyway. Does it matter which implementation of equals is
invoked?

Yes. As I already stated in my post, it will fail in the general case
when clients invoke it with an 'Object' argument. You will then get
the non-overridden 'equals(Object)' method from the parent class,
which typically provides object identity comparison rather than value
comparison, or else value comparison on the wrong fields.
I can see how this might matter for other methods with different
semantics, but why equals, particularly?

The parent 'equals()' method typically is one of those "other methods
with different semantics". The semantics of 'equals(Object)' will
differ from that of 'equals(Foo)' without special care, i.e., an
override of the former that invokes the latter.

'equals()' is typically called with an 'Object' argument, and
therefore the overload will not be invoked. Things like collections
depend on the method to have an override. No override yields
undesired behavior.
 
E

Eric Sosman

Sorry, why is this a mistake? If a 'Bar' argument is passed, equals must
return false anyway. Does it matter which implementation of equals is
invoked?

The second sentence is often true, even "usually" true, but
not universally true. It sometimes makes sense for the equals()
of one class to return true when handed an instance of a different
class. For example, consider the equals() contract of Set:

Set<String> hs = new HashSet<String>();
hs.add("hello");
hs.add("world");

Set<String> ts = new TreeSet<String>();
ts.addAll(hs);

assert hs.getClass() != ts.getClass();
assert hs.equals(ts);
 
L

Lew

Eric Sosman said:
     The second sentence is often true, even "usually" true, but
not universally true.  It sometimes makes sense for the equals()
of one class to return true when handed an instance of a different
class.  For example, consider the equals() contract of Set:

If 'equals(Object)' is not overridden, then it likely returns 'false'
even for an argument where 'equals(Foo)' would return 'true' if it
were invoked on the '(Foo)' cast of that instance. Programs usually
invoke 'equals(Object)', not 'equals(Foo)', so you get results you
don't expect.
 
L

Lew

Hmm.. So it should rather be?

 public class Foo
 {
  ...

Use @Override here
   public boolean equals( Object other )
   {
    ...
   }
 }

The answer to your question depends on whether you want an override or
an overload. In the case of 'equals()' you want an override, or both.

For code that depends on an override, as with 'java.util.Set' and its
use of 'equals()', there must be such an override to give the expected
results.
 
S

Simon Brooke

If 'equals(Object)' is not overridden, then it likely returns 'false'
even for an argument where 'equals(Foo)' would return 'true' if it were
invoked on the '(Foo)' cast of that instance. Programs usually invoke
'equals(Object)', not 'equals(Foo)', so you get results you don't
expect.

OK, this is because selector-method resolution is done at compile time,
not at run time as it would be in CLOS and in some other object oriented
languages... yes?

What you're saying is if I do

Foo foo = new Foo();
Foo bar = foo;

assert foo.equals( bar);

will in this example invoke the variant of equals declared on class Foo
with the signature equals( Foo f), but

Foo foo = new Foo();
Object bar = foo;

assert foo.equals( bar);

will invoke some more generic variant of equals declared on Foo or some
superclass of Foo with the signature equals( Object o).

Have I understood you right?
 
L

Lew

Simon Brooke said:
OK, this is because selector-method resolution is done at compile time,
not at run time as it would be in CLOS and in some other object oriented
languages... yes?

I don't know anything about CLOS. It's not relevant anyway, nor are
those other languages.
What you're saying is if I do

        Foo foo = new Foo();
        Foo bar = foo;

        assert foo.equals( bar);

will in this example invoke the variant of equals declared on class Foo
with the signature equals( Foo f), but

        Foo foo = new Foo();
        Object bar = foo;

        assert foo.equals( bar);

will invoke some more generic variant of equals declared on Foo or some
superclass of Foo with the signature equals( Object o).

Have I understood you right?

Yes, barring the confusion with the Java-specific meaning of
"generic".

See the JLS as mentioned upthread:
<http://java.sun.com/docs/books/jls/third_edition/html/
expressions.html#292575>
 
M

markspace

Lew said:
I don't know anything about CLOS. It's not relevant anyway, nor are
those other languages.


Basically, Simon is correct. Java and C++ implement single dispatch in
their method overloading resolution. Both languages have to implement
double dispatch manually, which is the reason for the Visitor pattern.
Lisp and a few other languages implement double dispatch (or
multi-dispatch) natively, although I believe there's a considerable
runtime cost overhead.

C.f.

<http://en.wikipedia.org/wiki/Double_dispatch>
 
J

Joshua Cranmer

And we invoke the method as : test(null) : from another class, the
method with the String parameter is executed. Any particular reason
for this?

JLS 3 § 15.12.2, your goto resource for pretty much any question about
why a method invocation is doing what it is doing.

A brief overview of what happens in this particular case:
First, the compiler selects all the possible methods that the invocation
could match based on name and arity (in the absence of varargs, which is
a bit trickier) (§ 15.12.1 and § 15.12.2.1 goes into this in more detail
if you really want to know, but it's pretty intuitive). In your case,
this is both of the test methods.

After that, the compiler looks at types to figure stuff out. If you
regress to pre-1.5 functionality, the subtyping is pretty simple. The
compiler is basically asking "how well do each of these methods match
what the user is trying?"; all expressions have types (the assignment of
types is fairly straightforward, see most of § 15 if you really want to
know in full gory detail), so it's trying to match the types to the
types that the method supports--identifying matching methods by subtyping.

All types have supertypes: it is pretty much the same relation between
classes and superclasses, so all reference types are subtypes of Object.
The null type's supertypes are all reference types other than null.

In your case, you have an expression whose type is the null type. Since
null is a subtype of String and Object, this phase of the search selects
both test methods. In the next phase, we need to choose between them.
The powers that be decided that the most intuitive thing to do is to
select the "most specific" method, where "most specific" has particular
meaning. In the absence of generics, if method A is more specific than
method B, than the arguments of A have declared types that are subtypes
of method B's arguments. Under this definition, test(String) is more
specific than test(Object), so it is the one chosen.

Note that if you also had a test(Set) method, there would be no most
specific method, and the compiler would complain at you as a result.
 
A

Arne Vajhøj

Use @Override here

If a recent Java version.
The answer to your question depends on whether you want an override or
an overload. In the case of 'equals()' you want an override, or both.

I think it would be rare when both would be wanted.

Arne
 
A

Arne Vajhøj

Sorry, why is this a mistake? If a 'Bar' argument is passed, equals must
return false anyway. Does it matter which implementation of equals is
invoked?

I can see how this might matter for other methods with different
semantics, but why equals, particularly?

equals(Object) is more polymorh than equals(Foo) !

Try and run this little demo:

public class Equals {
public static void main(String[] args) {
Foo foo1 = new Foo(123);
Foo foo2 = new Foo(123);
System.out.println(foo1.equals(foo2));
Object ofoo1 = new Foo(123);
Object ofoo2 = new Foo(123);
System.out.println(ofoo1.equals(ofoo2));
Bar bar1 = new Bar(123);
Bar bar2 = new Bar(123);
System.out.println(bar1.equals(bar2));
Object obar1 = new Bar(123);
Object obar2 = new Bar(123);
System.out.println(obar1.equals(obar2));
}
}

class Foo {
private int v;
public Foo(int v) {
this.v = v;
}
public boolean equals(Foo o) {
return (v == o.v);
}
public int hashCode() {
return v;
}
}

class Bar {
private int v;
public Bar(int v) {
this.v = v;
}
@Override
public boolean equals(Object o) {
return (o instanceof Bar) && (v == ((Bar)o).v);
}
@Override
public int hashCode() {
return v;
}
}

The Bar class works as expected with polymorphism. The Foo class
gives inconsistent results depending on the type of the reference.

Arne
 
V

Vikram

JLS 3 15.12.2, your goto resource for pretty much any question about
why a method invocation is doing what it is doing.

A brief overview of what happens in this particular case:
First, the compiler selects all the possible methods that the invocation
could match based on name and arity (in the absence of varargs, which is
a bit trickier) ( 15.12.1 and 15.12.2.1 goes into this in more detail
if you really want to know, but it's pretty intuitive). In your case,
this is both of the test methods.

After that, the compiler looks at types to figure stuff out. If you
regress to pre-1.5 functionality, the subtyping is pretty simple. The
compiler is basically asking "how well do each of these methods match
what the user is trying?"; all expressions have types (the assignment of
types is fairly straightforward, see most of 15 if you really want to
know in full gory detail), so it's trying to match the types to the
types that the method supports--identifying matching methods by subtyping..

All types have supertypes: it is pretty much the same relation between
classes and superclasses, so all reference types are subtypes of Object.
The null type's supertypes are all reference types other than null.

In your case, you have an expression whose type is the null type. Since
null is a subtype of String and Object, this phase of the search selects
both test methods. In the next phase, we need to choose between them.
The powers that be decided that the most intuitive thing to do is to
select the "most specific" method, where "most specific" has particular
meaning. In the absence of generics, if method A is more specific than
method B, than the arguments of A have declared types that are subtypes
of method B's arguments. Under this definition, test(String) is more
specific than test(Object), so it is the one chosen.

Note that if you also had a test(Set) method, there would be no most
specific method, and the compiler would complain at you as a result.

Thank you very much. Its quite clear now.

Regards,
Vikram
 
L

Lew

If a recent Java version.

Five-and-a-half-plus years' recent since it was introduced in a Java version
itself already officially obsolete. I felt it was pretty safe to mention on
the "off" chance the poster had access to a version newer than eight years old
and was not discussing ME edition.

Whatever happened to the notion that "recent" in software meant somewhat less
than four years old? How long will it take before JLS 3rd ed. and Java 5+ are
no longer consider probationary? How is it that the vast and significant
improvements of Java 5 and later over 1.4 and earlier, including the repair of
the memory model for example, are not overwhelmingly compelling by now? Does
Java 5 have to be twenty-one and allowed to buy booze before we recognize that
it's an established product and not some Johnny-come-lately? Five years in
software years is thirty-five in dog years, for Pete's sake!

Regardless of the above rant, the OP being a new student of Java must surely
be using at least Java 5, and therefore the advice was virtually certain to be
relevant. And if he isn't, then he'd darn well better spend the zero
dollars/euros/rupees and hurry up and buy Java 6 at no cost. (Java 6 isn't
even recent any more, having been out for over four years.)

"Recent", indeed. Pah!
 
Joined
Jun 15, 2010
Messages
1
Reaction score
0
Overload problem

"null" is treated as string most of the time. So that's why your method of taking string argument is executed.
 
A

Alessio Stalla

Basically, Simon is correct.  Java and C++ implement single dispatch in
their method overloading resolution.  Both languages have to implement
double dispatch manually, which is the reason for the Visitor pattern.
Lisp and a few other languages implement double dispatch (or
multi-dispatch) natively, although I believe there's a considerable
runtime cost overhead.

Multiple dispatch in CLOS is done entirely at runtime. While
reasonable implementations of CLOS use caching strategies to mitigate
runtime costs, they can't match the efficiency of Java/C++ virtual
method dispatch, which most of the time is just a pointer dereference
+ a function call (if the JIT doesn't inline the call; else it's even
faster). In fact, even in heavily OO Lisp programs, "virtual
methods" (using C++ terminology) are not used for everything like they
are (almost) in Java: plain functions are used when runtime dispatch
is not needed.

So, imho, multiple dispatch is very powerful but only makes sense in
languages which don't use OO for everything; else, its performance is
probably too bad.

Cheers,
Alessio
 
A

Arne Vajhøj

Five-and-a-half-plus years' recent since it was introduced in a Java
version itself already officially obsolete. I felt it was pretty safe to
mention on the "off" chance the poster had access to a version newer
than eight years old and was not discussing ME edition.

Whatever happened to the notion that "recent" in software meant somewhat
less than four years old? How long will it take before JLS 3rd ed. and
Java 5+ are no longer consider probationary? How is it that the vast and
significant improvements of Java 5 and later over 1.4 and earlier,
including the repair of the memory model for example, are not
overwhelmingly compelling by now? Does Java 5 have to be twenty-one and
allowed to buy booze before we recognize that it's an established
product and not some Johnny-come-lately? Five years in software years is
thirty-five in dog years, for Pete's sake!

Regardless of the above rant, the OP being a new student of Java must
surely be using at least Java 5, and therefore the advice was virtually
certain to be relevant. And if he isn't, then he'd darn well better
spend the zero dollars/euros/rupees and hurry up and buy Java 6 at no
cost. (Java 6 isn't even recent any more, having been out for over four
years.)

"Recent", indeed. Pah!

Recent has some relative associations.

Recent ice age and recent political scandal have some different
magnitudes.

And you know as well as I do that Java has a huge backlog
regarding upgrading software.

But I completely agree that if starting with a blank
sheet of paper, then version older than 1.6 should not
even be considered.

And I expect ME to die soon. Todays embedded devices can run SE.

Arne
 

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

Similar Threads


Members online

Forum statistics

Threads
473,774
Messages
2,569,599
Members
45,162
Latest member
GertrudeMa
Top