【问题标题】:Implement two interfaces with the same method signature but different return type实现两个具有相同方法签名但返回类型不同的接口
【发布时间】:2015-09-14 08:28:35
【问题描述】:

假设我有两个接口:

public interface InterfaceA {
 public int getStuff();

}

public interface InterfaceB {
 public String getStuff();

}

现在我有一个实现这两个接口的类:

public class Testing implements InterfaceA, InterfaceB{
 public String getStuff() {
   return "one";
 }

}

我的问题是,这会编译吗?如果它编译了,它会运行吗?

编辑:

我确实在我的计算机上尝试过,发现它已编译并运行,但是当调用 getStuff() 时,程序以

终止

未解决的编译问题

知道 Java 在较低级别上做了什么吗?

【问题讨论】:

  • 既然你没有实现 InterfaceA 的方法,我不会指望它。
  • it compiled and it did run :我认为它不会编译。能否请您发布编译的代码?
  • @Kartic 可能是 IDE 问题或项目尚未正确构建。

标签: java oop inheritance interface


【解决方案1】:

这会编译吗?如果它编译了,它会运行吗?

您可能会感到惊讶:它不会编译,但如果可以,它会运行。

Java 编译器要求您实现在类上声明的接口中声明的所有方法。在您的情况下,您需要实现 both public int getStuff();public String getStuff();。如果不这样做,编译器将显示错误:

测试不是抽象的,不会覆盖 InterfaceA 中的抽象方法 getStuff() 测试中的 getStuff() 无法在 InterfaceA 中实现 getStuff()

现在,对于 Java 虚拟机,在您的类中同时包含这两种方法会非常好。但是,Java 编译器不允许这样做:

方法 getStuff() 已在类 Testing 中定义

因此您的代码不会以一种或另一种方式编译。


您知道 Java 在较低级别上做了什么吗?

让我们回到 JVM 本身并做一些邪恶的事情。看看这个jasmin 程序:

.class public Testing
.super java/lang/Object
.implements InterfaceA
.implements InterfaceB

.method public static getStuff()I
  ldc 666
  ireturn
.end method

.method public static getStuff()Ljava/lang/String;
  ldc "Evil stuff"
  areturn
.end method

