我可以调用Java中的构造函数方法吗?

Can I call methods in constructor in Java?

在这种情况下,当类被实例化时,我只想读取一次配置文件。

假设我有一个名为readConfig()的方法,它读取配置并将其放入Map对象中。当程序需要使用配置值时,它用定义键读取对象。我想知道构造函数只调用一次它的生命周期。我可以将我的方法readConfig()放入构造函数中吗?这会给我一次调用带来好处,还是有其他机制可以做到这一点?


您可以:这就是构造函数的作用。此外,您还清楚地表明,永远不会在未知状态(未加载配置)下构造对象。

您不应该这样做:在构造函数中调用实例方法是危险的,因为该对象尚未完全初始化(这主要适用于可重写的方法)。此外,构造函数中的复杂处理也会对可测试性产生负面影响。


更好的设计是

1
2
3
public static YourObject getMyObject(File configFile){
    //process and create an object configure it and return it
}
  • 工厂设计模式


Can I put my method readConfig() into constructor?

在构造函数中调用不可重写的方法是可以接受的方法。而如果该方法仅由构造函数使用,您可能会怀疑是否确实需要将其提取到方法(甚至是private中)。

如果您选择将构造函数完成的某些逻辑提取到一个方法中,那么对于任何方法,都必须选择一个符合方法要求的访问修饰符,但是在这种特定情况下,更重要的是保护方法不被方法覆盖,这样做的风险是使超类构造函数不一致。

因此,如果它只由类的构造函数(和实例方法)使用,那么它应该是private。否则,如果方法在包或子类中被重用,那么它应该同时是package-privatefinal

which would give me benefit of one time calling or is there another mechanism to do that ?

你用这种方法没有任何好处或缺点。我不鼓励在构造函数中执行太多的逻辑,但在某些情况下,在一个构造函数中初始化多个东西可能是有意义的。例如,复制构造函数可以执行很多操作。多个JDK类说明了这一点。以HashMap复制构造函数为例,该构造函数使用与指定Map参数相同的映射构造新的HashMap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public HashMap(Map<? extends K, ? extends V> m) {
    this.loadFactor = DEFAULT_LOAD_FACTOR;
    putMapEntries(m, false);
}

final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
    int s = m.size();
    if (s > 0) {
        if (table == null) { // pre-size
            float ft = ((float)s / loadFactor) + 1.0F;
            int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                     (int)ft : MAXIMUM_CAPACITY);
            if (t > threshold)
                threshold = tableSizeFor(t);
        }
        else if (s > threshold)
            resize();
        for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
            K key = e.getKey();
            V value = e.getValue();
            putVal(hash(key), key, value, false, evict);
        }
    }
}

提取putMapEntries()中的地图填充逻辑是一件好事,因为它允许:

  • 在其他上下文中重用该方法。例如,clone()putAll()也使用它
  • (很小但很有趣)给出一个有意义的名字,用来传达执行的逻辑


构造函数只被调用一次,因此您可以安全地执行您想要的操作,但是从构造函数内部而不是直接调用方法的缺点是,如果方法失败,您不会得到直接反馈。你调用的方法越多,这就越困难。

一种解决方案是提供方法,您可以在构造对象后调用这些方法来查询其"运行状况"。例如,方法isConfigOK()可用于查看配置读取操作是否正常。

另一种解决方案是在失败时在构造函数中抛出异常,但这实际上取决于这些失败有多"致命"。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class A
{
    Map <String,String> config = null;
    public A()
    {
        readConfig();
    }

    protected boolean readConfig()
    {
        ...
    }

    public boolean isConfigOK()
    {
        // Check config here
        return true;
    }
};


单例模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MyClass() {

    private static MyClass instance = null;
    /**
    * Get instance of my class, Singleton
    **/

    public static MyClass getInstance() {
        if(instance == null) {
            instance = new MyClass();
        }
        return instance;
    }
    /**
    * Private constructor
    */

    private MyClass() {
        //This will only be called once, by calling getInstanse() method.
    }
}


你可以。但是,通过将其放置在构造函数中,您将使对象难以测试。

相反,你应该:

  • 为配置提供一个setter
  • 有单独的init()方法

依赖注入框架为您提供了这些选项。

1
2
3
4
5
6
7
8
9
10
public class ConfigurableObject {
   private Map<String, String> configuration;
   public ConfigurableObject() {

   }

   public void setConfiguration(..) {
       //...simply set the configuration
   }
}

第二个选项的示例(最好在对象由容器管理时使用):

1
2
3
4
5
6
7
8
9
10
11
public class ConfigurableObject {
   private File configFile;
   private Map<String, String> configuration;
   public ConfigurableObject(File configFile) {
       this.configFile = configFile;
   }

   public void init() {
       this.configuration = parseConfig(); // implement
   }
}

当然,这可以通过只使用构造函数来编写。

1
2
3
public ConfigurableObject(File configfile) {
    this.configuration = parseConfig(configFile);
}

但是,您将无法提供模拟配置。

我知道第二个选项听起来更冗长,而且容易出错(如果忘记初始化)。如果你在一个构造器中做的话,它不会真正伤害你那么多。但是,使代码更面向依赖注入通常是一个好的实践。

第一个选项是最好的-它可以与DI框架和手动DI一起使用。


为什么不使用Static Initialization Blocks?其他详细信息:静态初始化块