java 我怎样才能强制一个构造函数在我的抽象类的所有子类中被定义

ztyzrc3y  于 2023-02-11  发布在  Java
关注(0)|答案(7)|浏览(109)

我有一个抽象类A,它定义了抽象方法,这意味着,对于一个可示例化的类,所有的抽象方法都必须被实现。
我希望我所有的子类都能实现一个以2个int为参数的构造函数。
声明一个构造函数违背了我的初衷,因为我希望构造函数定义在子类中,而我对实现一无所知,而且我不能声明一个构造函数是抽象的;
有什么办法吗?

    • 我想要的示例:**

假设我正在定义一个Matrix类的API,在我的问题中,Matrix不能改变它们的维数。
对于要创建的矩阵,我需要提供它的大小。
因此,我希望我所有的实现者都给构造函数提供一个参数,这个构造函数是由问题而不是实现所驱动的,只要方法的所有语义都保持不变,实现就可以对这些构造函数做任何事情。
假设我想在抽象类中提供一个invert()方法的基本实现,这个方法将创建一个新的矩阵,其维数为this,更具体地说,由于它是在抽象类中定义的,它将创建一个与this相同的类的新示例。使用一个构造函数,它接受两个整型。因为它不知道示例,所以它将使用反射(getDefinedConstructor),我需要一种方法来保证我将得到它,并且它对实现有意义。

falq053o

falq053o1#

你不能强迫子类中的构造函数有一个特定的签名--但是你可以强迫它通过抽象类中的一个构造函数来获取两个整数。子类可以从一个无参数的构造函数调用那个构造函数,比如传递常量。这是你能做到的最接近的了。
此外,正如你所说,你对实现一无所知--那么你怎么知道他们有一个需要两个整数的构造函数是合适的呢?如果其中一个也需要一个String呢?或者可能对其中一个整数使用一个常量是有意义的。
这里更重要的是什么-- * 为什么 * 要在子类上强制一个特定的构造函数签名呢?(正如我所说,实际上你不能 * 这样做 *,但是如果你解释了为什么要这样做,一个解决方案可能会出现)。
一种选择是为工厂提供单独的接口:

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

那么MyClass的每个具体子类都需要一个工厂,它知道如何在给定两个整数的情况下构建一个示例。尽管这不是很方便--而且你仍然需要构建工厂本身的示例。再问一次,这里的真实的情况是什么?

u1ehiz5o

u1ehiz5o2#

你可以尝试下面的方法。如果实现类没有一个带合适参数的构造函数,构造函数将抛出一个异常。
这很愚蠢。比较OK和Bad。这两个类是相同的,除了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 ) ;
    }
}
vlurs2pr

vlurs2pr3#

如果你需要在你的接口中定义实现类将要使用的内部表示,那么你只是做错了。请阅读关于封装和数据抽象的内容。
如果你的抽象实现依赖于特定的实现细节,那么它们就属于那个抽象类,也就是说,抽象类应该定义一个构造函数,允许它初始化内部状态,以使抽象方法能够工作。
一般来说,构造函数是用来通过提供对象示例初始状态的一些细节来创建类的示例的。这并不意味着被构造的示例应该复制对每个参数的引用,这在我看到的大多数软件中是常见的。因此,即使Java确实提供了一个构造函数来强制在子类上实现某些构造函数签名,那些子类可以容易地丢弃参数。

jdgnovmf

jdgnovmf4#

有点晚了,但是...
只需在类中创建一个默认构造函数,它总是被称为超构造函数。在这个默认构造函数中,你可以检查所有定义的构造函数,并在它自己的类对象上进行反射(这样就不是抽象超类,而是具体子类)。如果你想要实现的构造函数丢失,抛出一个运行时异常。
我不是一个很好的朋友反思,因为它有黑客通过后门的味道,但有时它帮助...
请看这个例子:

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的创建将抛出运行时异常。
但是您不能确定是否调用了这个构造函数--您甚至不能确定它正在执行您想要的操作。
这个测试只有在使用反射时才有用。

gywdnpxw

gywdnpxw5#

晚餐:

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);
  }
}
jtw3ybtb

jtw3ybtb6#

您可以选择APT处理器
免责声明:

  • bla bla "使用工厂"
  • bla bla "封装和数据抽象"
  • bla bla "运行时抛出"

关键是我需要这样使用它

public class QueryProviders {
    private final ArrayList<AbstractQueryProvider> providers = new ArrayList<>();

