Java泛型可变参数方法参数

18

我想知道是否有一种简单、优雅和可重用的方法来将一个字符串和一个字符串数组传递给期望varargs的方法。

/**
 * The entry point with a clearly separated list of parameters.
 */
public void separated(String p1, String ... p2) {
    merged(p1, p2, "another string", new String[]{"and", "those", "one"});
}

/**
 * For instance, this method outputs all the parameters.
 */
public void merged(String ... p) {
    // magic trick
}

即使所有类型都是一致的(`String`),我也找不到一种方法告诉JVM将p2“展平”并将其注入合并的参数列表中?
此时唯一的方法是创建一个新数组,将所有内容复制到其中并将其传递给函数。
有任何想法吗?

编辑

根据您的建议,这是我将使用的通用方法:
/**
 * Merge the T and T[] parameters into a new array.
 *
 * @param type       the destination array type
 * @param parameters the parameters to merge
 * @param <T>        Any type
 * @return the new holder
 */
@SuppressWarnings("unchecked")
public static <T> T[] normalize(Class<T> type, Object... parameters) {
    List<T> flatten = new ArrayList<>();
    for (Object p : parameters) {
        if (p == null) {
            // hum... assume it is a single element
            flatten.add(null);
            continue;
        }
        if (type.isInstance(p)) {
            flatten.add((T) p);
            continue;
        }
        if (p.getClass().isArray() && 
            p.getClass().getComponentType().equals(type)) {
            Collections.addAll(flatten, (T[]) p);
        } else {
            throw new RuntimeException("should be " + type.getName() + 
                                             " or " + type.getName() + 
                                             "[] but was " + p.getClass());
        }
    }
    return flatten.toArray((T[]) Array.newInstance(type, flatten.size()));
}

normalize(String.class, "1", "2", new String[]{"3", "4", null}, null, "7", "8");

3
JLS 15.12.2.4非常明确地规定了我们可以将什么作为可变参数传递。它不会接受/展开数组、可变参数或集合作为可变元参数的一部分。 - default locale
6个回答

6
我认为没有办法将标量和数组隐式地拆分为单个数组。最清晰的解决方案是使用一个辅助函数:
// generic helper function
public static<T> T[] join(T scalar, T[] arr) {
    T[] ret = Arrays.copyOfRange(arr, 0, arr.length + 1);
    System.arraycopy(ret, 0, ret, 1, arr.length);
    ret[0] = scalar;
    return ret;
}

public void separated(String p1, String ... p2) {
    merged(join(p1, p2));
}

这对我来说不够通用。它无法处理 merged(T, T[], T[], T, T) - poussma

2

merge 的签名更改为:

public<T> void merged(T ... p) 

至少会让您能够无问题地调用合并。不过,您需要处理作为参数的字符串数组。


请参见https://dev59.com/7HA85IYBdhLWcg3wJf4Z,最后一个回答由polygenelubricants获得了30个赞。 - Sebastian

0
separated中,参数p2是一个String[]merged方法可以接受单个String[]参数或一系列字符串,但不能混合使用两者,因此我认为你没有太多选择,只能构建一个新的数组(这也是可变参数背后的原理)。

0

来自varargs doc

最后一个参数类型后面的三个点号表示,最后一个参数可以作为数组或一系列参数传递。

它明确说明了一个数组或一系列参数,因此您必须自己展开数组,例如NPE建议的方式。


0

我认为创建一个新的数组是可行的方式。

例如,你可以使用Apache Commons中的ArrayUtils来实现这一点。

ArrayUtils.addAll(p2, p1);

最好的祝福, Thomas


1
可能应该是 ArrayUtils.addAll(new String[]{p1}, p2); - frIT

0

我怀疑你能做到这一点,Java编译器应该将merged(p1, p2)翻译成merged ( p1, p2 [ 0 ], p2 [ 1 ] ... p2 [ p2.length ] ),但我不认为他们曾经想过创建这样一个聪明的编译器(如果merged ( p1, p2 )也存在的话,这也会引发问题)。

相反,你可以这样做(使用伪代码语法):

import com.google.common.collect.Lists
import java.util.Arrays

separated ( String p1, String... p2 ) {
  merged ( Lists.asList ( p1, p2 ) )
}

merged ( List<String> p ) {
...
}

merged ( String ... p ) {
  merged ( Arrays.asList ( p )
}

数组来自于标准JDK,Lists 来自于Guava。两个asList()方法都创建视图,而非副本,所以速度和内存方面不用太担心。

我认为不存在更简单的方法。是的,像其他人建议的那样将其复制到新的数组中更简单,但会消耗时间和内存。如果您的数组很小,创建和访问列表可能比复制慢,但对于较大的数组,复制会变得更加繁琐。

我喜欢Sebastian的建议:您可以定义一个merge()的包装器,使其在运行时平展数组。只要merge()不是第三方方法并且您可以更改它,这种方法就很有效。否则,在这里我正在做类似的事情。


我的需求是找到一种处理任何组合的方法。类似于(String|String[])*Lists.asList只有非常有限的组合。 - poussma
我明白了,您需要一个列表,在调用get(i)时可以展开vararg。它需要创建一个从索引到数组对象的映射,但此时它与复制并没有太大区别,除非您期望其中包含非常大的对象。 - zakmck

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接