【问题标题】:Requiring at least one element in java variable argument listjava变量参数列表中至少需要一个元素
【发布时间】:2012-09-26 22:25:12
【问题描述】:

在此代码结构中:

public MyClass(Integer... numbers) {
    do_something_with(numbers[]);
}

是否可以要求numbers 至少包含一个条目,以便在编译时进行检查? (当然,在运行时,我可以检查 numbers.length。)

显然我可以这样做:

public MyClass(Integer number, Integer... more_numbers) {
    do_something_with(number, more_numbers[]);
}

但这不会很优雅。

我想这样做的原因是确保子类不会简单地忘记调用这个构造函数,这将默认调用super(),列表中没有数字。在这种情况下,我宁愿得到熟悉的错误信息:Implicit super constructor is undefined. Must explicitly invoke another constructor

是否有另一种方法可以实现相同的效果,例如一些 @-annotation 将这个构造函数标记为非隐式?

【问题讨论】:

  • 不,你不能这样做.. var-arg 只不过是array.. 的不同表示。所以,就像你不能让你的数组至少包含一个元素一样。同样适用到 var-args..
  • RuntimeException,因为不是所有的编程错误都能在编译时发现。
  • 这是语言限制。你不能那样做……
  • @-annotation(最后一句话)怎么样?有这种事吗?
  • 我喜欢 MyClass(Integer number, Integer... more_numbers),只是我制作(很短)do_something_with(number + more_numbers) 并制作 do_something_with 私有

标签: java arguments minimum variadic-functions


【解决方案1】:

唯一的验证方式是验证参数。

验证参数:

if (numbers == null || numbers.length == 0 ) {
            throw new IllegalArgumentException("Your angry message comes here");
        }

【讨论】:

  • 这正是我想要避免的运行时检查;)
  • @Markus,在编译时也不能避免以下调用:MyClass m = new MyClass(null,null,null,null),除非您将Integer... numbers 更改为int... numbers。所以我认为抛出 IllegalArgumentException 已经足够优雅了。
  • 我实际上对空值没问题,而且我的真实代码也没有使用整数,这只是一个简化的例子。我只需要提请注意,有一个构造函数需要被调用。在您的代码中忘记它太容易了,并且可能会导致一些故障模式,这将很难通过 RuntimeException 追踪。
【解决方案2】:

正如 cmets 中所述,不,似乎不可能强制 var arg 的大小至少为 1。

我能想到的唯一编译时修复是简单地要求一个数组 (Integer[]) 作为构造函数的参数。子类仍然可以在其构造函数中使用 var arg,并且该类的任何其他用户只需在调用构造函数之前从所需的参数创建一个数组。

【讨论】:

    【解决方案3】:

    您可以做到这一点的一种非常老套的方法是制作该方法的私有版本,不带参数。这至少会阻止此类之外的任何人在编译时传递一个参数,但它不会提供有用的错误消息。但如果它是超级重要的,至少传入一个值,那就是这样。

    private MyClass() {
        // This exception will be thrown only if you manage to do a "new MyClass()"
        // within your own class since it is private.
        throw new RuntimeException("Why did you do this?!?"); 
    }
    

    【讨论】:

    • 我想过这个,但不幸的是它不起作用......它仍然会用一个空列表调用另一个构造函数,因为这个是不可见的。唯一可以防止的(同样,还有一个 RuntimeException)是不带参数调用自身的类。
    • 真的吗?这太有趣了。如果创建了更具体的函数,则默认应忽略 ... 函数。我觉得奇怪的是 Java 会尝试根据它是公共的还是私有的来区分。我觉得这不应该发生,但我没有制作 Java...
    【解决方案4】:
    public MyClass(boolean ignore, Integer... numbers) {
        do_something_with(numbers[]);
    }
    

    【讨论】:

    • 这可行。不是最干净的,但绝对可以接受。也许我会做 Void ignore 代替。如果只有我需要一个实际的其他参数... :)
    【解决方案5】:

    我认为它不在 ̶f̶u̶n̶c̶t̶i̶o̶n̶ 类本身中,而是当您调用 ̶f̶u̶n̶c̶t̶i̶o̶n̶ 类时。在调用 ̶f̶u̶n̶c̶t̶i̶o̶n̶ 类之前,请确保您的数组有元素。

    【讨论】:

    • 这在运行时可能有意义,但问题是是否可以检查数组编号是否包含至少一个元素在编译时
    【解决方案6】:

    我想一种令人难以置信的 hacky 方法是创建一个无参数方法并将其标记为已弃用。然后使用这两个标志进行编译:-Xlint:deprecation -Werror。这将导致对已弃用方法的任何使用都成为编译时错误。

    编辑(在最初的答案之后很长时间):

    一个不那么老套的解决方案是放弃 MyClass(Integer... numbers) 构造函数并将其替换为 MyClass(Integer[] numbers) (并添加一个私有的无参数构造函数)。它阻止编译器隐式使用超类构造函数,但没有任何参数,并为您提供编译时错误消息。

    ./some_package/Child.java:7: error: constructor Parent in class Parent cannot be applied to given types;
        public Child(Integer[] args) {
                                     ^
      required: Integer[]
      found: no arguments
      reason: actual and formal argument lists differ in length
    

    代价是你的调用语法会变得更冗长:

    new Child(new Integer[] {1, 2, 3});
    

    你当然可以编写一个辅助函数来帮助解决这个问题。

    public static Child newInstance(Integer... numbers) {
        return new Child(numbers);
    }
    
    @SafeVarargs
    public static <T> T[] array(T... items) {
        return items;
    }
    

    然后:

    Child c0 = Child.newInstance(1, 2, 3);
    Child c1 = new Child(array(1, 2, 3));
    

    【讨论】:

    • 这是一个非常有创意的想法!我喜欢它!不幸的是,它有两个问题:如果您没有显式调用 super(),Eclipse 将无处向您显示弃用警告,因此不幸的是,它不会立即得到我希望的红色方块和曲线。而且,更糟糕的是,我确实在我的代码的其他地方使用了几个不推荐使用的方法,我不能在我身上出错。 (在我因使用不推荐使用的方法而受到打击之前:这些是 Date 上的方法,因为 GWT 不支持 Calendar。)
    • 我会设置这个作为答案。它超级hacky,不幸的是对我来说不可用,但它是我问题的最真实答案,即在编译时捕获对 super 的调用是否没有参数。此外,跳出框框思考应该得到回报! ;)
    • 不推荐使用 Date API 是有充分理由的,因为在某些情况下会产生错误的输出。将 Date 视为 long 的简单包装器,将 Calendar 视为以毫秒以外的单位添加时间的方式。在日历中获得所需日期后,您可以要求它创建一个代表该时间的日期。肯定会建议重构您的代码。
    • 那是一个很棒的答案.. 开箱即用的思考
    • “开箱即用”且不合常规。 Ivan V 下面的解决方案是典型的。
    【解决方案7】:

    我认为拥有至少 1 个参数的最佳方法是像这样添加一个:

    public MyClass (int num, int... nums) {
        //Handle num and nums separately
        int total = num;
        for(i=0;i<nums.length;i++) {
            total += nums[i];
        }
        //...
    }
    

    将相同类型的参数与可变参数一起添加将强制构造函数需要它(至少一个参数)。然后你只需要单独处理你的第一个参数。

    【讨论】:

    • 非常聪明的伊万。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-22
    • 2011-10-07
    • 1970-01-01
    • 1970-01-01
    • 2016-07-13
    • 2017-11-16
    相关资源
    最近更新 更多