    @SafeVarargs
    public QueryProviders(SomeClass arg1, ...OtherArgs... ,
                          Class<? extends AbstractQueryProvider>... providerClasses) {
        try {
            for (Class<? extends AbstractQueryProvider> providerClass : providerClasses) {
                final Constructor<? extends AbstractQueryProvider> ctor = providerClass.getConstructor(SomeClass arg1, ...OtherArgs...);
                providers.add(ctor.newInstance(arg1, ...OtherArgs...));
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

和类

public abstract class AbstractQueryProvider {
    public AbstractQueryProvider(SomeClass arg1, ...OtherArgs...) {

    }
}

和用法

QueryProviders providers = new QueryProviders(SomeClass arg1, ...OtherArgs...,
            Backup.class,
            GoodsPrices.class, ...OtherDerived...);

其中,BackupGoodsPrices是从AbstractQueryProvider导出的
当我忘记这样的构造函数时,我需要编译时错误
因此,对于APT处理器,您将拥有如下类:

@ConstructorConstraint(SomeClass.class, ...)
public abstract class AbstractQueryProvider {
    public AbstractQueryProvider(SomeClass arg1, ...) {

    }
}

(包:"pl. selvin. constructorsconstraints. apt"),带有如下注解:

@Inherited
@Documented
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface ConstructorConstraint {
    Class<?>[] arguments() default {};
}

处理器是这样的:

@SupportedAnnotationTypes("pl.selvin.constructorsconstraints.apt.ConstructorConstraint")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class ConstructorConstraintProcessor extends AbstractProcessor {
    private static final TypeVisitor<Boolean, ArrayList<String>> constraintArgsVisitor =
            new SimpleTypeVisitor7<Boolean, ArrayList<String>>() {
                public Boolean visitExecutable(ExecutableType t, ArrayList<String> args) {
                    final List<? extends TypeMirror> types = t.getParameterTypes();
                    if (args.size() != types.size()) {
                        return false;
                    }
                    for (int i = 0; i < args.size(); i++) {
                        if (!args.get(i).equals(types.get(i).toString()))
                            return false;
                    }
                    return true;
                }
            };

    @Override
    public Set<String> getSupportedOptions() {
        final HashSet<String> ret = new HashSet<>();
        ret.add("org.gradle.annotation.processing.aggregating");
        return ret;
    }

    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
        for (final TypeElement type : annotations) {
            processConstructorConstraintClasses(env, type);
        }
        return true;
    }

    private void processConstructorConstraintClasses(final RoundEnvironment env, final TypeElement type) {
        final Element constructorConstraintElement = processingEnv.getElementUtils().getTypeElement(ConstructorConstraint.class.getName());
        final TypeMirror constructorConstraintType = constructorConstraintElement.asType();
        ArrayList<String> constructorConstraints = new ArrayList<>();
        for (final Element element : env.getElementsAnnotatedWith(type)) {
            for (AnnotationMirror am : element.getAnnotationMirrors()) {
                if (am.getAnnotationType().equals(constructorConstraintType)) {
                    for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : am.getElementValues().entrySet()) {
                        if ("arguments".equals(entry.getKey().getSimpleName().toString()) && entry.getValue() instanceof Attribute.Array) {
                            final Attribute.Array array = (Attribute.Array) entry.getValue();
                            for (final Attribute a : array.values) {
                                constructorConstraints.add(a.getValue().toString());
                            }
                        }
                    }
                }
            }
            processClass(element, constructorConstraints);
        }
    }

    private void processClass(Element element, ArrayList<String> arguments) {
        if (!doesClassContainConstructorWithConstraint(element, arguments)) {
            final String needs;
            if (arguments == null || arguments.size() == 0) {
                needs = "a No-Args Constructor";
            } else {
                needs = "a Contrcutor with arguments(" + String.join(", ", arguments) + ")";
            }
            processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Class " + element + " needs " + needs);
        }
    }

    private boolean doesClassContainConstructorWithConstraint(Element element, ArrayList<String> arguments) {
        for (final Element subElement : element.getEnclosedElements()) {
            if (subElement.getKind() == ElementKind.CONSTRUCTOR && subElement.getModifiers().contains(Modifier.PUBLIC)) {
                final TypeMirror mirror = subElement.asType();
                if (mirror.accept(constraintArgsVisitor, arguments))
                    return true;
            }
        }
        return false;
    }
}

如果派生类没有这样的构造函数,则会引发编译时错误
可能出现的问题:

  • 泛型类,如
class Whatever<T>{
    Whatever(T arg) {}
}
  • 多重构造函数
  • 它不是递增

Github页面:https://github.com/SelvinPL/ConstructorsConstraints

qni6mghb

qni6mghb7#

让抽象类有一个抽象方法,它接受你想要的参数,例如:

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

相关问题