如何实现具有未知数量参数的方法?

时间:2017-06-03 17:38:29

标签: java interface java-8 variadic-functions

我有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)
}

6 个答案:

答案 0 :(得分:10)

一个切实可行的解决方案是将接口声明为通用接口:

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

原则是将不同字段的需求委托给一个类而不是方法的参数。

答案 1 :(得分:3)

你可以通过一些改变和功能编程的帮助来实现你想要的......

TL; DR

主要思想是transform方法没有收到任何参数。相反,它将返回一些功能接口的实例。

此功能接口的实现将包含transform方法在有参数时执行的代码。

为了表示A接口的每个子类的不同类型和/或不同数量的参数的参数,我们将在方法transform的返回类型中使用协方差。< / p>

这意味着功能接口将是通用的(因此A的每个子类的参数类型可以不同),并且将存在将扩展此功能接口的子接口,每个接口都接受单个抽象方法中的参数数量不同。这将允许transform()方法的返回值包含1,2,3,...等参数。

要执行transform()方法返回的代码,我们会这样做:

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

最后,为了能够以通用方式执行代码,基本功能接口定义了一个varargs方法executeVariadic(Object... args),它将被每个子功能接口实现为默认方法,委托给它execute方法并根据需要转换参数。

现在是长版...

< p>

让我们首先将您的A界面重命名为更具描述性的界面。在定义名为transform的方法时,请将其命名为Transformer

然后,让我们创建一个功能接口,代表transform接口的Transformer方法。这是:

@FunctionalInterface
public interface Transformation {

    void executeVariadic(Object... args);
}

此接口只定义了一个接收Object... varargs参数的抽象方法(SAM)。它存在,以便子接口可以覆盖它。

现在,让我们创建一个扩展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类型的参数。 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接口:

@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();
}

所以现在这是你的基础界面。 transform方法不再有参数,而是返回Transformation

现在让我们看看如何实施您的BCD课程。但首先,请允许我将它们重命名为TransformerBTransformerCTransformerD

此处TransformerB

public class TransformerB implements Transformer {

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

这里重要的是在transform方法的返回类型中使用协方差。我使用Transformation1<String>类型,它是Transformation的子类型,并表示对于TransformerB类,transform方法返回接受的转换类型为String的一个参数。由于Transformation1接口是SAM类型,我使用lambda表达式来实现它。

以下是如何调用TransformerB.transform方法中的代码:

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

b.transform()返回Transformation1的实例,其execute方法会立即调用其预期的String参数。

现在让我们看看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);
}

这就是executeVariadic方法存在的原因。并且它在每个TransformationX界面中都被覆盖,因此它可以多态使用,如上面的代码所示。

调用callTransf方法也很简单:

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

答案 2 :(得分:1)

你问的是不可能的。 如果接口方法使用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中调用方法时,必须将字符串转换为char数组:

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  

这不是完美的解决方案,但它正在运作。

但是没有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个参数而另外1个2.我实际上认为最好简单地将这个方法分开

答案 3 :(得分:0)

这是一个非常有趣的问题。如果你知道参数的最大数量,你可以使用方法重载概念。

假设您知道在最多用户可以提供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)

现在将此作为参数传递给函数调用

objecReference.implementation(l)

2.使用var arg方法。 这是从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)

答案 4 :(得分:0)

根据您的要求,您希望在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) {

    }

}

答案 5 :(得分:0)

一种可能的解决方案是使用标记接口。标记(或标记)接口是一个内部没有方法或常量的接口。它提供有关对象的运行时类型信息。

这是一个使用 Input 接口作为 transform 方法参数的示例。实现此标记接口的类的实例可用作 transform 方法参数。

public interface Input {
}
public interface Transformable {
  void transform(Input input);
}
public class InputForA implements Input {
  int a;
  String b;

  public int getA() {
    return a;
  }

  public InputForA setA(int a) {
    this.a = a;
    return this;
  }

  public String getB() {
    return b;
  }

  public InputForA setB(String b) {
    this.b = b;
    return this;
  }
}
public class TransformerA implements Transformable {
  @Override
  public void transform(Input input) {
    InputForA inputForA = (InputForA) input;
    System.out.println(inputForA.getA() + inputForA.getB());
  }
}