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) {
// ...
// ...
}