Как реализовать метод с неизвестным количеством аргументов?

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

пример :

public interface A{
    public void transform(Object ... args);
}

public class B implements A{
    public void transform(String a){
        System.out.println(a);
    }
}

public class C implements A{
    public void transform(Integer a, Character b){
        System.out.println(a+b);
    }
}

// super generic case if possible with Objects + primitive
public class D implements A{
    public void transform(int a, String b){
        System.out.println(a+b);
    }
}

Это не сработает. Но я надеюсь, вы поняли. Возможно ли что-то подобное на java ? Как я должен называть их в общем смысле ? Скажем, если у меня есть другой метод, как :

void callTransf(A a, Object ... objs){
    Method m = a.getClass().getMethods()[0];
    m.invoke(a, objs)
}

5 ответов


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

TL; DR

основная идея заключается в том, что transform метод не принимает никаких аргументов. Вместо этого он вернет экземпляр некоторого функционального интерфейса.

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

для представления аргументов разных типов и / или разного количества аргументов для каждого подкласса A интерфейс, мы будем использовать ковариацию в возвращаемом типе метода transform.

это означает, что функциональный интерфейс будет общим (так что тип аргументов может быть разным для каждого подкласса A), и что будут подинтерфейсы, которые расширят этот функциональный интерфейс, каждый из которых принимает другой количество аргументов в одном абстрактном методе. Это позволит transform() возвращаемое значение метода должно иметь либо 1, 2, 3,... аргументы etc.

для выполнения кода, возвращаемого transform() метод, мы будем делать:

instanceOfB.transform().execute("hello");
instanceOfC.transform().execute(1, 'a');
instanceOfD.transform().execute(1, "hello");

наконец, чтобы иметь возможность выполнять код в общем виде, базовый функциональный интерфейс определяет метод varargs executeVariadic(Object... args), который будет реализован как метод по умолчанию каждым дочерним функциональным интерфейсом, делегируя его execute метод и приведение аргументов по мере необходимости.


теперь длинная версия...

давайте начнем с переименования вашего A интерфейс к чему-то более описательному. Как он определяет метод под названием transform, назовем его Transformer.

тогда, давайте создадим функциональный интерфейс, который будет представлять transform метод Transformer интерфейс. Вот оно есть:

@FunctionalInterface
public interface Transformation {

    void executeVariadic(Object... args);
}

этот интерфейс просто определяет один абстрактный метод (SAM), который получает Object... аргумент с varargs. Он существует, чтобы подинтерфейсы могли его переопределить.

теперь, давайте создадим Transformation1 функциональный интерфейс, который расширяет Transformation интерфейс:

@FunctionalInterface
public interface Transformation1<A> extends Transformation {

    void execute(A a);

    @Override
    @SuppressWarnings("unchecked")
    default void executeVariadic(Object... args) {
        this.execute((A) args[0]);
    }
}

этой Transformation1<A> функциональный интерфейс является общим и определяет один абстрактный метод execute, который принимает один аргумент типа A. The executeVariadic метод переопределено как метод по умолчанию, который делегирует его выполнение execute метод, бросая первый аргумент соответственно. Этот бросок генерирует предупреждение, но о, хорошо... нам лучше научиться жить с этим.

Теперь создадим аналогичный интерфейс с двумя параметрами универсального типа и execute метод, который получает два аргумента, типы которых соответствуют параметрам универсального типа:

@FunctionalInterface
public interface Transformation2<A, B> extends Transformation {

    void execute(A a, B b);

    @Override
    @SuppressWarnings("unchecked")
    default void executeVariadic(Object... args) {
        this.execute((A) args[0], (B) args[1]);
    }
}

идея та же:Transformation2 интерфейс расширяет Transformation интерфейс и мы переопределяем executeVariadic метод, чтобы он был делегирован execute метод, бросая аргументы соответственно (и подавляя раздражающее предупреждение).

для полноты картины введем Transformation3 интерфейс, который аналогичен предыдущему TransformationX ones:

@FunctionalInterface
public interface Transformation3<A, B, C> extends Transformation {

    void execute(A a, B b, C c);

    @Override
    @SuppressWarnings("unchecked")
    default void executeVariadic(Object... args) {
        this.execute((A) args[0], (B) args[1], (C) args[2]);
    }
}

надеюсь, что картина уже ясна. Вы должны создать столько TransformationX интерфейсы в качестве аргументов, которые вы хотите поддерживать для transform метод Transformer интерфейс (A интерфейс в вашем вопросе, помните, что я переименовал его).

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

так A интерфейс? Давайте не только изменим его название на Transformer, но и подпись его transform способ:

@FunctionalInterface
public interface Transformer {

    Transformation transform();
}

теперь это ваш базовый интерфейс. The transform метод не имеет аргументов, но возвращает Transformation вместо.

давайте посмотрим, как реализовать свой B, C и D теперь классы. Но сначала, позвольте мне переименовать их в TransformerB, TransformerC и TransformerD, соответственно.

здесь TransformerB:

public class TransformerB implements Transformer {

    @Override
    public Transformation1<String> transform() {
        return a -> System.out.println(a); // or System.out::println
    }
}

важная вещь здесь-использование ковариации в возвращаемом типе transform метод. И я использую Transformation1<String> type, который является подтипом Transformation и указывает, что на TransformerB класс, transform метод возвращает преобразование, которое принимает один аргумент типа String. Как Transformation1 интерфейс-это тип SAM, я использую лямбда-выражение для его реализации.

