使用相同的方法签名但不同的返回类型实现两个接口

时间:2015-09-14 08:28:36

标签: java oop inheritance interface

假设我有两个界面:

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在较低的层面上做了什么?

3 个答案:

答案 0 :(得分:4)

  

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

你可能会感到惊讶:它不会编译,但如果确实如此,它就会运行。

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

  

测试不是抽象的,并且不会覆盖InterfaceA中的抽象方法getStuff()   Testing中的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的形式参数类型是相同的。

  •   

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

答案 1 :(得分:2)

  

未解决的编译问题

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

并且没有编译的原因,你没有完全实现所有的接口方法。您只使用InterfaceB方法,而不是InterfaceA方法。

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

答案 2 :(得分:0)

“未解决的编译问题”特别是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()是两种完全不同的方法,第二种方法在我们的课堂中不存在。