老問題,但沒有答案提供了Java中的具體的解決方案來解決這個問題的清潔方式。
其實並不容易但很有趣的問題。這是我的貢獻。
確定要調用的方法是在編譯時決定的。是否有 解決方法來避免使用instanceof運算符?
正如在優秀的@DaveFar答案中所說的,Java僅支持單派遣方法。
在這種調度模式下,編譯器通過依賴聲明的參數類型而不是它們的運行時類型來限制調用的方法。
我有一個集合(或列表或數組列表)中,我想提出兩個 字符串值和雙精度值。
要以乾淨的方式解決答案並使用雙派遣,我們必須爲操縱數據提供抽象。
爲什麼?
這裏一個天真的遊客的方法來說明這個問題:
public class DisplayVisitor {
void visit(Object o) {
System.out.println("object"));
}
void visit(Integer i) {
System.out.println("integer");
}
void visit(String s) {
System.out.println("string"));
}
}
現在,問題:如何訪問類可以調用visit()
方法?
雙調度實現的第二次調度依賴於接受訪問的類的「this」上下文。
所以我們必須Integer
,String
和Object
班accept()
方法來執行本次分派:
public void accept(DisplayVisitor visitor){
visitor.visit(this);
}
但不可能!訪問的課程是內置課程:String
,Integer
,Object
。
所以我們沒有辦法添加這個方法。
無論如何,我們不想補充說。
所以要實現雙派遣,我們必須能夠修改我們想要作爲第二派遣參數傳遞的類。
因此,我們不會操縱Object
和List<Object>
作爲聲明類型,而是操縱Foo
和List<Foo>
,其中Foo
類是包含用戶值的包裝。
這裏是Foo
接口:
public interface Foo {
void accept(DisplayVisitor v);
Object getValue();
}
getValue()
返回用戶值。
它指定Object
作爲返回類型,但Java支持協變量返回(自1.5版本以來),因此我們可以爲每個子類定義更具體的類型以避免向下轉換。
ObjectFoo
public class ObjectFoo implements Foo {
private Object value;
public ObjectFoo(Object value) {
this.value = value;
}
@Override
public void accept(DisplayVisitor v) {
v.visit(this);
}
@Override
public Object getValue() {
return value;
}
}
StringFoo
public class StringFoo implements Foo {
private String value;
public StringFoo(String string) {
this.value = string;
}
@Override
public void accept(DisplayVisitor v) {
v.visit(this);
}
@Override
public String getValue() {
return value;
}
}
IntegerFoo
public class IntegerFoo implements Foo {
private Integer value;
public IntegerFoo(Integer integer) {
this.value = integer;
}
@Override
public void accept(DisplayVisitor v) {
v.visit(this);
}
@Override
public Integer getValue() {
return value;
}
}
這裏是DisplayVisitor類訪問Foo
子類:
public class DisplayVisitor {
void visit(ObjectFoo f) {
System.out.println("object=" + f.getValue());
}
void visit(IntegerFoo f) {
System.out.println("integer=" + f.getValue());
}
void visit(StringFoo f) {
System.out.println("string=" + f.getValue());
}
}
這裏是一個示例代碼來測試實施:
public class OOP {
void test() {
List<Foo> foos = Arrays.asList(new StringFoo("a String"),
new StringFoo("another String"),
new IntegerFoo(1),
new ObjectFoo(new AtomicInteger(100)));
DisplayVisitor visitor = new DisplayVisitor();
for (Foo foo : foos) {
foo.accept(visitor);
}
}
public static void main(String[] args) {
OOP oop = new OOP();
oop.test();
}
}
輸出:
字符串=字符串
string = another字符串
整數= 1
對象= 100
改進實施
實際實現需要對每個BUIT入式引入特定包裝類的我們想要包裝。如上所述,我們沒有選擇操作雙重派單。
但需要注意的是,在富子類的重碼可避免:
private Integer value; // or String or Object
@Override
public Object getValue() {
return value;
}
我們確實可以引入保存用戶價值,並提供一個訪問到一個抽象的泛型類:
public abstract class Foo<T> {
private T value;
public Foo(T value) {
this.value = value;
}
public abstract void accept(DisplayVisitor v);
public T getValue() {
return value;
}
}
現在Foo
子類更輕申報:
public class IntegerFoo extends Foo<Integer> {
public IntegerFoo(Integer integer) {
super(integer);
}
@Override
public void accept(DisplayVisitor v) {
v.visit(this);
}
}
public class StringFoo extends Foo<String> {
public StringFoo(String string) {
super(string);
}
@Override
public void accept(DisplayVisitor v) {
v.visit(this);
}
}
public class ObjectFoo extends Foo<Object> {
public ObjectFoo(Object value) {
super(value);
}
@Override
public void accept(DisplayVisitor v) {
v.visit(this);
}
}
應該修改test()
方法,以聲明List<Foo>
聲明中Foo
類型的通配符類型(?
)。
void test() {
List<Foo<?>> foos = Arrays.asList(new StringFoo("a String object"),
new StringFoo("anoter String object"),
new IntegerFoo(1),
new ObjectFoo(new AtomicInteger(100)));
DisplayVisitor visitor = new DisplayVisitor();
for (Foo<?> foo : foos) {
foo.accept(visitor);
}
}
事實上,如果真的需要,我們可以通過引入Java代碼生成進一步簡化Foo
子類。
聲明該子類:
public class StringFoo extends Foo<String> {
public StringFoo(String string) {
super(string);
}
@Override
public void accept(DisplayVisitor v) {
v.visit(this);
}
}
可能就這麼簡單聲明一個類上添加註釋:
@Foo(String.class)
public class StringFoo { }
哪裏Foo
是在編譯時處理的自定義註解。
因爲這就是Java重載的工作原理。 – 2012-03-18 14:35:16
但是,該對象在運行時是一個字符串,並且有一個爲字符串定義的方法。我究竟做錯了什麼? – 2012-03-18 14:36:50
你假定選擇哪個重載調用是在運行時進行的。不是;它是在編譯時根據參數的靜態類型決定的。 – 2012-03-18 14:37:51