ChatGPT解决这个技术问题 Extra ChatGPT

如何强制在我的抽象类的所有子类中定义构造函数

我有一个定义抽象方法的抽象类 A。这意味着,要使一个类可实例化,必须实现所有抽象方法。

我希望我的所有子类都实现一个以 2 个整数作为参数的构造函数。

声明构造函数违背了我的目的,因为我希望在子类中定义构造函数并且我对实现一无所知。此外,我不能将构造函数声明为抽象的;

有没有办法做到这一点 ?

我想要的示例:

假设我正在定义 Matrix 类的 API。在我的问题中,Matrix 无法更改它们的尺寸。

要创建一个矩阵,我需要提供它的大小。

因此,我希望我的所有实现者都为构造函数提供大小作为参数。这个构造函数的动机是问题,而不是实现问题。只要保留方法的所有语义,实现就可以对这些进行任何操作。

假设我想在我的抽象类中提供 invert() 方法的基本实现。此方法将创建一个具有 this 个倒置维度的新矩阵。更具体地说,正如它在抽象类中定义的那样,它将创建一个与 this 相同的类的新实例,使用一个接受两个整数的构造函数。由于它不知道它将使用反射(getDefinedConstructor)的实例,我想要一种方法来保证我会得到它并且它将对实现有意义。

可以在这里找到一个优雅的解决方案:stackoverflow.com/questions/6028526/…

J
Jon Skeet

您不能在子类中强制构造函数的特定签名 - 但您可以强制它通过抽象类中的构造函数,获取两个整数。例如,子类可以从无参数构造函数调用该构造函数,传递常量。不过,这是你能来的最接近的。

此外,正如你所说,你对实现一无所知 - 那么你怎么知道他们有一个需要两个整数的构造函数是合适的?如果其中一个也需要字符串怎么办?或者可能对其中一个整数使用常量是有意义的。

这里的大局是什么——为什么要在子类上强制使用特定的构造函数签名? (正如我所说,你实际上不能这样做,但如果你解释你为什么想要它,一个解决方案可能会出现。)

一种选择是为工厂提供单独的接口:

interface MyClassFactory
{
    MyClass newInstance(int x, int y);
}

然后,您的 MyClass 的每个具体子类还需要一个知道如何在给定两个整数的情况下构建实例的工厂。不过,这并不是很方便——而且您仍然需要自己构建工厂的实例。再次,这里的真实情况是什么?


假设我正在定义 Matrix 类的 API。在我的问题中,Matrix 无法更改它们的尺寸。要创建一个矩阵,我需要提供它的大小。因此,我希望我的所有实现者都为构造函数提供大小作为参数。这个构造函数的动机是问题,而不是实现问题。只要保留方法的所有语义,实现就可以对这些进行任何操作。
@dodecaplex:但是如果您想创建一个始终 10 x 10 的FixedSizeMatrix 实现怎么办?无论如何,您都不能调用构造函数,那么为什么要限制实现呢?
那么实现将不符合我的 API... 如果它有充分的理由这样做,那么它将提供一个零 arg 构造函数和一个 2 args 构造函数,如果 args 不是 10x10,它将引发异常。这意味着我仍然可以创建一个具有相同实现和相同大小的空矩阵(不知道有效的实现),但是如果我尝试将此实现用于非 10x10 矩阵,我会遇到异常。
我不想限制实施,而是要正确定义它必须履行的合同。唯一的问题是为什么我不能在合同中包含构造函数?
@dodecaplex,您可以轻松地在抽象类中声明一个构造函数,该构造函数采用两个整数,然后是 FixedSizeMatrix 的无参数构造函数,看起来像 public FixedSizeMatrix() { super(10, 10); },就像 Jon 说的那样。 API 与构造函数(如何构建对象)无关,而与方法(如何使用对象)有关。
M
Matthieu

你可以试试下面的东西。如果实现类没有具有适当参数的构造函数,则构造函数将引发异常。

这很愚蠢。比较好和坏。这两个类是相同的,除了 OK 满足您的要求并因此通过运行时检查。因此,执行要求会促进适得其反的忙碌工作。

更好的解决方案是某种工厂。

abstract class RequiresConstructor
{
    RequiresConstructor( int x, int y ) throws NoSuchMethodException
    {
    super();
    System.out.println( this.getClass().getName() ) ;
    this.getClass(). getConstructor ( int.class , int.class ) ;
    }

    public static void main( String[] args ) throws NoSuchMethodException
    {
    Good good = new Good ( 0, 0 );
    OK ok = new OK ();
    Bad bad = new Bad ();
    }
}

