Java два varargs в одном методе

Есть ли способ в Java, чтобы создать метод, который ожидает двух разных varargs? Я знаю, с тем же типом объекта это невозможно, потому что компилятор не знает, где начинать или заканчивать. Но почему это также невозможно с разными типами объектов?

Например:

public void doSomething(String... s, int... i){
    //...
    //...
}

Есть ли способ создать такой метод? Спасибо!

12 ответов

Решение

Только один вараг, извини. Но использование asList() делает его почти таким же удобным:

 public void myMethod(List<Integer> args1, List<Integer> args2) {
   ...
 }

 -----------

 import static java.util.Arrays.asList;
 myMethod(asList(1,2,3), asList(4,5,6));

В Java только один varargs Аргумент разрешен и должен быть последним параметром подписи.

Но в любом случае все, что он делает, это конвертирует его в массив, так что вам просто нужно сделать два параметра явными массивами:

public void doSomething(String[] s, int[] i){

Возможный дизайн API, в котором вызывающий код выглядит

    doSomething("a", "b").with(1,2);

через "свободный" API

public Intermediary doSomething(String... strings)
{
    return new Intermediary(strings);
}

class Intermediary
{
    ...
    public void with(int... ints)
    {
        reallyDoSomething(strings, ints);
    }
}

void reallyDoSomething(String[] strings, int[] ints)
{
    ...
}

Опасность, если программист забыл позвонить with(...)

    doSomething("a", "b");  // nothing is done

Может быть, это немного лучше

    with("a", "b").and(1, 2).doSomething();

Только один vararg позволено. Это потому, что несколько vararg аргументы неоднозначны. Например, что если вы сдали два varargs одного и того же класса?

public void doSomething(String...args1, String...args2);

Где заканчивается args1 и начинается args2? Или как насчет чего-то более запутанного здесь.

class SuperClass{}
class ChildClass extends SuperClass{}
public void doSomething(SuperClass...args1, ChildClass...args2);

ChildClass расширяет SuperClass и, таким образом, может легально существовать в args1 или args2. Это замешательство, почему только один varargs позволено.

varargs должен также появиться в конце объявления метода.

Просто объявите конкретный тип как 2 массива.

Хотя такие вещи иногда бывают полезны, обычно, если вы обнаружите, что наложены ограничения в Java, вы, вероятно, могли бы что-то изменить и сделать намного лучше. Вот несколько возможных других способов взглянуть на это...

Если два списка связаны между собой, вы, вероятно, захотите создать класс-оболочку для двух разных списков и передать его в оболочку. Обертки вокруг коллекций - почти всегда хорошая идея - они дают вам возможность добавить код, относящийся к коллекции.

Если это способ инициализации данных, проанализируйте их из строки. Например, "abc, 123:def, 456:jhi,789" почти несложно разделить с помощью двух операторов split и цикла (2-3 строки кода). Вы даже можете создать небольшой пользовательский класс синтаксического анализатора, который разбирает строку, подобную этой, в структуру, которую вы передаете в свой метод.

Хм - если честно, если исходить из инициализации данных, я даже не знаю, почему вы захотите сделать это в любом случае, в любом другом случае, и я ожидаю, что вы передадите 2 коллекции и вообще не будете заинтересованы в varags.

Вы можете сделать что-то вроде этого, затем вы можете привести и добавить дополнительную логику внутри этого метода.

public void doSomething(Object... stringOrIntValues) {
    ...
    ...
}

И используйте этот метод так:

doSomething(stringValue1, stringValue2, intValue1, intValue2,         
    intValue3);

Это старая ветка, но я подумал, что это будет полезно в любом случае.

Решение, которое я нашел, не очень аккуратное, но оно работает. Я создал отдельный класс, чтобы справиться с тяжелой работой. В нем только две переменные, которые мне нужны, и их получатели. Конструктор обрабатывает заданные методы самостоятельно.

Мне нужно было передать объекты направления и соответствующий объект данных. Это также решает возможную проблему неравномерных пар данных, но это, вероятно, только для моих потребностей использования.

public class DataDirectionPair{

    Data dat;
    Directions dir;

    public DataDirectionPair(Data dat, Directions dir) {
        super();
        this.dat = dat;
        this.dir = dir;
    }

    /**
     * @return the node
     */
    public Node getNode() {
        return node;
    }

    /**
     * @return the direction
     */
    public Directions getDir() {
        return dir;
    }
}

Я бы тогда просто передал этот класс как vararg для метода

public void method(DataDirectionPair... ndPair){
    for(DataDirectionPair temp : ndPair){
        this.node = temp.getNode();
        this.direction = temp.getDir();
        //or use it however you want
    }
}

Это невозможно, потому что в спецификации языка Java так сказано (см. 8.4.1. Формальные параметры):

Последний формальный параметр метода или конструктора является специальным: это может быть переменный параметр арности, обозначенный многоточием после типа.

Обратите внимание, что многоточие (...) является маркером само по себе (§3.11). Можно поставить пробел между ним и типом, но это не рекомендуется как вопрос стиля.

Если последний формальный параметр является параметром переменной арности, метод является методом переменной арности. В противном случае это метод фиксированной арности.

Относительно того, почему только один и только последний параметр, это было бы предположение, но, вероятно, потому, что разрешение этого может привести к неразрешимым или неоднозначным проблемам (например, рассмотреть, что происходит с method(String... strings, Object... objects)), и только допущение непересекающихся типов может привести к сложностям (например, с учетом рефакторинга, где неожиданно возникли ранее непересекающиеся типы), отсутствию ясности, когда он работает или не работает, и сложности для компилятора, чтобы решить, когда он применим или не.

Следуя за Лемуэлем Адане (не могу прокомментировать пост, из-за отсутствия репутации:))

