Check the following code sample:
public class Test
{
private void process(Instance1 obj)
{
System.out.println("Process Instance 1");
}
private void process(Instance2 obj)
{
System.out.println("Process Instance 2");
}
/*
COMMENT THIS OUT - I DON'T HAVE THIS CODE IN REAL LIST. Only here to prove point 3 below calls this
private void process(SuperClass obj)
{
System.out.println("Process superclass");
}
*/
/**
* @param args
*/
public static void main(String[] args)
{
Test test = new Test();
Instance1 instance1 = test.new Instance1();
Instance2 instance2 = test.new Instance2();
SuperClass instance3 = test.new Instance1();
// test #1
test.process(instance1);
// test #2
test.process(instance2);
// test #3 (compiler error unless there is a process(SuperClass obj) call)
test.process(instance3);
// test #4
if(instance3 instanceof Instance1)
test.process((Instance1)instance3);
else if(instance3 instanceof Instance2)
test.process((Instance2)instance3);
}
abstract class SuperClass
{
}
class Instance1 extends SuperClass
{
}
class Instance2 extends SuperClass
{
}
}
This gives the output:
Process Instance 1
Process Instance 2
Process superc开发者_StackOverflowlass
Process Instance 1
I was hoping that test #3 would know to call the right function but it seems it does not. I suppose this is a compile time thing and not a runtime thing. Option #4 works but is ugly and I am hoping for a better way.
Update: To clarify the issue... I have an abstract class of which there exists two concrete implementations. What I would like is to have two overloaded methods in another class (one for each concrete class) and be able to call it without doing any instanceof ugliness. From what I know now, this isn't possible because this is a compile time issue and the compiler obviously doesn't know what concrete class it is when it isn't strongly typed.
This isn't a matter of polymorphism. This is a matter of method overloading.
When you pass instance3
to process()
, it will call process(SuperClass obj)
because as far as the JVM is knows, instance3
is a SuperClass
since that's what you declared it as.
If you want the desired behavior (test#3 printing out "Process Instance 1"), you should define your process()
method like so:
private void process(SuperClass obj)
{
System.out.println("Process " + obj.name());
}
And your classes like this:
abstract class SuperClass
{
String name() {
return "SuperClass";
}
}
class Instance1 extends SuperClass
{
String name() {
return "Instance 1";
}
}
class Instance2 extends SuperClass
{
String name() {
return "Instance 2";
}
}
This will work because of dynamic (or late) method binding.
Can you change SuperClass
and Instance
s? If so, you should declare abstract method SuperClass.process()
and implement it in the subclasses.
To make test#3 work, you could add casting: test.process((Instance1)instance3);
. Ugly.
You could also define a factory class, and encapsulate type switching inside it.
You'd have ProcessFactory
with a ProcessObject getInstance(SuperClass obj)
method that returns objects of type ProcessObjectInstance1
or ProcessObjectInstance2
, with a method process()
. ProcessFactory
would work similar to your test#4.
This is what I currently doing. Basically turning the problem inside out a bit. Not sure if this counts as a visitor pattern.
Basically I just added an executeProcessor(Test test) abstract function within SuperClass, and the concrete classes implicitly know which function to call so they can simply use a cast.
What do you think? Is this code smell? Or a neat solution?
public class Test
{
private void process(Instance1 obj)
{
System.out.println("Process Instance 1");
}
private void process(Instance2 obj)
{
System.out.println("Process Instance 2");
}
/**
* @param args
*/
public static void main(String[] args)
{
Test test = new Test();
// Test 1
Instance1 instance1 = test.new Instance1();
instance1.executeProcessor(test);
// Test 2
Instance2 instance2 = test.new Instance2();
instance2.executeProcessor(test);
// Test 3 (note this is not strongly typed)
SuperClass instance3 = test.new Instance1();
instance3.executeProcessor(test);
}
abstract class SuperClass
{
public abstract void executeProcessor(Test test);
}
class Instance1 extends SuperClass
{
@Override
public void executeProcessor(Test test)
{
test.process((Instance1)this);
}
}
class Instance2 extends SuperClass
{
@Override
public void executeProcessor(Test test)
{
test.process((Instance2)this);
}
}
}
As others have pointed out, this isn't really something you can do in Java. If you absolutely must, you can use reflection to do something like:
private void process(SuperClass obj)
{
try
{
Class<? extends SuperClass> klass = obj.getClass();
Method m = this.getClass().getMethod("process", klass);
m.invoke(this, obj);
}
catch (Exception e)
{
// default behavior
System.out.println("Process superclass");
}
}
I would really follow one of the other suggestions however.
Couple suggestions for cleaner way to do what you are doing:
Options 1: Abstract method
Define process()
on Superclass
, and override it on Instance1
and Instance2
:
abstract class SuperClass
{
public void process(String[] args)
{
System.out.println("Process superclass");
}
}
class Instance1 extends SuperClass
{
@Override
public void process(String[] args)
{
System.out.println("Process Instance 1");
}
}
class Instance2 extends SuperClass
{
@Override
public void process(String[] args)
{
System.out.println("Process Instance 2");
}
}
Option 2: Visitor Pattern
Define an class that does the processing, and is an InstanceVisitor
public interface InstanceVisitor
{
public void processSuperclass(Superclass obj, String[] args);
public void processInstance1(Instance1 obj, String[] args);
public void processInstance2(Instance2 obj, String[] args);
}
public class InstanceProcessor implements InstanceVisitor
{
public void processSuperclass(Superclass obj, String[] args)
{
System.out.println("Process superclass");
}
public void processInstance1(Instance1 obj, String[] args)
{
System.out.println("Process Instance 1");
}
public void processInstance2(Instance2 obj, String[] args)
{
System.out.println("Process Instance 2");
}
}
Then each subclass accepts a visitor, and calls the correct method:
abstract class SuperClass
{
public void accept(InstanceVisitor v, String[] args)
{
v.visitSuperclass(this, args);
}
}
class Instance1 extends SuperClass
{
@Override
public void accept(InstanceVisitor v, String[] args)
{
v.visitInstance1(this, args);
}
}
class Instance2 extends SuperClass
{
@Override
public void accept(InstanceVisitor v, String[] args)
{
v.visitInstance2(this, args);
}
}
Either make it a virtual function inside hierarchy:
abstract class SuperClass
{
public void process() { System.out.println("Process super class"); }
}
class Instance1 extends SuperClass
{
public void process() { System.out.println("Process instance1"); }
}
class Instance2 extends SuperClass
{
public void process() { System.out.println("Process instance2"); }
}
or check out various approaches to multiple dispatch that are not natively supported in Java.
精彩评论