class Good extends RequiresConstructor
{
    public Good( int x, int y ) throws NoSuchMethodException
    {
    super( x, y ) ;
    }
}

class OK extends RequiresConstructor
{
    public OK( int x, int y ) throws NoSuchMethodException
    {
    super( x, y ) ;
    throw new NoSuchMethodException() ;
    }

    public OK() throws NoSuchMethodException
    {
    super( 0, 0 ) ;
    }
}

class Bad extends RequiresConstructor
{
    public Bad() throws NoSuchMethodException
    {
    super( 0, 0 ) ;
    }
}

T
Tim Bender

如果您需要在接口中定义实现类将使用的内部表示,那么您做错了。请阅读有关 encapsulationdata abstraction 的内容。

如果您的抽象实现依赖于某些实现细节,那么它们属于该抽象类。意思是,抽象类应该定义一个构造函数,允许它初始化允许抽象方法工作所需的内部状态。

通常,构造函数旨在通过提供该对象实例的初始状态的一些细节来创建类的实例。这并不意味着正在构造的实例应该复制对每个单独参数的引用,就像我看到的大多数软件中经常出现的情况一样。因此,即使 Java 确实提供了一个强制在子类上实现某些构造函数签名的构造,这些子类也可以很容易地丢弃参数。


好吧,如果我在 n 维空间中定义一个点,我肯定知道该点不能存在于 m 维空间中(m!= n)。因此,在我看来,空间的维度是点的固有属性,无论点的实现如何。据我所知,这是封装和数据抽象。如果对于要创建的每个实例都必须知道维度,则要求所有实现提供接受此参数的构造函数似乎很自然。
@dodecaplex,啊,一个点,很好的例子。让我们假设您正在谈论一个二维点。好吧,当然,我可以使用 x 和 y 值来表示它。或者,我可以使用弧度/度值和距离值来表示它。只是一个例子。可能还有其他方法。您只认为您知道如何实施。无论如何,根据定义,如果您尝试强制执行表示,则您没有使用数据抽象。
A
Abdullah

有点晚了,但是...

只需在您的类中创建一个默认构造函数,该构造函数始终称为超级构造函数。在这个默认构造函数中,您可以检查所有定义的构造函数,并对其自己的类对象(不是抽象超类而是具体子类)进行反射。如果你要实现的构造函数丢失,抛出运行时异常。

我不是反思的好朋友,因为它有从后门破解的味道,但有时它会有所帮助......

看看这个例子:

import java.lang.reflect.Constructor;

public abstract class Gaga {
  public Gaga() {
    boolean found = false;
    try {
      Constructor<?>[] constructors = getClass().getConstructors();
      for (Constructor<?> c : constructors) {
        if (c.getParameterTypes().length==2) {
          Class<?> class0 = c.getParameterTypes()[0];
          Class<?> class1 = c.getParameterTypes()[1];
          if ( (class0.getName().equals("int") || class0.isAssignableFrom(Integer.class))
              &&  (class1.getName().equals("int") || class1.isAssignableFrom(Integer.class)) )
            found = true;
        }
      }
    } catch (SecurityException e)
    {
      found = false;
    }

    if (!found)
      throw new RuntimeException("Each subclass of Gaga has to implement a constructor with two integers as parameter.");

    //...
  }

}

和一个测试类:

public class Test {
  private class Gaga1 extends Gaga {
    public Gaga1() { this(0, 0); }
    public Gaga1(int x, Integer y) { }
  }

  private class Gaga2 extends Gaga {

  }

  public static void main(String[] args)
  {
    new Gaga1();
    new Gaga1(1, 5);
    new Gaga2();
    System.exit(0);
  }
}

在 main 函数中会创建 Gaga1 的对象,但创建 Gaga2 会抛出运行时异常。

但是你不能确定这个构造函数是否被调用——你甚至不能确保它正在做你想做的事情。

此测试仅在您使用反射时才有用。


M
Mehdi Akbarian Rastaghi

晚餐班:

public abstract class SupperClass {
  protected Foo foo;

  //primary constructor
  public SupperClass(Foo foo) {
      this.foo = foo;
  }

  private SupperClass(){
    //with this private constructor, the subclass forced to implement primary constructor
  }

}

子类:

public class SubClass extends JLLayer {

  public SubClass(Foo foo) {
      super(foo);
  }
}

y
yesennes

让抽象类有一个抽象方法,该方法采用您将拥有的参数。例如:

public abstract void setSize(int rows,int columns);