Java接口:在实现类中使用默认方法实现

4

我有一个接口,以及它的两个或更多实现,例如:

public interface IProcessor {
  default void method1() {
   //logic
  }
  default void method2() {
   //logic
  }
  public void method3();
  public void method4();
}

在这里,method1method2的实现逻辑在多个实现类中是通用的,因此被定义为默认方法。因此,只有其余的方法可以在实现类中被覆盖。
public CarImpl implements IProcessor {
  @Override 
  public void method3() {
    //logic
  }
  @Override 
  public void method4() {
    //logic
  }
}

public VanImpl implements IProcessor {
  @Override 
  public void method3() {
    //logic
  }
  @Override 
  public void method4() {
    //logic
  }
}

有没有更好的方法可以在不使用默认方法和避免在各个实现类中出现冗余代码的情况下完成这个操作?因为如果在默认方法中增加代码,则接口看起来会很臃肿。

你可以创建一个抽象类来保存这些实现,并将其作为两个Impl的公共超类。在接口上有默认方法之前,人们就是这样做的。 - Thilo
3
“只有剩下的方法可以在实现类中被重写。”这句话是不正确的。接口中的默认方法也可以在实现类中被重写,如果实现者需要其他行为,他们可以自由地这样做。 - scottb
1
顺便提一下,在Java中通常不会在接口名称前加上'I'。名称本身就足够了。 - Software Engineer
2个回答

4

默认方法可以被覆盖

您说:

因此,只有实现类中的其他方法可以被覆盖。

这里的“只有”一词是不正确的。接口中的默认方法确实可以被实现类覆盖。因此,“default”关键字在此处使用,意味着:如果运行时没有其他实现代码,则使用此方法代码。

这里有一个荒谬的例子,我们定义了一个接口Fruit,其中包含一个默认方法isJuicy,返回true。我们有两个子类OrangeBanana,第一个没有覆盖isJuicy,因此其行为来自默认方法。第二个演示了您可以覆盖默认方法。在这里,我们看到覆盖返回false

package work.basil.example;

public class OverridingDefault
{
    public static void main ( String[] args )
    {
        OverridingDefault app = new OverridingDefault();
        app.demo();
    }

    private void demo ( )
    {
        System.out.println( "new Orange().isJuicy(): " + new Orange().isJuicy() );
        System.out.println( "new Banana().isJuicy(): " + new Banana().isJuicy() );
    }

    public interface Fruit
    {
        default boolean isJuicy ( )
        {
            return true;
        }
    }

    public class Orange implements Fruit
    {
    }

    public class Banana implements Fruit
    {
        @Override
        public boolean isJuicy ( )
        {
            return false;
        }
    }
}

运行时。

new Orange().isJuicy(): true
new Banana().isJuicy(): false

优先使用抽象类而不是默认方法

你问:

有没有更好的方法可以在没有默认方法和各自实现类中的冗余代码的情况下实现这一点?

我建议您不要使用接口的default方法来实现。

Java接口添加默认方法的想法和技术并不是作为一项功能本身,而是作为解决另一个问题的解决方案:为了支持新的lambda特性而将功能添加到现有接口上,但不会破坏数百万Java程序员的现有代码,否则将向现有接口添加方法。通过在接口上发明default方法,Java团队能够在现有接口上添加更多方法,同时减轻所有现有实现必须实现这些新方法的需要。新功能不会破坏代码,这是Java的标志。

正如Brian Goetz 2013-09年在State of the Lambda中所述:

默认方法(…)的目的是使接口在其初始发布后以兼容的方式进行演进。

我个人的看法是,程序员通常不会期望行为内置于您的接口中。在Java中,接口的经典用途是根据方法签名定义合同,而不是定义行为(代码)。因此,请仅在最后一种情况下将行为(代码)添加为默认方法。

相反,将您的接口定义为合同,不包含默认方法。至少起初没有默认方法;您可能会像Brian Goetz和Java团队一样发现需要稍后添加默认方法。但是,从只有合同开始。

然后定义一个实现该接口的抽象类。任何要在各种子类之间共享的行为(代码)都可以移动到此抽象类中。

然后继续定义子类、具体类,这些类继承自您的抽象类。

使用接口+抽象类+具体类的经典和常见方法,您可以灵活地进行更改,并使测试变得更容易(使用存根而不是真实类),同时有效地共享来自一个位置的代码,但允许需要时进行覆盖。


我完全同意。就个人而言,我不会将方法添加到接口作为语言规范的一部分。我理解其意图,并认为支持需要此功能的百万分之一的边缘情况只会让学习该语言的人感到困惑。任何在我手下工作的工程师必须有一个相当充分的理由来使用这样的东西,否则他们将被送去参加面向对象原则的复习课程。最好避免使用。 - Software Engineer
默认方法的目的是在接口初始发布后以兼容的方式使其得以发展。这是 Joshua Bloch 和 Brian Goetz 意见不一致的地方。Bloch 断言,如果默认实现不能保留客户端中的所有不变量,则在发布后演化接口可能会破坏客户端代码。然而,他确实推广在发布前在接口中使用默认方法为客户端提供骨架实现。即,在发布前尽可能使用默认方法而不是抽象类(如果可行)。 - scottb
@scottb,你能引用一下Bloch的观点来源吗?那会帮助那些想进一步探讨这个主题的人们。 - Basil Bourque

1
你没有做错任何事情,虽然我会说在这种情况下抽象类更加适合,当你有多个实现共享方法实现(method1method2)时。
如果你仍然想让它成为接口,请创建一个接口和一个扩展该接口的抽象类。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接