构造函数重载 - Java中的最佳实践

时间:2018-05-03 07:05:06

标签: java inheritance constructor instance superclass

构造函数也可以像任何其他方法一样重载,我知道这个事实。由于任务,我决定使用具有多个构造函数的抽象超类:

抽象超类:

protected ListSortierer()
{
  this( null, null );
}

protected ListSortierer( List<E> li )
{
  this( li, null );
}

protected ListSortierer( Comparator<E> comp )
{
  this( null, comp );     
}

protected ListSortierer( List<E> li, Comparator<E> com )
{
  this.original = Optional.ofNullable( li );
  this.comp = Optional.ofNullable( com );
}

要访问每个构造函数,我还需要子类中的多个构造函数。

BubbleSort.java:

public ListBubbleSort()
{
  super();
}

public ListBubbleSort( List<E> li )
{
  super( li );
}

public ListBubbleSort( Comparator<E> com )
{
  super( com );
}

public ListBubbleSort( List<E> li, Comparator<E> com )
{
  super( li, com );
}

在这种情况下,子类的每个构造函数都会立即调用超类的构造函数。我想到我可以再次引用自己的构造函数并传递null值:

public ListBubbleSort()
{
  this( null, null );
}

public ListBubbleSort( List<E> li )
{
   this( li, null );
}

public ListBubbleSort( Comparator<E> com )
{
   this( null, com );
}

public ListBubbleSort( List<E> li, Comparator<E> com )
{
   super( li, com );
}

这样做可以省略抽象超类中的3个重载构造函数,但会强制每个子类遵循相同的模式。

我的问题是:在一致性的情况下,更好的方法是什么?处理抽象超类或子类中的缺失值?它是否会对实例化产生影响,还是只是意见问题?

5 个答案:

答案 0 :(得分:14)

  

在一致性的情况下,有什么更好的方法?

  1. 让所有子构造函数都是私有的。
  2. 介绍静态工厂方法。

    ListBubbleSort.withList(List<E> list)
    ListBubbleSort.withComparator(Comparator<E> comparator)
    
  3. 调用正确的super构造函数。不要传递任何null

    public static <E> ListBubbleSort withList(List<E> list) {
        return new ListBubbleSort(list);
    }
    
    private ListBubbleSort(List<E>) {
        super(list);
    }
    
    protected ListSortierer(List<E>) {
        // initialise only the list field
        this.origin = list;
    }
    
  4. Don't use Optional as a field.

    <击> this.original = Optional.ofNullable(li);

  5. 如果您有3个以上的参数,请考虑the Builder Pattern

  6.   

    处理抽象超类或子类中的缺失值吗?

    构造函数应该提供初始值。 您没有传递初始值,只是表示他们缺席

    默认情况下,null是引用类型的初始值。因此,如果没有给出它的值,则无需重新分配字段。

      

    它是否会对实例化产生影响,还是只是意见问题?

    可读性,维护。

    我建议阅读Effective Java by Joshua Bloch

    创建和销毁对象

    • 第1项:考虑使用静态工厂方法而不是构造函数
    • 第2项:面对许多构造函数参数时考虑构建器

答案 1 :(得分:8)

关于问题本身:我认为两种选择都不理想。

您努力编写尽可能少的代码。添加重载只是因为它们似乎方便,所以要小心。实际上,你应该反过来:非常认真地考虑你的真实用例是什么,并且只支持那些。

在您的情况下,练习的重点似乎是允许使用不同的实现进行排序。从这个意义上说,你应该考虑一下strategy pattern

换句话说:您的第一个想法总是更喜欢构图而不是继承。当你的设计引导你遇到这样的问题时,更好的答案可能是退出你当前的设计并找到一种方法来启用不同的排序作为某种“服务” - 而不是支持< strong> list 本身一起成为一个排序的东西。

答案 2 :(得分:2)

  

这样做可以省略3个重载的构造函数   抽象超类但会强制执行每个子类   相同的模式。

通过抽象方法或接口实现合同。您不能确定每个子类都将具有这些构造函数,或者至少您不能确定每个子类都将正确添加这些构造函数。

因此,考虑到Encapsulation,这些构造函数在超类中更好。

答案 3 :(得分:2)

  

这样做可以省略3个重载的构造函数   抽象超类但会强制执行每个子类   相同的模式。

子类没有任何约束来调用父类的特定构造函数。当它调用其中一个时,它可以调用它们中的任何一个 所以你无法达到这样的要求。

关于使用静态工厂方法的解决方案,在某些情况下它是可以接受的,甚至很好,但它不是奇迹,也有一些限制。
例如:它不允许切换到其他实现 对于JDK类,我同意这几乎不是问题,但对于自定义类,我们应谨慎使用它 此外,没有什么能阻止子类扩展父类,也不会阻止工厂传递以创建该子类的实例 因此也没有确保引用的要求。

  

我的问题是:如果是的话,更好的方法是什么?   一致性?处理抽象超类或者中的缺失值   子类?它在实例化方面是否有所不同,或者是它   只是意见问题?

这不是意见问题。这很重要。
如果子类委托给父类并且检查总是相同的,那么让父类执行检查似乎很有趣。
但是,如果您不保证永远不会修改父类关于检查,则子类可能会在父类更改其检查时创建一些不一致实例。

答案 4 :(得分:1)

您可以添加最终addAll,在子构造函数中作为可选的额外项进行调用。对于比较器,语义不同,它(几乎)在可选时必须重载。

private final Optional<Comparator<E>> comparatorOption;

public final void addAll(List<E> li) {
    ...;
}

protected ListSortierer() {
    comparatorOption = Optional.empty();     
}

protected ListSortierer(Comparator<E> comp) {
    comparatorOption = Optional.of(comp);     
}

避免使用空参数似乎更好。

如果你愿意的话,通过拥有许多构造函数来规定所有子类具有相同的构造函数并不错。优点是所有类的API行为相同。然而,不需要锅炉板代码,构建器模式可以防止。在最初的旧java中,许多构造函数是可行的,但现在人们可以在API中看到许多构建器。

对于列表排序器,使用某种构建器的流畅API可能是个主意。

所以最后的答案是:更多的情境决定。