Java 继承、组合和默认方法

Java 继承、组合和默认方法,java,inheritance,java-8,composition,default-method,Java,Inheritance,Java 8,Composition,Default Method,人们通常承认,通过继承扩展接口的实现并不是最佳实践,而组合(例如从头开始重新实现接口)更易于维护 这是因为接口契约强制用户实现所有所需的功能。然而,在Java8中,默认方法提供了一些可以“手动”重写的默认行为。考虑下面的例子:我想设计一个用户数据库,它必须具有列表的功能。为了提高效率,我选择使用ArrayList来支持它 public class UserDatabase extends ArrayList<User>{} 公共类UserDatabase扩展了ArrayList{}

人们通常承认,通过继承扩展接口的实现并不是最佳实践,而组合(例如从头开始重新实现接口)更易于维护

这是因为接口契约强制用户实现所有所需的功能。然而,在Java8中,默认方法提供了一些可以“手动”重写的默认行为。考虑下面的例子:我想设计一个用户数据库,它必须具有列表的功能。为了提高效率,我选择使用ArrayList来支持它

public class UserDatabase extends ArrayList<User>{}
公共类UserDatabase扩展了ArrayList{} 这通常不会被认为是一个伟大的实践,如果你真的想要列表的全部功能,并且遵循通常的“组合优于继承”的格言,你会更愿意:

public类UserDatabase实现列表{
//在这里实现,使用ArrayList类型字段或装饰器模式等。
}
但是,如果不注意,一些方法,例如spliterator()将不需要重写,因为它们是列表接口的默认方法。需要注意的是,List的spliterator()方法的性能比ArrayList的spliterator()方法差得多,后者已针对ArrayList的特定结构进行了优化

这迫使开发人员

  • 请注意,ArrayList有自己更高效的spliterator()实现,并手动重写自己的List或
  • 使用默认方法会损失大量性能
    所以问题是:在这种情况下,一个人应该更喜欢组合而不是继承,这仍然是“正确的”吗

    我不太明白这里的大问题。您仍然可以使用ArrayList支持
    UserDatabase
    ,即使不扩展它,也可以通过委托获得性能。您不需要扩展它来获得性能

    public class UserDatabase implements List<User>{
       private ArrayList<User> list = new ArrayList<User>();
    
       // implementation ...
    
       // delegate
       public Spliterator() spliterator() { return list.spliterator(); }
    }
    
    public类UserDatabase实现列表{
    private ArrayList list=new ArrayList();
    //实施。。。
    //委派
    public Spliterator()Spliterator(){return list.Spliterator();}
    }
    
    你的两点并没有改变这一点。如果您知道“ArrayList有它自己的、更高效的spliterator()实现”,那么您可以将其委托给您的支持实例,如果您不知道,那么默认方法会处理它


    我仍然不确定实现列表接口是否真的有意义,除非您显式地创建了一个可重用的集合库。最好为这种一次性的API创建自己的API,这种API将来不会通过继承(或接口)链出现问题。

    我不能为每种情况提供建议,但对于这种特殊情况,我建议根本不要实现
    列表。
    UserDatabase.set(int,User)
    的目的是什么?是否确实要用全新的用户替换备份数据库中的第i个条目?添加(int,User)
    怎么样?在我看来,您应该将其实现为只读列表(在每个修改请求上抛出
    UnsupportedOperationException
    ),或者只支持一些修改方法(例如
    add(User)
    受支持,但
    add(int,User)
    不受支持)。但后一种情况会让用户感到困惑。最好提供您自己的修改API,它更适合您的任务。对于读取请求,最好返回一个用户流:

    我建议创建一个返回
    流的方法

    public class UserDatabase {
        List<User> list = new ArrayList<>();
    
        public Stream<User> users() {
            return list.stream();
        }
    }
    
    公共类用户数据库{
    列表=新的ArrayList();
    公共流用户(){
    返回list.stream();
    }
    }
    

    注意,在这种情况下,您可以完全自由地在将来更改实现。例如,用<代码> > TreSeT或 CONTRONCE LIKEDDEQ或其他什么来替换<代码> ARAYLIST/<代码>。

    < P>在开始思考性能之前,我们总是要考虑正确性,即在你的问题中,我们应该考虑使用继承而不是委托意味着什么。这一点已经通过以下例子加以说明。由于继承,如果延迟填充的列表尚未填充,则排序(同样适用于流操作)不起作用

    因此,我们必须在重写新的
    default
    方法的专门化在继承情况下完全中断的可能性和
    default
    方法在委托情况下不能以最大性能工作的可能性之间进行权衡。我认为,答案应该是显而易见的

    由于您的问题是关于新的
    默认
    方法是否会改变这种情况,因此应该强调的是,与以前不存在的方法相比,您谈论的是性能下降。让我们继续看
    排序
    示例。如果您使用委派,并且不重写
    default
    排序方法,则
    default
    方法的性能可能低于优化的
    ArrayList.sort
    方法,但在Java 8之前,后者不存在,并且未针对
    ArrayList
    进行优化的算法是标准行为

    因此,在Java8下,您并没有失去委托的性能,只要不重写
    default
    方法,您就不会获得更多。我认为,由于其他改进,性能仍将优于Java7(没有
    default
    方法)

    Stream
    API不易比较,因为在java8之前不存在该API。但是,很明显,类似的操作,例如,如果您手动实现缩减,除了通过委托列表的
    迭代器
    之外别无选择,必须防止public class UserDatabase { List<User> list = new ArrayList<>(); public Stream<User> users() { return list.stream(); } }