代码之家  ›  专栏  ›  技术社区  ›  Cuga

为什么我的重载方法不使用最特定的实例?[副本]

  •  0
  • Cuga  · 技术社区  · 5 年前

    我做了个小测试:

    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?!?!?
        }
    }
    

    在测试中,参数类型似乎是在编译时决定的,而不是在运行时。为什么?

    Polymorphism vs Overriding vs Overloading
    Try to describe polymorphism as easy as you can

    好的,要调用的方法是在编译时决定的。是否有避免使用 instanceof 接线员?

    0 回复  |  直到 8 年前
        1
  •  19
  •   DaoWen    11 年前

    这篇文章记录了voo的答案,并给出了延迟绑定的详细信息/替代方案。

    一般的jvm只使用 单次调度 method tables (类似于C++的虚拟表)。你可以在 the HotSpot Wiki .

    多次调度 对于您的参数,请查看

    • groovy . 但据我所知,它有一个过时的、缓慢的多重分派实现(参见。 this performance comparison ),例如,没有缓存。
    • clojure ,但这与Java完全不同。
    • MultiJava ,它为Java提供了多个分派。另外,您可以使用
      • this.resend(...) 而不是 super(...)
      • 值调度(下面的代码示例)。

    如果你想的话 坚持Java ,你可以


    价值调度:

    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'
      }
    }
    
        2
  •  12
  •   Voo    13 年前

    你想要的是两倍或更一般的 multiple dispatch

    可能java没有它的主要原因,是因为它的性能受到了影响,因为重载解析必须在运行时而不是编译时完成。通常的方法是 visitor pattern -很难看,但就是这样。

        3
  •  3
  •   unholysampler    13 年前

    变量 传递给函数。它不使用实际实例的类型。

        4
  •  3
  •   davidxxx    7 年前

    旧的问题,但没有答案,在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() 方法?

    accept() Integer , String Object 执行第二次分派的类:

    public void accept(DisplayVisitor visitor){
        visitor.visit(this);
    }
    

    但不可能!访问的类是内置的类: , 整数 , .
    所以我们没有办法添加这个方法。

    因此,要实现双重分派,我们必须能够修改要在第二个分派中作为参数传递的类。
    对象 List<Object> Foo List<Foo> 类是保存用户值的包装器。

    public interface Foo {
        void accept(DisplayVisitor v);
        Object getValue();
    }
    

    getValue() 返回用户值。
    它规定 作为返回类型,但Java支持协方差返回(自1.5版本以来),因此我们可以为每个子类定义一个更具体的类型,以避免下推。

    对象foo

    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;
        }
    
    }
    

    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;
        }
    
    }
    

    这是 课堂参观

    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=另一个字符串

    整数=1

    对象=100


    如前所述,我们没有选择操作双重调度。
    子类可以避免:

    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;
        }
    
    }
    

    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() 方法应修改为声明通配符类型( ? )为了 列表<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代码生成子类。

    声明此子类:

    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 { }
    

    在哪里? 是在编译时处理的自定义注释。

        5
  •  1
  •   NimChimpsky    13 年前

        6
  •  1
  •   Lyubomyr Shaydariv    9 年前

    Object /对象(基本类型除外)。将字符串和整数存储为对象,然后调用 prove instanceof 关键字。 Check this link

    void prove(Object o){
       if (o instanceof String)
        System.out.println("String");
       ....
    }