.method public static main([Ljava/lang/String;)V
  .limit stack 10
  getstatic java/lang/System/out Ljava/io/PrintStream;
  dup
  invokestatic Testing/getStuff()I
  invokevirtual java/io/PrintStream/println(I)V
  invokestatic Testing/getStuff()Ljava/lang/String;
  invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
  return
.end method

我们声明一个类Testing,它声明了两个具有相同名称和参数的方法,唯一的区别是返回类型。它实现了这两个接口并有一个public static void main(String[] args) 方法,该方法调用这两个方法(getStuff()getStuff())并显示结果。而且这个程序确实可以运行,虚拟机也不会抱怨什么。

为什么有效? JVM specification 定义了这样一个方法的描述符

MethodDescriptor:
    ( ParameterDescriptor* ) ReturnDescriptor

此描述符用于(除了类和方法名称)在运行时解析方法。因为它包含ReturnDescriptor,所以可以有多个同名的方法。

编译器呢?编译器不使用它们的描述符 来识别方法,而是使用它们的签名Java language specification 包含以下内容:

两个方法具有相同的签名,如果它们具有相同的名称和 参数类型。

两个方法或构造函数声明 M 和 N 具有相同的参数 如果满足以下所有条件,则类型:

  • 它们具有相同数量的形参(可能为零)

  • 它们具有相同数量的类型参数(可能为零)

  • 令 A1, ..., An 为 M 的类型参数,令 B1, ..., Bn 为 N 的类型参数。将 N 的类型中出现的 Bi 重命名为 Ai 后,对应类型变量的边界相同,M和N的形参类型相同。

因此,方法在源代码中无法通过返回类型来区分,编译器禁止声明仅返回类型不同的方法。

【讨论】:

    【解决方案2】:

    你不能这样做,它不会编译。

    直接取自doc:

    interface Fish       { int    getNumberOfScales(); }
    interface StringBass { double getNumberOfScales(); }
    class Bass implements Fish, StringBass {
        // This declaration cannot be correct,
        // no matter what type is used.
        public ?? getNumberOfScales() { return 91; }
    }
    

    不可能声明一个名为 getNumberOfScales 的方法 签名和返回类型与两者的兼容 在接口 Fish 和接口 StringBass 中声明的方法, 因为一个类不能有多个具有相同签名的方法 和不同的原始返回类型(第 8.4 节)。因此它是 单个类不可能同时实现接口 Fish 和 接口 StringBass

    因此你不能这样做并且它不会编译。在您的情况下,它会抱怨该方法正在尝试使用不兼容的类型,因为这两个方法具有相同的名称但返回类型不同。

    【讨论】:

      【解决方案3】:

      未解决的编译问题

      仅当您尝试运行带有编译器错误的程序时才会发生此异常。

      以及没有编译的原因,你还没有完全实现所有的接口方法。你只为InterfaceB 方法做,而不是InterfaceA 方法。

      注意:如果您没有在 IDE 中看到 ant 错误,请重新启动或清理您的项目一次。

      【讨论】:

        【解决方案4】:
        interface I1 { void f(); }
        interface I2 { int f(int i); }
        interface I3 { int f(); }
        class C { public int f() { return 1; } }
        
        class C2 implements I1, I2 {
          public void f() {}
          public int f(int i) { return 1; } // overloaded
        }
        
        class C3 extends C implements I2 {
          public int f(int i) { return 1; } // overloaded
        }
        
        class C4 extends C implements I3 {
          // Identical, no problem:
          public int f() { return 1; }
        }
        

        之所以出现困难,是因为覆盖、实现和重载令人不快地混合在一起,而且重载的方法不能仅在返回类型上有所不同。

        现在试试这个:- interface I4 extends I1, I3 {}

        您将得到的错误:- 接口 I3 和 I1 不兼容;都定义了 f( ),但返回类型不同

        在旨在组合的不同接口中使用相同的方法名称通常也会导致代码的可读性混乱。尽量避免。

        查看this链接

        【讨论】:

          【解决方案5】:

          “未解决的编译问题”特别是 Eclipse 编译器功能 (ECJ)。即使您的代码中有非常严重的错误,该编译器也可以生成编译文件。只要你不运行错误的代码,你会没事的。在您的情况下,当您在 getStuff() 方法中遇到问题时,Eclipse 会创建如下所示的方法:

          public class Testing implements InterfaceA, InterfaceB {
              @Override
              public String getStuff() {
                  throw new Error("Unresolved compilation problem: \n"+
                   "\tThe return type is incompatible with InterfaceA.getStuff()\n";
              }
          }
          

          因此,只要您不启动此方法就可以了。一旦你启动它,你就会抛出一个错误。

          如果你用 javac 编译,它根本不会创建 .class 文件。所以回到你原来的问题,不,Java 语言不允许这样做(但是 Java 字节码允许)。

          如果您尝试调用根本不存在的InterfaceA 方法(在Eclipse 编译之后)会发生什么,这也很有趣:

          public class Testing implements InterfaceA, InterfaceB {
              @Override
              public String getStuff() { // compilation error here, but class is still generated
                  return "one";
              }
          
              public static void main(String[] args) {
                  System.out.println("Hello");
                  InterfaceA a = new Testing();
                  int i = a.getStuff();
                  System.out.println(i);
              }
          }
          

          当你运行它时,你会看到:

          Hello
          Exception in thread "main" java.lang.AbstractMethodError: compileTest.Testing.getStuff()I
              at compileTest.Testing.main(Testing.java:12)
          

          也就是说:当你试图调用一个没有实现的抽象方法时,JVM 会抛出一个AbstractMethodError。从 JVM 的角度来看,String getStuff()int getStuff() 是两种完全不同的方法,而第二种方法在我们的课程中只是没有。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-05-31
            • 1970-01-01
            • 1970-01-01
            • 2013-02-28
            • 2011-12-13
            • 1970-01-01
            相关资源
            最近更新 更多