【问题标题】:Which Overloaded Method is Called in JavaJava中调用了哪个重载方法
【发布时间】:2018-10-05 10:44:17
【问题描述】:

我有一个基本的继承情况,超类中有一个重载的方法。

public class Person {
    private String name;
    private int dob;
    private String gender;

    public Person(String theName, int birth, String sex){
        name = theName;
        dob = birth;
        gender = sex;
    }

    public void work(){
        getWorkDetail(this);
    }

    public void getWorkDetail(Employee e){
        System.out.println("This person is an Employee");
    }

    public void getWorkDetail(Person p){
        System.out.println("This person is not an Employee");
    }
}

下面的Employee 类扩展了上面的Person 类:

public class Employee extends Person {

    String department;
    double salary;

    public Employee(String theName, int birth, String sex){
        super(theName, birth, sex);
        department = "Not assigned";
        salary = 30000;
    }
}

main 方法只是简单地创建一个Employee 对象(静态和动态类型)并在其上调用.work()

public static void main(String[] args){
    Employee e1 = new Employee("Manager1", 1976, "Female");
    e1.work();
}

这最终会打印出来

This person is not an Employee

通过这个,我认为由于对象e1 的静态和动态类型都是Employee,它会调用以Employee 作为参数的Person 中的重载方法。由于我对此显然错了,我打开了一个调试器,假设在Person 类中的getWorkDetail(this) 行中对“this”的引用必须已经变形为它的超类。然而,这不是我发现的。

显然此时代码this 是一个Employee 对象,但是它仍然选择执行重载方法getWorkDetail(Person p)。谁能解释这种行为?

【问题讨论】:

  • 恕我直言,父类不应该对子类一无所知。我的意思是Person 类中没有getWorkDetail(Employee e) 方法的位置...
  • @zlakad 我认为这些功能应该是静态的?
  • @StarWeaver,静态与否,这个特定问题有什么区别?
  • Person中的this是静态类型Person
  • 正如@zlakad 所暗示的,这个类结构的结构很差。 this 是一种隐含的非静态方法。在这里将this 传递给成员方法确实没有任何意义。

标签: java inheritance overloading


【解决方案1】:

与方法重载不同,方法重载是基于静态类型链接的。而在这种情况下,Person 中的getWorkDetail(this) 只知道Person 类型。

方法重载并非旨在提供动态运行时行为。

要利用动态绑定,您可能需要重新设计代码以覆盖方法:

public static void main(String[] args) throws IOException {
    new Employee("Manager1", 1976, "Female").getWorkDetail();
    new Person("Manager1", 1976, "Female").getWorkDetail();
}

并根据实现类修改行为。当然,您可以重载方法,只要您在需要时也注意覆盖重载的方法。

class Person {
    private String name;
    private int dob;
    private String gender;

    public Person(String theName, int birth, String sex) {
        name = theName;
        dob = birth;
        gender = sex;
    }

    public void getWorkDetail() {
        System.out.println("This person is not an Employee");
    }
}

class Employee extends Person {

    String department;
    double salary;

    public Employee(String theName, int birth, String sex) {
        super(theName, birth, sex);
        department = "Not assigned";
        salary = 30000;
    }

    public void getWorkDetail() {
        System.out.println("This person is an Employee");
    }
}

【讨论】:

  • @Ernest Kiwele 谢谢!我不知道 Java 不为方法重载提供动态行为。这样就搞定了。
  • 您可能还需要注意,JVM 首先查找具有您指定的签名的非重写方法,然后再查找与您的签名匹配的重写方法。但理想情况下,您希望利用 Java 的继承机制来重构您的代码,就像 @Ernest 展示的那样
【解决方案2】:

重载解决发生在编译时,而不是运行时。

因此,当您调用 getWorkDetails(this) 时,this 被假定为 Person(这是静态类型),因此称为相应的重载。

注意:在 Employee 类中使用 this 会使其成为 Employee 类型。您可以像这样在Employee 中重载work() 来验证这一点。

class Employee extends Person {
    ...

    public void work() {
        getWorkDetails(this); // This should print "This person is an Employee"
    }
}

【讨论】:

    【解决方案3】:

    问题具体解决方案

    在某些语言中,参数被解析为它们的动态类型,但在 java 中不是。编译器已经在编译时确定了您的getWorkDetail(this); 的去向。 this 的类型为 Person,因此会调用 getWorkDetail(Person e)。在您的具体情况下,解决方案非常明显。正如其他人已经指出的那样,您需要在 Employee 类中覆盖 getWorkDetail()

    将方法解析为其动态参数类型

    要解决在运行时解析参数类型的一般问题,应避免使用instanceof 运算符,因为它通常会导致代码不干净。

    如果您有两个不同的类,那么上述简单的解决方案将不再可能。在这些情况下,您必须使用visitor pattern

    考虑以下类:

    public interface Animal {
        default void eat(Food food) {
            food.eatenBy(this);
        }
    
        void eatMeat(Meat meat);
    
        void eatVegetables(Vegetables vegetables);
    }
    
    public class Shark implements Animal {
        public void eatMeat (Meat food) {
            System.out.println("Tasty meat!");
        }
    
        public void eatVegetables (Vegetables food) {
            System.out.println("Yuck!");
        }
    }
    
    public interface Food {
        void eatenBy(Animal animal);
    }
    
    public class Meat implements Food {
        public void eatenBy(Animal animal) {
            animal.eatMeat(this);
        }
    }
    
    public class Vegetables implements Food {
        public void eatenBy(Animal animal) {
            animal.eatVegetables(this);
        }
    }
    

    你可以这样称呼:

    Animal animal = new Shark();
    Food someMeat = new Meat();
    Food someVegetables= new Vegetables();
    animal.eat(someMeat);        // prints "Tasty meat!"
    animal.eat(someVegetables);  // prints "Yuck!"
    

    遵循访问者模式,调用Animal.eat 将调用Food.eatenBy,由Meat Vegetables 实现。这些类将调用更具体的eatMeateatVegetables 方法,它们使用正确的(动态)类型。

    【讨论】:

      【解决方案4】:

      通话偏好

      class Foo {
          static void test(int arg) { System.out.println("int"); }
          static void test(float arg) { System.out.println("float"); }
          static void test(Integer arg) { System.out.println("Integer"); }
          static void test(int... arg) { System.out.println("int..."); }
      
          public static void main(String[] arg) {
              test(6);
          }
      }
      

      输出将 int 打印在控制台上。现在你注释第一个test() 方法,看看输出是什么。

      这是原始数据类型中的首选项层次结构。现在来派生类型声明一个类 FooChild 像这样

      class FooChild extends Foo {
      
      }
      

      并在Foo 中创建两个新方法

      static void testChild(Foo foo) { System.out.println("Foo"); }
      static void testChild(FooChild fooChild) { System.out.println("FooChild"); }
      

      然后在 main 方法中尝试像这样testChild(new FooChild()); 调用testChild

      【讨论】:

        【解决方案5】:

        getWorkDetail(this) 不知道子类是什么。改为调用 getWorkDetail。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-05-08
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多