Why this overloading example works this way?

  • Thread starter Gabriel Belingueres
  • Start date
G

Gabriel Belingueres

Hi,

I wanted to know why the following example works the way it works:

class A {}

class B extends A {}

public class OverloadingTest {

public void method(A a) {
System.out.println("A.method");
}

public void method(B b) {
System.out.println("B.method");
}

public static void main(String[] args) {
OverloadingTest o = new OverloadingTest();

A p = new B();
o.method(p);

o.method(new B());

final A p2 = new B();
o.method(p2);
}
}

It prints:

A.method
B.method
A.method

Now, the question is:

Why the third method call is calling method(A) when the _final_
modifier is present in the local variable. I mean, since the local
variable is marked final, the compiler ALREADY KNOW, that for this
specific method call, we want to call the method with a B instance, not
an A instance. AFAIK, it is impossible to change the dynamic data type
of the p2 variable at runtime because it is marked final.

I can guess that this would introduce tricky rules to the language, but
I consider it as a nice to have feature for some future version of
Java. :)

Regards,
Gabriel
 
C

Chris Smith

Gabriel Belingueres said:
final A p2 = new B();
o.method(p2);
Why the third method call is calling method(A) when the _final_
modifier is present in the local variable. I mean, since the local
variable is marked final, the compiler ALREADY KNOW, that for this
specific method call, we want to call the method with a B instance, not
an A instance.

The rules of the Java programming language state that method overloading
is based on the types of the variables passed as parameters. The type
of 'p2' is 'A', because you declared it that way. Therefore, the
overload with A as a parameter is called.

You seem to want the compiler to engage in some kind of reasoning
process to figure out that 'p2' must point to a B instance. Such a
language may be possible, but it's not Java. As for why Java doesn't do
that, one good reason is that the language behavior should be
predictable. The compiler can never completely reason out what objects
may be passed in to a method in the general case. The problem is
undecidable; there exists no software algorithm to do it. It could
guess correctly in certain simple cases, but there would always be the
outstanding question of whether some specific kind of reasoning is too
difficult or not. It would no longer be feasible for most programmers
to predict in advance what their programs will do. This is undesirable
for obvious reasons. Therefore, the language chooses a particularly
simple rule: find the variable declaration, and look at its (static)
type.
AFAIK, it is impossible to change the dynamic data type
of the p2 variable at runtime because it is marked final.

Yes, it is impossible at least from Java. Some fancy native code could
do it, given sufficient knowledge about the JVM implementation.
I can guess that this would introduce tricky rules to the language, but
I consider it as a nice to have feature for some future version of
Java. :)

It's a bit too late for this kind of change; it would potentially break
all sorts of existing code. There do exist other languages that
dispatch method calls dynamically on the basis of the types of
parameters. They are sometimes nice, but even then the rules for
resolving method calls can get complex.
 
P

Patricia Shanahan

Gabriel said:
Hi,

I wanted to know why the following example works the way it works:

class A {}

class B extends A {}

public class OverloadingTest {

public void method(A a) {
System.out.println("A.method");
}

public void method(B b) {
System.out.println("B.method");
}

public static void main(String[] args) {
OverloadingTest o = new OverloadingTest();

A p = new B();
o.method(p);

o.method(new B());

final A p2 = new B();
o.method(p2);
}
}

It prints:

A.method
B.method
A.method

Now, the question is:

Why the third method call is calling method(A) when the _final_
modifier is present in the local variable. I mean, since the local
variable is marked final, the compiler ALREADY KNOW, that for this
specific method call, we want to call the method with a B instance, not
an A instance. AFAIK, it is impossible to change the dynamic data type
of the p2 variable at runtime because it is marked final.

Without reading your mind, it is impossible for the compiler to know
what you want. It can only go by what you say.

