使用反射从抽象基类访问构造函数

7

我正在研究Java的反射技术。我有一个抽象类Base,它有一个构造函数。

abstract class Base {
    public Base( String foo ) {
        // do some magic
    }
}

我有一些扩展了Base的类,它们没有太多逻辑。我想用Base的构造函数来实例化它们,而不必在这些派生类中编写一些代理构造函数。当然,我也想用反射来实例化这些派生类。比如说:

Class cls = SomeDerivedClass.class;
Constructor constr;
constr = cls.getConstructor( new Class[] { String.class } ); // will return null
Class clsBase = Base.class;
constr = clsBase.getConstructor( new Class[] { String.class } ); // ok
Base obj = (Base) constr.newInstance( new Object[] { "foo" } ); // will throw InstantiationException because it belongs to an abstract class

有什么办法可以用基类的构造函数实例化派生类?或者说必须声明那些愚蠢的代理构造函数吗?

在实例化这些类之前,你确定一切都编译通过了吗? - gabuzo
是的,它编译得很好。这只是一个简化的例子,缺少那些catch子句,但基本上这段代码编译得很好。啊,而且“Base”有一个额外的空默认构造函数(javac 坚持要这样做)。 - craesh
4个回答

12

一个类不会从其父类中继承构造函数。一个类没有其父类的构造函数(尽管它可以调用它们)。因此,您必须调用类拥有的构造函数,而不是超类拥有的构造函数。

默认构造函数之所以看起来像这样是因为它默认调用父类的默认构造函数。如果父类没有默认构造函数,那么其直接子类也不能有。


2

如果你要创建一个抽象类,那么必须指定所有使它“非抽象”的细节。

这意味着在下面的例子中:

public abstract class Parent {
  String name;

  public Parent(String name) {
    this.name = name;
  }

  abstract public String getName();

}

通过反射进行构造函数操作也无法返回仅为Parent的类。但是,您可以在构建时指定抽象细节来返回“匿名”类,例如:

Parent parent = new Parent() {
    public String getName() { return "Bob"; }
  };

记住,子类化也会调用父构造函数,即使你没有明确地放置代码。一个像这样的子类:

public class Child extends Parent {
  public Child(String name) {
  }
}

将在Parent类中查找一个无参构造函数。如果找到,则将其编译为等效于以下代码:

public class Child extends Parent {
  public Child(String name) {
    super();
  }
}

如果在Parent类中找不到无参构造函数,除非您使用super(name);显式指定父类的构造函数调用,否则编译将失败。
另一件需要记住的事情是,所有类都是Object的子类,因此如果您不提供像这样的extends SomeClass
public class JustMe {
}

编译器在编译时会对你的代码进行概念上的“正确性”修正:
public class JustMe extends Object {

   public JustMe() {
     super();
   }
}

Object类中有一堆本地(非Java)代码,用于向JVM注册,确保在对象的生命周期内遵循正确的垃圾收集、内存管理、类型强制执行等。

也就是说,你无法绕过它,除非通过匿名类或子类解决所有抽象类方法,否则JVM会阻止你构造和使用抽象类。


子类化不会隐式调用任何东西,除非存在无参数的父构造函数。它绝对不会自己传递参数。此外,如果抽象类的构造函数需要一个参数,您应该在创建匿名类时指定它,因此匿名示例应该写成:new Parent(“foo”){...}。 - biziclop
@biziclop 感谢您的精彩观察,为了后人的利益已经更正了帖子。 - Edwin Buck

2

恐怕你的子类在调用其中一个super()构造函数之前,必须有一个明确的构造函数才能编译。


一切编译正常。Base(String)在任何子类中均不会被调用。但是我不得不为Base定义一个空的默认构造函数。 - craesh
这是一个非常重要的区别。这意味着您的子类将编译和实例化,但仅调用空父构造函数。除非在它们的构造函数中显式调用该构造函数,否则无法访问其他构造函数。 - biziclop

1
问题在于您的基类构造函数是非默认的(具有参数)。因此,它不能被生成的默认子类构造函数隐式调用。(实际上,您应该会收到编译警告/错误。)恐怕您需要添加显式的子类构造函数。

我没有收到任何警告。可能是因为Base和派生类位于不同的包中,分别位于不同的Eclipse项目中。 - craesh

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