如何防止在 class 的实例上调用静态方法?

How can I prevent invoking a static method on an instance of my class?

我有以下 class,用于在我的 Android 应用程序的各个位置控制一些调试和 Beta 测试选项。它只包含一些标志和一些逻辑来(反)序列化它 to/from 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 也不会比以前发出更多警告。

有什么方法可以 "hide" 来自实例变量的静态方法吗?

编辑

评论清楚地表明在 null 实例上调用静态方法是完全合法的。不过问题不是"How do I prevent a NullPointerException when invoking a static method on a null instance?",而是更笼统的"How can I prevent invoking a static method on an instance of my class?"

换句话说 - 如果有人不小心尝试在实例上调用静态方法,是否有任何方法可以阻止编译器进行编译?

我看到有几种方法可以做到这一点:

  1. 使用实用程序class:

    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()? 表示 method() 如果 instance 为空则不会执行。

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

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

由您来编写按预期工作的代码,并实施适当的空值检查、错误处理等。如果您遗漏了什么,也没什么大不了的;这就是为什么要在发布代码之前测试代码并修复发现的崩溃和错误的原因。 Google Play 控制台(如果您在那里发布)或 Firebase(如果您实施)或您的用户将报告您未发现的任何内容。

抱歉,如果上面的内容听起来很刺耳,但我真的很难理解你为什么要这样做而不只是检查你的代码。

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

public class DevConfiguration {
    //...

    private DevConfiguration() {}

    //...
}

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

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

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

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


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

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

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

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