The declaration of p2 as type A is an instruction from the programmer to
the compiler to treat uses of p2, for all compile-time purposes, as type
A expressions. If the compiler started second-guessing by deducing that
the object is always a B, what syntax would you recommend for saying
that the programmer really means it to be treated as type A?

As it is, you can completely control whether the compiler treats p2 as a
type A variable or a type B variable through its declared type.

Patricia
 
N

NeoGeoSNK

"Gabriel Belingueres дµÀ£º
"
Hi,

I wanted to know why the following example works the way it works:

class A {}

class B extends A {}

public class OverloadingTest {

public void method(A a) {
System.out.println("A.method");
}

public void method(B b) {
System.out.println("B.method");
}

public static void main(String[] args) {
OverloadingTest o = new OverloadingTest();

A p = new B();
o.method(p);

o.method(new B());

final A p2 = new B();
o.method(p2);
}
}

It prints:

A.method
B.method
A.method

Now, the question is:

Why the third method call is calling method(A) when the _final_
modifier is present in the local variable. I mean, since the local
variable is marked final, the compiler ALREADY KNOW, that for this
specific method call, we want to call the method with a B instance, not
an A instance. AFAIK, it is impossible to change the dynamic data type
of the p2 variable at runtime because it is marked final.

I can guess that this would introduce tricky rules to the language, but
I consider it as a nice to have feature for some future version of
Java. :)

Regards,
Gabriel

You can tell the compiler that p2 is a B by:
o.method((B)p2);
^ ^"
 
G

Gabriel Belingueres

I share your opinion in that introducing such a "type inference"
behavior in the compiler would complicate things enough to drive you
mad.

But, I think this is a valid check that the compiler can do without
sacrificing too much the compilation performance, and output a warning
message when it parse code like this, so that the programmer can make
your mind about that he/she want. Something like this:

When the compiler encounters code like this (where B is a subclass of
A):

final A p2 = new B();
o.method(p2);

AND there exists overloaded methods taking a B instance and some
instance of a superclass of B, then mark the method call as
"suspicious", so that the programmer can take some action:

If he/she really meant having a final B instance, then declare it:

final B p2 = new B();
o.method(p2);

If he/she really meant to have an A instance because he/she will use
the same variable with other dynamic type, then he/she can take out the
final modifier:

A p2 = new B();
o.method(p2);

Gabriel

If he/she really wanted a final A local variable referencing a B
instance, then I think it is OK to SHOW A WARNING, though the class
file compiles normally under the current overloading rules.


Chris Smith ha escrito:
 
L

Lew

Gabriel said:
I share your opinion in that introducing such a "type inference"
behavior in the compiler would complicate things enough to drive you
mad.

But, I think this is a valid check that the compiler can do without
sacrificing too much the compilation performance, and output a warning
message when it parse code like this, so that the programmer can make
your mind about that he/she want. Something like this:

Ah, but then it wouldn't be Java would it?
When the compiler encounters code like this (where B is a subclass of
A):

final A p2 = new B();
o.method(p2);

AND there exists overloaded methods taking a B instance and some
instance of a superclass of B, then mark the method call as
"suspicious", so that the programmer can take some action:

If he/she really meant having a final B instance, then declare it:

final B p2 = new B();
o.method(p2);

If he/she really meant to have an A instance because he/she will use
the same variable with other dynamic type, then he/she can take out the
final modifier:

A p2 = new B();
o.method(p2);

Gabriel

If he/she really wanted a final A local variable referencing a B
instance, then I think it is OK to SHOW A WARNING, though the class
file compiles normally under the current overloading rules.

First of all, "final" has nothing to do with overloading. A "final" variable
is one that cannot be reassigned.

Most telling, there are times when one wants to declare overloads that differ
only where a parameter in one is of a supertype of that from the other. The
overload with the subtype is called a "specialization", and it is a perfectly
valid idiom.

E.g.,

A is a supertype of B
X declares methods:
public T foo( A parm );
and
public T foo( B parm );

