Java中的重载和多重分派
我有一个集合(或列表或数组列表),我想在其中放置字符串值和双精度值。我决定让它成为一个对象集合,并使用重载ond多态性,但我做了一些错误的事情 我运行了一个小测试:Java中的重载和多重分派,java,oop,polymorphism,overloading,Java,Oop,Polymorphism,Overloading,我有一个集合(或列表或数组列表),我想在其中放置字符串值和双精度值。我决定让它成为一个对象集合,并使用重载ond多态性,但我做了一些错误的事情 我运行了一个小测试: public class OOP { void prova(Object o){ System.out.println("object"); } void prova(Integer i){ System.out.println("integer"); } void
public class OOP {
void prova(Object o){
System.out.println("object");
}
void prova(Integer i){
System.out.println("integer");
}
void prova(String s){
System.out.println("string");
}
void test(){
Object o = new String(" ");
this.prova(o); // Prints 'object'!!! Why?!?!?
}
public static void main(String[] args) {
OOP oop = new OOP();
oop.test(); // Prints 'object'!!! Why?!?!?
}
}
在测试中,参数类型似乎是在编译时决定的,而不是在运行时决定的。为什么呢
这个问题涉及:
编辑:
好的,要调用的方法在编译时决定。是否有一种方法可以避免使用
instanceof
操作符?这不是多义性,您只是重载了一个方法,并使用对象类型的参数来调用它。当调用重载的方法时,Java会根据传递给函数的变量的类型来选择限制性最强的类型。它不使用实际实例的类型。Java中的所有内容都是对象
/Object(基本类型除外)。将字符串和整数存储为对象,然后在调用prove
方法时,它们仍然被称为对象。您应该查看一下关键字的实例
您想要的是双重的或更一般的,实际上是用其他语言实现的(我想到的是CommonLisp)
java没有它的主要原因大概是因为它会带来性能损失,因为重载解析必须在运行时完成,而不是在编译时完成。通常的解决方法是-非常丑陋,但事实就是这样。这篇文章介绍了voo的答案,并给出了后期绑定的详细信息/替代方案
一般JVM只使用单一分派:运行时类型只考虑接收方对象;对于方法参数,考虑了静态类型。使用优化的高效实现非常容易(类似于C++的虚拟表)。您可以在中找到详细信息,例如
如果您想要为您的参数多次分派,请查看
- 。但据我最近所知,它有一个过时的、缓慢的多重分派实现(参见示例),例如,没有缓存
- ,但这与Java完全不同
- ,它为Java提供了多个分派。此外,您可以使用
this.重新发送(…)
而不是super(…)
以调用封闭方法中最具体的重写方法李>
- 值分派(下面的代码示例)
如果你想坚持使用Java,你可以
- 通过在细粒度的类层次结构上移动重载方法来重新设计应用程序。第41项中给出了一个示例(明智地使用重载)李>
- 使用一些设计模式,如策略、访问者、观察者。这些通常可以解决与多重分派相同的问题(即,在这些情况下,您可以使用多重分派为这些模式提供微不足道的解决方案)
价值分配:
class C {
static final int INITIALIZED = 0;
static final int RUNNING = 1;
static final int STOPPED = 2;
void m(int i) {
// the default method
}
void m(int@@INITIALIZED i) {
// handle the case when we're in the initialized `state'
}
void m(int@@RUNNING i) {
// handle the case when we're in the running `state'
}
void m(int@@STOPPED i) {
// handle the case when we're in the stopped `state'
}
}
旧问题但没有答案提供了一个具体的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);
}
但是不可能!访问的类是内置类:字符串
,整数
,对象
因此,我们无法添加此方法。
不管怎样,我们不想再加上这一点。
因此,为了实现双重分派,我们必须能够修改要在第二次分派中作为参数传递的类。
因此,我们将操作Foo
和List
作为声明的类型,而不是操作Object
和List
,其中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;
}
}
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;
}
}
IntegerFoo
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;
}
}
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();
}
}
我们确实可以引入一个抽象泛型类,该类保存用户值并提供访问器:
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;
}
}
并且应该修改test()
方法,为列表中的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);
}
}
可以简单到声明一个类并在上添加注释:
@Foo(String.class)
public class StringFoo { }
其中Foo
是在编译时处理的自定义注释。我支持您的答案(+1),
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 { }