【问题标题】:Overloaded Java method and inheritance, call most specific implementation重载Java方法和继承,调用最具体的实现
【发布时间】:2019-09-07 19:14:53
【问题描述】:

如果我有一个方法的多个实现,其中一个采用特定类的实例,另一个采用它的超类,我如何强制调用最具体的一个?

让我们看下面的代码:

public static class Cat {};
public static class DomesticCat extends Cat {};
public static class Lion extends Cat {};

public static class CatOwner {
    public Cat cat;
}

public static void identifyCat(Cat cat) {
    System.out.println("Generic cat");
}

public static void identifyCat(Lion lion) {
    System.out.println("Lion");
}

现在如果我这样做:

identifyCat(new Cat());
identifyCat(new Lion());
identifyCat(new DomesticCat());

我明白了:

Generic cat
Lion
Generic cat

到目前为止,一切都很好,Java 选择了最具体的一个——除了匹配是基于声明的类(或我将其转换为的任何内容)完成的,而不是实际的类。例如以下代码:

Cat simba = new Lion();
identifyCat(simba);

调用identifyCat(Cat),而不是identifyCat(Lion)——因为simba被声明为Cat

对于一个更现实的用例,让我们假设 Cat 及其所有子类都定义在一个外部库中(我无法控制),我从那里得到一个 Cat 实例,无法控制实例的实际类,例如:

Cat cat = CatFactory.getCat();
identifyCat(cat);

这将始终调用identifyCat(Cat),即使返回的Cat 实例是Lion 类型。

考虑到我可能对identifyCat() 有更多的实现,而且它们不仅仅是识别实例(例如,它们可能与该特定子类引入的成员交互):有没有一种简单的方法可以让我的代码调用identifyCat(Lion) 获取Lion(即使声明为Cat),而不求助于枚举if 语句à la if (cat instanceof Lion) identifyCat((Lion) cat)

【问题讨论】:

  • Double Dispatch 来救援。
  • 其实我觉得你的代码违反了Liskov Substitution Principle。如果对于您的业务规则,区分CatLion 至关重要,那么您不应该像现在这样拥有designCat(Cat cat)

标签: java inheritance overloading


【解决方案1】:

如果你不想在 switch case 中使用 instanceof 运算符。

由于方法重载发生在编译时,但你想要实现的是动态的方法调用解析,你需要使用反射。

import java.beans.Statement;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

class Cat {
}

class DomesticCat extends Cat {
}

class Lion extends Cat {
}

class CatOwner {
    public Cat cat;
}

public class Abc {



    public  void identifyCat(Cat cat) {
        System.out.println("Generic cat");
    }

    public  void identifyCat(Lion lion) {
        System.out.println("Lion");
    }
    public  void identifyCat(Object lion) {
        System.out.println("object");
    }

    public void invokeSpecific(Object object,String methodName) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException
    {


        Statement s = new Statement(this, methodName, new Object[] { object });
        Method findMethod = s.getClass().getDeclaredMethod("getMethod", Class.class,
                                                           String.class, Class[].class);
        findMethod.setAccessible(true);
        Method mostSpecificMethod = (Method) findMethod.invoke(null, this.getClass(),
                methodName, new Class[] { object.getClass() });
        mostSpecificMethod.invoke(this, object);
    }


    public static void main(String[] args) throws  NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        Abc a = new Abc();  
        Object simba = new Lion();
        a.invokeSpecific(simba,"identifyCat");
    }

}

你也可以在这里找到更多类似的方法Finding most specific overloaded method using MethodHandle

【讨论】:

  • 我不认为Reflection 是一种方式。
  • @jpact 你能详细说明原因吗?
  • 清晰、维护、简单。它当然有效,但从长远来看,维护这样的代码会很困难。
【解决方案2】:

重载的方法是静态绑定的,而重载的方法是动态绑定的。我建议在每个类中创建一个 getType 方法来返回类型并在静态方法中使用它来打印。

复习 - 以下是静态和动态绑定之间的一些重要区别:

  • Java 中的静态绑定发生在编译时,而动态绑定发生在运行时。
  • 私有、最终和静态方法和变量使用静态绑定并由编译器绑定,而虚拟方法在运行时根据运行时对象绑定。
  • 静态绑定使用类型(Java 中的类)信息进行绑定,而动态绑定使用对象来解析绑定。
  • 重载的方法使用静态绑定进行绑定,而重写的方法在运行时使用动态绑定进行绑定。

【讨论】:

  • 问题是,Cat 及其子类不是我的类,而是取自库,无法向它们添加代码。此外,真正的代码不仅仅是打印类名,它是关于涉及特定类成员的逻辑——识别类只是说明如何为不同类执行不同代码的一种方式。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-24
  • 1970-01-01
  • 2017-01-07
  • 2011-02-27
  • 1970-01-01
相关资源
最近更新 更多