as in
public boolean equals( Object obj )
{
if ( obj instanceof X )
{
return equals( (X) obj );
}
else
{
return false;
}
}

(The return statement would be more compact as:
return (obj instanceof X? equals( (X) obj ) : false);
)

We don't want warnings here. In any case, this is how Java works.

- Lew
 
C

Chris Smith

Lew said:
Ah, but then it wouldn't be Java would it?

Sure it would. So long as the compiler generates code to do what the
language spec says it will do, it's still Java. The extra warning
message would be entirely legal. That said, though, I am having enough
problems with the spurious warnings being generated by mixing generic
code with older APIs. I've got some code that would probably trigger
it, and I write it intentionally that way. I'm not sure whether I'd
like this warning or not. So long as I could turn it off...
First of all, "final" has nothing to do with overloading. A "final" variable
is one that cannot be reassigned.

The final qualifier is relevant because it guarantees that the variable
will ALWAYS point to a B, so there isn't a question as to what it was
meant to point to.

First, I'd add the caveat that one must exclude blank finals as well to
even accomplish that purpose. Second, I think there's value to being
able to use types to guarantee that code is resolved as if it were a
supertype. Even if I currently give the variable a pointer to B, I may
be looking ahead and predicting that this may change in a few weeks when
we implement the super bingle-bopper feature. So I'd like the compiler
to respect me when I say so, and not complain. (Again, I would at least
need to be able to turn the warning off, as I could do in Eclipse, for
example, without scattering @SuppressWarnings all over my code.)
 
L

Lew

Chris said:
Sure it would. So long as the compiler generates code to do what the
language spec says it will do, it's still Java. The extra warning
message would be entirely legal. That said, though, I am having enough
problems with the spurious warnings being generated by mixing generic
code with older APIs. I've got some code that would probably trigger
it, and I write it intentionally that way. I'm not sure whether I'd
like this warning or not. So long as I could turn it off...


The final qualifier is relevant because it guarantees that the variable
will ALWAYS point to a B, so there isn't a question as to what it was
meant to point to.

Ah, I see. OTOH, it is typical to have an idiom more like:

final A aVar = factoryForA();

where the factory method in fact returns a subtype of A. For example, this
occurs heavily in JDBC code. The idiom

[final} A aVar = someB();

doesn't mean "always treat aVar as if it were a B", it means "treat whatever
the right side returns as if it were an A". The point of the idiom is to refer
to aVar with the exact interface of an A and not care what subtype it is.

We are saying "whatever someB() returns, it's really an A".
First, I'd add the caveat that one must exclude blank finals as well to
even accomplish that purpose. Second, I think there's value to being
able to use types to guarantee that code is resolved as if it were a
supertype. Even if I currently give the variable a pointer to B, I may
be looking ahead and predicting that this may change in a few weeks when
we implement the super bingle-bopper feature. So I'd like the compiler
to respect me when I say so, and not complain.

I agree, but this is called polymorphism and it doesn't need warnings when you
use it. It is, arguably, the single most powerful idiom in object-oriented
coding. We don't want the supertype variable to change its external behavior
just because we changed its internal implementation.

If you mean a variable to act like a B, declare it a B.

If you do want the variable to change behavior at some future time, you are
deliberately sacrificing type safety, a fundamental tenet of Java and
object-oriented programming.

Wouldn't you have to rewrite code dependent on that variable if you changed
its behaviors?

- Lew
 
G

Gabriel Belingueres

Lew ha escrito:
Ah, but then it wouldn't be Java would it?


First of all, "final" has nothing to do with overloading. A "final" variable
is one that cannot be reassigned.

Most telling, there are times when one wants to declare overloads that differ
only where a parameter in one is of a supertype of that from the other. The
overload with the subtype is called a "specialization", and it is a perfectly
valid idiom.

E.g.,

A is a supertype of B
X declares methods:
public T foo( A parm );
and
public T foo( B parm );

