【问题标题】:How can I prevent invoking a static method on an instance of my class? [duplicate]如何防止在我的类的实例上调用静态方法? [复制]
【发布时间】:2018-09-26 18:24:09
【问题描述】:

我有以下类,用于在我的 Android 应用程序的各个位置控制一些调试和 beta 测试选项。它只包含一些标志和一些逻辑来(反)序列化它到/从 JSON。

public class DevConfiguration {
    public boolean dontSendSMS;


    public static String toJsonString(DevConfiguration devConfiguration) {
        JSONObject json = new JSONObject();

        if( devConfiguration != null ) {
            try {
                json.put("dontSendSMS", devConfiguration.dontSendSMS);
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }

        return json.toString();
    }


    public static DevConfiguration fromJsonString(String jsonString) {
        if( jsonString.isEmpty() )
            return null;

        DevConfiguration result = new DevConfiguration();

        try {
            JSONObject jsonObj = new JSONObject(jsonString);
            result.dontSendSMS = jsonObj.optBoolean("dontSendSMS", false);
        } catch (JSONException e) {
            e.printStackTrace();
        }

        return result;
    }

}

现在,在我的一个服务中,我在Intent 中接收到一个序列化的DevConfiguration 对象,然后可能会将其传递给另一个服务:

serviceIntent.putExtra("dev_conf", DevConfiguration.toJsonString(mDevConfiguration));

我选择将toJsonString() 方法设为静态,这样我就不会冒险在null 实例上调用它。但是,仍然有可能在某处出错并在实例上调用静态方法 - 可能是 null 实例!

mDevConfiguration.toJsonString(mDevConfiguration);

Android Studio 中有一个 Lint 警告,但它仍然是一个潜在的 NullPointerException 错误等待发生。我认为可以通过定义类似的 private 方法来隐藏它,但签名不同

/** Hide instance implementation **/
private String toJsonString(Object o){ return ""; }

当然,使用 DevConfiguration 参数调用它无论如何都会调用静态方法,并且 IDE 也不会给出比以前更多的警告。

有没有办法从实例变量中“隐藏”静态方法?

编辑

评论清楚地表明,在null 实例上调用静态方法是完全合法的。然而,问题不在于“如何在空实例上调用静态方法时防止 NullPointerException?”,而是更一般的 “如何防止在空实例上调用静态方法?我的班级的实例?”

换句话说 - 如果一个人不小心尝试在实例上调用静态方法,有什么方法可以防止编译器编译?

【问题讨论】:

  • 内部类应该可以帮到你。
  • 你真的尝试过测试你的假设吗?我没有得到 NPE:ideone.com/u2UPPd
  • @JohannesKuhn Doh!你是对的,它不会抛出 NPE,这应该是公认的答案。在我的辩护中,我发现链接的问题措辞很奇怪,这可能就是为什么在搜索或输入我的问题标题时它没有出现的原因。进行快速谷歌搜索时也没有找到任何东西。
  • 我搜索了static method on null,这是第一个结果。
  • @JohannesKuhn 我搜索了prevent static method on instance,因为我对这种一般情况而不是空实例的边缘情况感兴趣。这个问题是适用于 Java 的第一个相关问题。

标签: java android nullpointerexception static static-methods


【解决方案1】:

对具有空值的变量调用静态方法不会引发 NullPointerException。以下代码将打印42,即使变量i 为空。

public class Test {
    public static void main(String... args) {
        Integer i = null;
        System.out.println(i.parseInt("42"));
    }
}

当通过变量调用静态方法时,真正重要的是变量的声明类型,而不是其值的引用类型。这与java中的静态方法不是多态的有关。


“如何防止在我的类的实例上调用静态方法?”

通过变量调用静态方法只是 Java 规范中定义的常规语言功能。如果有任何方法可以抑制它,我会感到惊讶。

如果我必须为选定的类执行此操作,我可能会将静态方法迁移到单独的“companion”实用程序(如另一个答案中所述)。

但是在你的类中拥有这样的静态(工厂)方法是一个非常好的习惯用法(例如,参见:Joshua Bloch,“Effective Java”,第 1 项:考虑静态工厂方法而不是构造函数)。我不会轻易放弃。

【讨论】:

