I have an interface I
with a method that allows it to interact with another I
:
public interface I {
I interactWith(I other);
}
Then I have a few classes that implement this interface, let's say A
, B
, C
and D
. Some of these interact with any other in the same way, so the implementation of the method is easy. Some others interact in different ways instead depending on the type of the other object, so i.e the interaction B
-A
is different from B
-C
and B
-D
. The naive implementation of the method for B
would be a mess of typechecking/instanceof
and feels like there's a better solution, so what's the correct approach?
I tried adding a default method interactWith(A other)
in both the interface and the B
class but it never gets called. Why is overloading not choosing the more specific method?
Edit: added sample code, also available here: https://onlinegdb.com/fakHVDDpSX
public interface I {
I interactWith(A other);
I interactWith(I other);
}
public class B implements I {
public I interactWith(A other) {
System.out.println("I'd like this to get called");
}
public I interactWith(I other) {
System.out.println("this get called even when other is of type A");
}
}
public static void main(String []args) {
List<I> list = Arrays.asList(new A(), new B());
list.get(1).interactWith(list.get(0));
}
CodePudding user response:
Without seeing your code, in particular how you are declaring variables and their types, I am guessing you are running into a common confusion with how Java does dispatch. You are probably expecting behavior that is known as multiple dispatch, while Java only supports single dispatch. Here's the wikipedia page about multiple dispatch, and how you can kind of emulate that behavior in Java: https://en.wikipedia.org/wiki/Multiple_dispatch#Java
The gist is that Java needs to know the static types of both the parameter and the receiver of the method call to be able to choose the more specific overload. For example:
ublic class Main {
public static void main(String[] args) {
I a = new A(); // runtime type is `A` but static type is `I`
A a2 = new A(); // both runtime and static types are `A`
I b = new B(); // runtime type is `B` but static type is `I`
B b2 = new B(); // both runtime and static types are `B`
C c = new C();
p(b.interactWithOther(a)); // got I Main$A@4617c264
p(b.interactWithOther(a2)); // got I Main$A@36baf30c even though static type of a2 is A, b is an I, so it "doesn't have" the overloaded method
p(b.interactWithOther(c)); // got I Main$C@7a81197d
p(b2.interactWithOther(a)); // got I Main$A@4617c264
p(b2.interactWithOther(a2)); // got A Main$A@36baf30c only in this case is there specific enough type information to call the specific overload
p(b2.interactWithOther(c)); // got I Main$C@7a81197d
}
static void p(Object o) { System.out.println(o); }
static public interface I {
I interactWithOther(I other);
}
static class A implements I {
@Override
public I interactWithOther(I other) { return other; }
}
static class B implements I {
@Override
public I interactWithOther(I other) { System.out.print("got I "); return other; }
public A interactWithOther(A other) { System.out.print("got A "); return other; }
}
static class C implements I {
@Override
public I interactWithOther(I other) { return other; }
}
static class D implements I {
@Override
public I interactWithOther(I other) { return other; }
}
}
Notice how got A
is only printed when the static type of the receiver is B
and the static type of the parameter is A
, rather than I
s.
You can play around with it here: https://replit.com/@anqit/NegligibleMustyDatamining#Main.java
CodePudding user response:
Here's a simple demonstration how polymorphic behavior is working in the situation you've described.
According to Java language specification the compiler determines all potentially applicable methods maps the call to the most specific method:
15.12.2. Compile-Time Step 2: Determine Method Signature
...
The process of determining applicability begins by determining the potentially applicable methods (§15.12.2.1). Then, to ensure compatibility with the Java programming language prior to Java SE 5.0, the process continues in three phases:
- The first phase performs overload resolution without permitting boxing or unboxing conversion, or the use of variable arity method invocation. If no applicable method is found during this phase then processing continues to the second phase.
Example:
public interface I { I interactWith(I other); default I interactWith(Foo other) { return null; } }
public static class Foo implements I {
@Override
public I interactWith(I other) {
System.out.println("Foo.interactWith(I other) is called");
return null;
}
}
public static class Bar implements I {
@Override
public I interactWith(I other) {
System.out.println("Bar.interactWith(I other) is called");
return null;
}
@Override
public I interactWith(Foo other) {
System.out.println("Bar.interactWith(Foo other) is called");
return null;
}
}
main()
public static void main(String[] args) {
Bar bar = new Bar();
Foo foo = new Foo();
foo.interactWith(bar); // method that expects super type `I` would be called
bar.interactWith(foo); // method that expects `Foo` as the most specific one would be called
}
Output:
Foo.interactWith(I other) is called
Bar.interactWith(Foo other) is called