as in
public boolean equals( Object obj )
{
if ( obj instanceof X )
{
return equals( (X) obj );
}
else
{
return false;
}
}

(The return statement would be more compact as:
return (obj instanceof X? equals( (X) obj ) : false);
)

We don't want warnings here. In any case, this is how Java works.

- Lew

I agree that the final keyword is not directly related with the
overload concept. But issues exist anyway.

Taking aside your equals implementation (I don't believe that
overloading the equals method is a common idiom, _overriding_ IS a
common idiom), I agree too that downcasting should not generate any
warnings/errors (as it is now). After all, that's the reason you are
downcasting anyway: to interfere into the type system and tell the
compiler to believe in whatever type are you casting to (only when you
are casting to a subtype, otherwise the compiler will and SHOULD
complain).

Gabriel
 
O

Oliver Wong

Gabriel Belingueres said:
(I don't believe that
overloading the equals method is a common idiom, _overriding_ IS a
common idiom)

Really? Whenever I override the equals method, it's pretty much always
overloaded as well:

public SomeClass {
final int field1;
final String field2;

/*Constructors and getters*/

@Override
public boolean equals(Object other) {
if (other instanceof SomeClass) {
return equals((SomeClass)other);
}
return false;
}

public boolean equals(SomeClass other) {
if (this.field1 != other.field1) {
return false;
}
if (!this.field2.equals(other.field2)) {
return false;
}
return true;
}
}

- Oliver
 
G

Gabriel Belingueres

Sure it would. So long as the compiler generates code to do what the
language spec says it will do, it's still Java. The extra warning
message would be entirely legal. That said, though, I am having enough
problems with the spurious warnings being generated by mixing generic
code with older APIs. I've got some code that would probably trigger
it, and I write it intentionally that way. I'm not sure whether I'd
like this warning or not. So long as I could turn it off...

Yes, certainly I would put it as a option not enabled by default (to
not break existing code).
First, I'd add the caveat that one must exclude blank finals as well to
even accomplish that purpose. Second, I think there's value to being
able to use types to guarantee that code is resolved as if it were a
supertype. Even if I currently give the variable a pointer to B, I may
be looking ahead and predicting that this may change in a few weeks when
we implement the super bingle-bopper feature. So I'd like the compiler
to respect me when I say so, and not complain. (Again, I would at least
need to be able to turn the warning off, as I could do in Eclipse, for
example, without scattering @SuppressWarnings all over my code.)

blank final declarations (if you mean by blank to declare and
initialize at different stages) can be catched to by this rule:

final A p2;
p2 = new B(); // did you mean final B p2?

One thing I'd like to note, and this is IMHO, is that the case in that
want to treat your subclass B as if it were an A, it is basically a
matter of polymorphism, which is an inherent OO concept, and
materialized by the method overriding mechanism in java, but I think
that method overloading must be treated more cautiously because I
rather see it as syntax sugar for different methods, not something we
should take for granted as having "fully polymorhic behavior".
That's why I tend to think twice when I use those overloading
situations. In fact, I think that double-dispatching idiom is better
suited for those situations than direct overloading.

Gabriel
 
G

Gabriel Belingueres

I think you should be careful with this practice. In the one hand, you
have to think that equals() and hashcode() are intimatelly related, so
you need to kkep track of changes in more methods when you overload
equals.
On the other hand, overloading fixes the actual method to call at
compile time (not runtime, as overriden methods do), so for example,
all the java Collections implementations will ALLWAYS use the
equals(Object) version, never use the one you created. In this case you
may solve the problem my implementing Comparable on your objects.

Gabriel

Oliver Wong ha escrito:
 
C

Chris Smith

Gabriel Belingueres said:
blank final declarations (if you mean by blank to declare and
initialize at different stages) can be catched to by this rule:

final A p2;
p2 = new B(); // did you mean final B p2?

final A p2;