  • 这是一个清晰、内容丰富且有用的答案!这不是这个特定问题的答案。在问题底部查看我的编辑。
  • @BadCash,我想也许知道在实例上调用静态方法时没有 NPE 风险,您不一定会尝试阻止它。我会更新我的答案。
【解决方案2】:

我看到了一些你可以做到这一点的方法:

  1. 使用 Utils 类:

    public class Utils {
        public static String toJsonString(DevConfiguration devConfiguration) {
            JSONObject json = new JSONObject();
    
            if( devConfiguration != null ) {
                try {
                    json.put("dontSendSMS", devConfiguration.dontSendSMS);
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }
    
            return json.toString();
        }
    
    
        public static DevConfiguration fromJsonString(String jsonString) {
            if( jsonString.isEmpty() )
                return null;
    
            DevConfiguration result = new DevConfiguration();
    
            try {
                JSONObject jsonObj = new JSONObject(jsonString);
                result.dontSendSMS = jsonObj.optBoolean("dontSendSMS", false);
            } catch (JSONException e) {
                e.printStackTrace();
            }
    
            return result;
        }
    }
    

    现在您只需拨打Utils.method() 即可避免混淆。

  2. 使用 Kotlin

    Kotlin 实际上让在动态接收器上调用静态方法变得非常困难(如果不是不可能的话)。它不会显示在方法建议中,如果您手动输入,它将带有红色下划线。它甚至可能无法编译,尽管我还没有走到那一步。

    Kotlin 还具有内置的 null 保护:instance?.method()? 表示如果 instance 为空,method() 将不会执行。

  3. 只是不要在动态接收器上调用静态方法。如果您不小心这样做,请返回并修复它。您不应该依赖 Java 为您解决语法错误。

最后,为什么还要这样做?我非常怀疑mDevConfiguration 是否永远为空,除非你在一个非常奇怪的地方初始化它。如果是这样,您可能希望重新组织您的代码。因为,同样,您不应该依赖 Java 来为您解决语法错误。此外,如果它为 null,它不会抛出 NPE,至少在 Java 中是这样,因为它不需要动态接收器来运行(这在 Kotlin 中可能不同)。

您可以自行决定编写应有的代码,并实施适当的空值检查、错误处理等。如果您遗漏了什么,也没什么大不了的;这就是为什么您在发布代码之前测试您的代码并修复您捕获的崩溃和错误的原因。 Google Play 控制台(如果您在此处发布)或 Firebase(如果您实施)或您的用户将报告您未捕获的任何内容。

很抱歉,如果上述内容听起来很苛刻,但我真的很难理解您为什么要这样做而不是仅仅检查您的代码。

如果你真的想保留这个结构,至少让DevConfiguration的构造函数私有:

public class DevConfiguration {
    //...

    private DevConfiguration() {}

    //...
}

这样,只有里面的静态方法才能创建实例。

【讨论】:

  • 如前所述,配置以 Intent 形式到达,但对于应用程序的运行并不重要,因为它仅用于 beta 和调试版本。此外,拥有一个单例或静态 DevConfiguration 是我考虑过的一个想法,但我希望不同的组件能够同时运行不同的 DevConfiguration。如果可能的话,我宁愿避免由于编码错误而导致的任何可能的 NPE,因为无论如何都会犯错误并且会出现错误。事实证明这也不是问题。不过还是感谢您抽出宝贵时间。
  • 将构造函数设为私有并不会使其成为单例。 fromJsonString() 每次调用时仍会创建一个新实例。
  • 抱歉,我将您的答案与另一个建议将其设为静态类的(现已删除)混淆了。但无论哪种方式,在某些时候,我实际上是使用 new 而不是从 JSON 字符串创建 DevConfiguration 的实例。
  • 没关系。不过,我要再说一遍,如果你在代码中犯了错误,最好修复它。大多数时候,避免犯错是不可能的。
  • 我不同意你所说的。但我认为我在这个问题中要实现的目标类似于定义一个私有构造函数,这样就不会意外地创建一个实例。我在想可能有一种方法可以通过使用“私有”实例方法和公共类方法来捕捉 IDE 中的错误,但事实证明 Java 编译器是允许错误的那个 - 甚至调用静态方法一个空实例完全没问题!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-27
  • 2017-06-11
  • 1970-01-01
  • 2014-10-04
  • 1970-01-01
  • 2015-04-16
相关资源
最近更新 更多