【问题标题】:Can I call methods in constructor in Java?我可以在 Java 的构造函数中调用方法吗?
【发布时间】:2011-07-10 23:24:49
【问题描述】:

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

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

【问题讨论】:

  • 您是否研究过单例模式
  • 这是一个非常古老的问题.. 但是如果有人想以 android 方式使用单例.. 看看this
  • 尝试静态块..一旦第一次引用 rhat 类(无论对象是否创建),静态块就会运行,并且它在该类的生命周期中运行一次

标签: java constructor


【解决方案1】:

你可以:这就是构造函数的用途。您还明确指出,该对象永远不会在未知状态下构造(未加载配置)。

您不应该:在构造函数中调用实例方法是危险的,因为对象尚未完全初始化(这主要适用于无法被覆盖的方法)。众所周知,构造函数中的复杂处理会对可测试性产生负面影响。

【讨论】:

    【解决方案2】:

    更好的设计应该是

    public static YourObject getMyObject(File configFile){
        //process and create an object configure it and return it
    }
    

    【讨论】:

    • @sam ,如果在构造函数中调用方法,由于对象的创建生命周期没有完全完成,部分成员变量的状态已损坏。
    • 我无法弄清楚该代码如何与您提供的工厂方法模式的链接相匹配。常见的错误。但是,+1 用于拆分文件的读取(可以进一步分解为可测试的方法)和构造使用数据的对象。
    • @Tom 好点 +1。 @sam 请阅读上面汤姆的评论,以便在替换 //process and create an object configure it and return it 和它的静态工厂模式仅汤姆时更可测试,
    • 这并没有回答标题中的问题。它不应该是公认的答案。
    【解决方案3】:

    我可以将我的方法 readConfig() 放入构造函数中吗?

    在构造函数中调用不可覆盖的方法是一种可接受的方法。
    如果该方法仅由构造函数使用,您可能想知道是否真的需要将其提取到方法中(甚至private)。

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

    所以它应该是private,如果它只被类的构造函数(和实例方法)使用。
    否则,如果在包内或子类中重用该方法,则它应该是 package-privatefinal

    这会给我一次打电话的好处,还是有另一种机制可以做到这一点?

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

    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() 也使用它
    • (次要但有趣)给出一个有意义的名称来传达执行的逻辑

    【讨论】:

    • 这个答案应该在堆栈中更高,因为它不只是说“不要这样做,它可能导致未定义的行为!”,但实际上解释了何时(以及为什么)在构造函数中调用实例方法是完全安全的。
    • @Janus Varmarken 非常感谢您的反馈。我很高兴你觉得它有帮助。这不是估计/赞成的,因为我对问题日期的回答很晚。不过好吧....
    【解决方案4】:

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

    一种解决方案是提供方法,您可以调用这些方法来查询对象的“健康状况”,一旦它被构造出来。比如isConfigOK()方法可以用来查看配置读取操作是否OK。

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

    class A
    {
        Map <String,String> config = null;
        public A()
        {
            readConfig();
        }
    
        protected boolean readConfig()
        {
            ...
        }
    
        public boolean isConfigOK()
        {
            // Check config here
            return true;
        }
    };
    

    【讨论】:

    • 但这会使错误比您希望的“更致命”;例如,如果调用失败,您可能不会太介意,因此您不希望通过抛出异常来使整个操作无效。
    【解决方案5】:

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

    相反,您应该:

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

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

    public class ConfigurableObject {
       private Map<String, String> configuration;
       public ConfigurableObject() {
    
       }
    
       public void setConfiguration(..) {
           //...simply set the configuration
       }
    }
    

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

    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
       }
    }
    

    当然,这可以通过构造函数来编写

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

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

    我知道第二个选项听起来更冗长且容易出错(如果您忘记初始化)。如果你在构造函数中这样做,它不会对你造成太大伤害。但是,让您的代码更加面向依赖注入通常是一个好习惯。

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

    【讨论】:

    • 你能讨论更多关于第二点的描述形式吗?
    • @sam - 不要在构造函数中执行工作,而是从实例化对象的位置调用 init() 方法。但不要在构造函数中进行工作。查看更新
    • @Tom Hawtin - tackline 更新了答案以包含第一个选项的示例(设置配置)。但是如果对象是托管的,公共的 init 方法就可以了。
    • 在第一个例子中,你的意思是在构造函数内部调用setConfiguration()方法吗?
    【解决方案6】:

    单例模式

    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. 
        }
    }
    

    【讨论】:

    • 单例可能是一个解决方案。但是有比这个更好的实现。 (它不是线程安全的,你可能更喜欢用枚举来解决它)
    【解决方案7】:

    为什么不使用 Static Initialization Blocks ?此处的其他详细信息: Static Initialization Blocks

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-05-22
      • 2019-12-11
      • 2014-09-02
      • 2013-02-25
      • 1970-01-01
      • 2017-04-18
      • 1970-01-01
      • 2015-02-14
      相关资源
      最近更新 更多