if (turingMachineHalts(machine, input)) p2 = new B();
else p2 = new A();
 
C

Chris Smith

Lew said:
I agree, but this is called polymorphism and it doesn't need warnings when you
use it.

No, it's really something different. What's being proposed here isn't
polymorphic method dispatch. There are very nice languages that do
dynamic dispatch using parameter classes; it's just not been proposed
here. This discussion was about keeping the existing static resolution,
but adding a louder compiler.
 
G

Gabriel Belingueres

Oops! Now I see it. Yes, those declarations would need to be excluded.

Gabriel

Chris Smith ha escrito:
 
G

Gabriel Belingueres

....Although on a second thought, this may well apply to the rule,
because it applies only to final variables AND it applies when there
are some overloading methods (taking as parameter the class and one of
their superclasss) in the current scope, nothing more.

In your example, it lacks the call to the overloaded method:

final A p2;

if (condition)
p2 = new B();
else
p2 = new A();
method(p2);

you not only have this problem present too, but this time, the code is
_ambiguous_ (it is a final A instance sometimes, but other times it is
a final B instance).

How to solve the warning:

1) As allways, you may cast down the object to the appropiate type.
2) You can deambiguate by taking the overloaded method(B) out of the
current scope. (this may be too much to ask however)
3) take out the final modifier to the variable.
4) Refactor your code (if you still want the variable to be final and
to keep both overloaded methods on scope), or in other words, don't use
blank final declarations at all:

if (condition) {
final B p2 = new B();
method(p2); // call overloaded method(B)
}
else {
final A p2 = new A();
method(p2); // call overloaded method(A)
}

Anyway, I think this case can be spotted too.

Gabriel

Gabriel Belingueres ha escrito:
 
O

Oliver Wong

[post re-ordered]

Gabriel Belingueres said:
Oliver Wong ha escrito:


I think you should be careful with this practice. In the one hand, you
have to think that equals() and hashcode() are intimatelly related, so
you need to kkep track of changes in more methods when you overload
equals.

With this pattern, the equals(Object) method always delegates to the
equals(SomeClass) method. I make the assumption that if the "other class" is
not an instance of "this class", then the two objects are clearly not
equal[*]. If they *are* of the same class, then some further analysis (e.g.
of the fields) needs to be done to ensure they are equal, and so the "real"
work is done in the equals(SomeClass) method.

The hashcode() method only needs to follow the equals(SomeClass) method,
so there isn't an extra layer of complexity here.

On the other hand, overloading fixes the actual method to call at
compile time (not runtime, as overriden methods do), so for example,
all the java Collections implementations will ALLWAYS use the
equals(Object) version, never use the one you created.

Actually, because equals(Object) always delegates to equals(SomeClass)
(unless the two objects under comparison are trivially unequal, e.g. by
being of different classes), then the equals(SomeClass) method *will* get
called at the appropriate times. And sometimes, the compiler will, at
compile time, make a call to the equals(SomeClass) method:

SomeClass a = new SomeClass();
SomeClass b = new SomeClass();
Object alsoB = b;

a.equals(b); /*directly calls equals(someClass)*/
a.equals(alsoB); /*indirectly calls equals(someClass)*/
In this case you
may solve the problem my implementing Comparable on your objects.

The problem here is that Comparable forces a total ordering on your
type. In designing an implementation for equals(), you only need to
determine whether two objects are equal or not. In designing an
implementation for Comparable, you need to additionally decide which object
is "greater" than the other object, in the case where they're not equal, and
this concept of greater-than might not always make sense.

- Oliver

*: Actually, making equals "aware" of class hierarchies is slightly trickier
than that. I wanted to gloss over this in my above example, but here's a
more complete example implementation:

public class SomeClass {
final int field1;
final String field2;

/*Constructors, getters, and hashcode*/

@Override
public boolean equals(Object other) {
if (other instanceof SomeClass) {
return equals((SomeClass)other);
}
return false;
}

public boolean equals(SomeClass other) {
if (!other.getClass().equals(this.getClass())) {
return other.equals(this);
}
if (this.field1 != other.field1) {
return false;
}
if (!this.field2.equals(other.field2)) {
return false;
}
return true;
}
}

There's an extra 3 lines. When equals(SomeClass) is invoked, we know that
other is an instance of SomeClass. But other might actually be a *subclass*
of SomeClass, with additional fields which may or may not be important in
the equals comparison. If it *is* a subclass of SomeClass, then presumably
the subclass "knows about" SomeClass, and is better informed to make the
judgment of whether they are equal, and can thus override the
equals(SomeClass) method, and probably add an equals(SomeSubClass) method.
 
C

Chris Uppal

Oliver said:
@Override
public boolean equals(Object other) {
if (other instanceof SomeClass) {
return equals((SomeClass)other);
}
return false;
}

public boolean equals(SomeClass other) {
if (this.field1 != other.field1) {
return false;
}
if (!this.field2.equals(other.field2)) {
return false;
}
return true;
}

Minor nitpick: the two methods treat null differently. Consider

SomeClass sc1 = new SomeClass();
SomeClass sc2 = null;
Object o = sc2;

scl.equals(o); // false
sc1.equals(sc2); // boom
sc2 == o; // true


Personally, I don't consider it unreasonable to <boom> when comparing against
null, /and/ not attempting to follow the contract of Object.eqauls(Object).
But where that's the case, I'd prefer to call the comparison method something
other than equals(<something>) even though it is formally an unrelated method.

-- chris
 
O

Oliver Wong

Chris Uppal said:
Minor nitpick: the two methods treat null differently. Consider

SomeClass sc1 = new SomeClass();
SomeClass sc2 = null;
Object o = sc2;

scl.equals(o); // false
sc1.equals(sc2); // boom
sc2 == o; // true

Damn. Now I'm gonna have to go back and review all my code. Thanks for
the heads up.

- Oliver
 
P

Patricia Shanahan

Oliver Wong wrote:
....
*: Actually, making equals "aware" of class hierarchies is slightly trickier
than that. I wanted to gloss over this in my above example, but here's a
more complete example implementation:

public class SomeClass {
final int field1;
final String field2;

/*Constructors, getters, and hashcode*/

@Override
public boolean equals(Object other) {
if (other instanceof SomeClass) {
return equals((SomeClass)other);
}
return false;
}

public boolean equals(SomeClass other) {
if (!other.getClass().equals(this.getClass())) {
return other.equals(this);
}
if (this.field1 != other.field1) {
return false;
}
if (!this.field2.equals(other.field2)) {
return false;
}
return true;
}
}

I got worried about whether this code would ever make up its mind, given
a subclass comparison. It doesn't:

public class SomeClass {
final int field1 = 0;
final String field2 = "test";

public static void main(String[] args) {
Object obj1 = new SomeClass();
Object obj2 = new SomeSubClass();
System.out.println(obj1.equals(obj2));
}


/*Constructors, getters, and hashcode*/

@Override
public boolean equals(Object other) {
if (other instanceof SomeClass) {
return equals((SomeClass)other);
}
return false;
}

public boolean equals(SomeClass other) {
if (!other.getClass().equals(this.getClass())) {
return other.equals(this);
}
if (this.field1 != other.field1) {
return false;
}
if (!this.field2.equals(other.field2)) {
return false;
}
return true;
}

private static class SomeSubClass extends SomeClass{
}
}

This program results in a StackOverflowError. obj1 passes the comparison
to obj2, which passes it back to obj1... What am I doing wrong? Are
subclasses of SomeClass required to override equals with specific rules?

Patricia
 

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

No members online now.

Forum statistics

Threads
473,787
Messages
2,569,629
Members
45,330
Latest member
AlvaStingl

Latest Threads

Top