если вы используете

public void f(Object... args){}

тогда вы можете использовать цикл Как определить класс объекта (в Java)?

как например

{
   int i = 0;
   while(i< args.length && args[i] instanceof String){
         System.out.println((String) args[i]);
         i++ ;
   }
   int sum = 0;
   while(i< args.length){
         sum += (int) args[i];
         i++ ;
   }
   System.out.println(sum);
}

или что-нибудь, что вы намереваетесь сделать.

Я только что прочитал еще один вопрос об этом "шаблоне", но он уже удален, поэтому я хотел бы предложить другой подход к этой проблеме, поскольку я не видел здесь этого решения.

Вместо того, чтобы заставить разработчика обернуть входные параметры в List или Array, будет полезно использовать подход "карри" или лучше шаблон построителя.

Рассмотрим следующий код:

/**
 * Just a trivial implementation
 */
public class JavaWithCurry {

    private List<Integer> numbers = new ArrayList<Integer>();
    private List<String> strings = new ArrayList<String>();

    public JavaWithCurry doSomething(int n) {

        numbers.add(n);

        return this;
    }

    public JavaWithCurry doSomething(String s) {

        strings.add(s);

        return this;

    }

    public void result() {

        int sum = -1;

        for (int n : numbers) {
            sum += n;
        }


        StringBuilder out = new StringBuilder();

        for (String s : strings) {

            out.append(s).append(" ");

        }

        System.out.println(out.toString() + sum);

    }

    public static void main(String[] args) {

        JavaWithCurry jwc = new JavaWithCurry();

        jwc.doSomething(1)
                .doSomething(2)
                .doSomething(3)
                .doSomething(4)
                .doSomething(5)
                .doSomething("a")
                .doSomething("b")
                .doSomething("c")
                .result();

    }

}

Как вы видите себя таким образом, вы можете добавлять новые элементы того типа, который вам нужен, когда вам нужно.

Вся реализация завернута.

Вы можете конвертировать ваши varargs в массивы

public void doSomething(String[] s, int[] i) {
    ...
}

затем с некоторыми вспомогательными методами для преобразования ваших varargs в массив, как это:

public static int[] intsAsArray(int... ints) {
    return ints;
}

public static <T> T[] asArray(T... ts) {
    return ts;
}

Затем вы можете использовать эти вспомогательные методы для преобразования ваших переменных параметров.

doSomething(asArray("a", "b", "c", "d"), intsAsArray(1, 2, 3));

Если вы не собираетесь передавать большое количество строк большую часть времени для первого аргумента, вы можете предоставить группу перегрузок, которые принимают различное число строк и помещают их в массив перед вызовом метода, который принимает массив в качестве Первый аргумент.

public void doSomething(int... i){
    doSomething(new String[0], i);
}
public void doSomething(String s, int... i){
    doSomething(new String[]{ s }, i);
}
public void doSomething(String s1, String s2, int... i){
    doSomething(new String[]{ s1, s2 }, i);
}
public void doSomething(String s1, String s2, String s3, int... i){
    doSomething(new String[]{ s1, s2, s3 }, i);
}
public void doSomething(String[] s, int... i) {
    // ...
    // ...
}
Другие вопросы по тегам