вот как вызвать код внутри TransformerB.transform способ:

TransformerB b = new TransformerB();
b.transform().execute("hello");

b.transform() возвращает экземпляр Transformation1, которого execute метод немедленно вызывается с помощью его ожидает.

теперь давайте посмотрим на реализацию TransformerC:

public class TransformerC implements Transformer {

    @Override
    public Transformation2<Integer, Character> transform() {
        return (a, b) -> System.out.println(a + b);
    }
}

опять же, ковариация в возвращаемом типе transform метод позволяет нам вернуть конкретный Transformation в этом случае Transformation2<Integer, Character>.

использование:

TransformerC c = new TransformerC();
c.transform().execute(1, 'A');

на TransformerD пример, я использовал преобразование из трех аргументов:

public class TransformerD implements Transformer {

    public Transformation3<Integer, Double, String> transform() {
        return (a, b, c) -> System.out.println(a + b + c);
    }
}

использование:

TransformerD d = new TransformerD();
d.transform().execute(12, 2.22, "goodbye");

это все типобезопасно, потому что общие типы могут быть указаны в TransformationX тип возврата каждого бетона transform способ реализация. Однако использовать примитивные типы невозможно, поскольку примитивные типы нельзя использовать в качестве параметров универсального типа.


относительно того, как назвать transform метод в общем виде, это просто:

void callTransf(Transformer a, Object... args) {
    a.transform().executeVariadic(args);
}

вот почему метод. И это переопределено в каждом TransformationX интерфейс, так что его можно использовать полиморфно, как в коде выше.

вызов callTransf метод простой тоже:

callTransf(b, "hello");
callTransf(c, 1, 'A');
callTransf(d, 12, 2.22, "goodbye");

практичным решением было бы объявить интерфейс как общий:

public interface Transformation<S, R> {
    R transform(S source);
}

параметр типа S играет роль источника; параметр типа R играет роль результата.

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

public final class TransformationSourceForA {
    // Here you declare whatever fields and methods you need for an A source.
    // For example:
    int a;
    String b;
}

public final class TransformationResultForA {
    // Here you declare whatever fields and methods you need for an A result.
}

С этим вы объявляете преобразование следующим образом:

public final class TransformationA implements Transformation<TransformationSourceForA, TransformationResultForA> {
    @Override
    public TransformationResultForA transform(TransformationSourceForA source) { ... }
}

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


то, что вы просите не возможно. Если метод интерфейса использует Varargs, то другие тоже должны. Таким образом, одним из решений было бы использование этого интерфейса обоими классами. Вот общая идея:

public interface A{
    public void transform(char ... args);
}

public class B implements A{
    public void transform(char ... args){
        String s = "";
        for(char c : args){
        s += c;
        }
        System.out.println(s);
    }
}

public class C implements A{
    public void transform(char ... args){
        System.out.println(args[0] + args[1]);
    }
}

теперь, когда вы вызываете метод в B, вы должны преобразовать строку в массив символов:

String str = "example"; 
char[] charArray = str.toCharArray();

при вызове метода в A вы обязательно преобразуете целое число в char:

int i = 5;
transform((char)Character.forDigit(i, 10), 'a'); // 10 stands for number radix which is probably 10  

это не идеальное решение, но оно работает.

но a бит более простое решение без varargs использует только массив char, но снова вам нужно преобразовать входные данные в массив char.

public interface A{
    public void transform(char[]);
} 

public class B implements A{
        public void transform(char[] args){
            String s = "";
            for(char c : args){
            s += c;
            }
            System.out.println(s);
        }
    }

    public class C implements A{
        public void transform(char[] args){
            System.out.println(args[0] + args[1]);
        }
    }

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


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

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

public void implementation(){
System.out.println("method with zero args")
}

public void implementation(String arg1){
System.out.println("method with one args and is:-"+arg1)
}

public void implementation(String arg1,String arg2){
System.out.println("method with two  args and are :-"+arg1+"  "+arg2)
}

если вы не знаете максимальное количество args вы можете реализовать несколькими способами. 1.создайте коллекцию и сохраните их в объекте коллекции и передайте объект в качестве аргумента.

List args= new List();
l.add(arg1)
----------
----------
----------
l.add(argn)

теперь передайте это как аргумент вызова функции as

objecReference.implementation(l)

2.используя ВАР методы арг. это очень простой способ решить такого рода проблемы с java 1.8.

в реализации

public String implementation(int(change to required datattype)...x){
    //here x will act like an array
    for(int a:x){//iam assuming int values are coming
    System.out.println(a)
}
}

теперь вы можете вызвать эту функцию с по крайней мере 0 args, как

    objecReference.implementation()
    objecReference.implementation(10)
    objecReference.implementation(10,20)
    objecReference.implementation(12,23,34,5,6)

в соответствии с вашим требованием вы хотите переопределить метод из своего интерфейса в классе B и C , но вы не можете сделать так, как вы это сделали.

один из способов сделать это так :

public interface A<T> {

    public void transform(T ... args);
}

public class B implements A<String> {

    @Override
    public void transform(String... args) {

    }

}


public class C implements A<Integer> {

    @Override
    public void transform(Integer... args) {

    }

}