How to implement a method with an unknown number of arguments?
Asked Answered
T

7

10

I have 1 interface and 3 class. I would like the class to be able to both implement the interface which need a transform method. This method must exist but there can't be more than one per class. I don't know the number of parameters taken by this class.

Example :

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

This doesn't work. But I hope you got the idea. Is something like this possible in java ? How should I call them in a generic way ? Let's say if I have an other method like :

void callTransf(A a, Object ... objs){
    Method m = a.getClass().getMethods()[0];
    m.invoke(a, objs)
}
Titos answered 3/6, 2017 at 17:38 Comment(8)
Are you asking for something like variadic templates? Java doesn't have those. Java can only do e.g. Object... or String... which is syntactic sugar for an array.Gayn
I think transform(int a, Char b) is not working in particular? If so, that's would be because of using primitive type int instead of type variable IntegerBambara
int and char are not Object types. Nor are they the same type (which is required for varargs). I think you may want interface A<T> { public void transform(T... args); } but it's better to describe what you want to do with this, not ask if it is possible to do something like it.Schreibman
@Bambara @Elliott Frisch I have edited my question. Yes, I meant Integer. class B and class C are users made class. They are plugins. I want to make it as simple as possible for them. So the transform method is a method created by the user that MUST exist but with no restriction of parameters. I find it a bit confusing for the user to obligate them to user var args.Titos
Why do you need a common interface for B, C and D when they appear to have quite different methods? Is there really anything common in these classes that an interface would capture?Latoshalatouche
There is no way to express what you want in Java. An interface declares methods that must exist, but those methods are recognized by their signature, meaning the name, number of parameters and type of parameters. You can't just declare an abstract method by its name.Scurvy
@DawoodibnKareem this is a plugin system and I want to call a common method of all plugins that would be written by the user. Anyway, thanks RealSkepic, look like it isn't possible, I'll try to find a way around.Titos
But there is no common method! The methods you've shown in each class are all different. You'd need to know which class you're dealing with, to know which arguments to pass. At least, if each method had the same number of parameters, you could make these classes generic.Latoshalatouche
T
6

You can achieve what you want, with some changes and some help of functional programming...

TL;DR

The main idea is that the transform method doesn't receive any arguments. Instead, it will return an instance of some functional interface.

The implementation of this functional interface will consist of the code that would have been executed by the transform method if it had arguments.

To represent arguments of different types and/or a different number of arguments for each subclass of the A interface, we'll use covariance in the return type of the method transform.

This means that the functional interface will be generic (so that the type of the arguments can be different for each subclass of A), and that there will be subinterfaces that will extend this functional interface, each one accepting a different number of arguments in its single abstract method. This will allow the transform() method's return value to have either 1, 2, 3, ... etc arguments.

To execute the code returned by the transform() method, we'll do:

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

Finally, in order to be able to execute the code in a generic way, the base functional interface defines a varargs method executeVariadic(Object... args), which will be implemented as a default method by every child functional interface, delegating to its execute method and casting the arguments as needed.


Now the long version...

Let's start by renaming your A interface to something more descriptive. As it defines a method called transform, let's name it Transformer.

Then, let's create a functional interface that will represent the transform method of the Transformer interface. Here it is:

@FunctionalInterface
public interface Transformation {

    void executeVariadic(Object... args);
}

This interface just defines one single abstract method (SAM) that receives an Object... varargs argument. It is there so that subinterfaces can override it.

Now, let's create a Transformation1 functional interface that extends the Transformation interface:

@FunctionalInterface
public interface Transformation1<A> extends Transformation {

    void execute(A a);

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

This Transformation1<A> functional interface is generic and defines the single abstract method execute, which takes one argument of type A. The executeVariadic method is overriden as a default method that delegates its execution to the execute method, casting the first argument accordingly. This cast generates a warning, but oh, well... we'd better learn to live with it.

Now, let's create an analogous interface with two generic type parameters and an execute method that receives two arguments whose types match the generic type parameters:

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

The idea is the same: the Transformation2 interface extends the Transformation interface and we override the executeVariadic method so that it is delegated to the execute method, casting the arguments accordingly (and suppressing the annoying warning).

For completeness, let's introduce the Transformation3 interface, which is analogous to the previous 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]);
    }
}

Hope the pattern is clear by now. You should create as many TransformationX interfaces as arguments you want to support for the transform method of your Transformer interface (A interface in your question, remember I've renamed it).

So far so good, I know this answer is long, but I needed to define the interfaces above so that they can now be used to put all the pieces together.

Remember your A interface? Let's not only keep its name changed to Transformer, but also the signature of its transform method:

@FunctionalInterface
public interface Transformer {

    Transformation transform();
}

So this is your base interface now. The transform method no longer has arguments, but returns a Transformation instead.

Let's see how to implement your B, C and D classes now. But first, allow me to rename them to TransformerB, TransformerC and TransformerD, respectively.

Here's TransformerB:

public class TransformerB implements Transformer {

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

The important thing here is the use of covariance in the return type of the transform method. And I'm using the Transformation1<String> type, which is a subtype of Transformation and indicates that, for the TransformerB class, the transform method returns a transformation that accepts one argument of type String. As the Transformation1 interface is a SAM type, I'm using a lambda expression to implement it.

Here's how to invoke the code inside the TransformerB.transform method:

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

b.transform() returns an instance of Transformation1, whose execute method is immediately invoked with the String argument it expects.

Now let's see the implementation of TransformerC:

public class TransformerC implements Transformer {

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

Again, covariance in the return type of the transform method allows us to return a concrete Transformation, in this case Transformation2<Integer, Character>.

Usage:

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

For the TransformerD example, I've used a three-argument transformation:

public class TransformerD implements Transformer {

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

Usage:

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

This is all type-safe, because the generic types can be specified in the TransformationX return type of each concrete transform method implementation. It's not possible to use primitive types, though, because primitive types cannot be used as generic type parameters.


Regarding how to call the transform method in a generic way, it's straightforward:

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

This is why the executeVariadic method exists. And it's overriden in each TransformationX interface, so that it can be used polymorphically, as in the code above.

Calling the callTransf method is straightforward too:

callTransf(b, "hello");
callTransf(c, 1, 'A');
callTransf(d, 12, 2.22, "goodbye");
Tanked answered 4/6, 2017 at 5:21 Comment(0)
H
10

A practicable solution would be to declare the interface as a generic one:

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

The type parameter S plays the source role; the type parameter R plays the result role.

You now can create source and result classes for each different transformation. An example:

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

With that you declare the transformation as following:

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

The principle is to delegate the needs for different fields to a class and not to the method's parameter.

Halation answered 3/6, 2017 at 18:10 Comment(3)
Thanks. This isn't a perfect solution, as I wanted it to be easy & "invisible" to the user developping the plugin. Howewer, it does answer to the question, as known as, how to have a transform method mandatory for the user with a free number of parameters without using varargs. I'm upvoting this answer, and will mark it at the answer if no better solution come up.Titos
My first thought was: crazy question. Your answer gives a solution relying on a simple OO design. Excellent!Ariana
this is a best example to represent how to packaging data into objects. object is more powerful than data, because they have some relative behaviors.Threecolor
T
6

You can achieve what you want, with some changes and some help of functional programming...

TL;DR

The main idea is that the transform method doesn't receive any arguments. Instead, it will return an instance of some functional interface.

The implementation of this functional interface will consist of the code that would have been executed by the transform method if it had arguments.

To represent arguments of different types and/or a different number of arguments for each subclass of the A interface, we'll use covariance in the return type of the method transform.

This means that the functional interface will be generic (so that the type of the arguments can be different for each subclass of A), and that there will be subinterfaces that will extend this functional interface, each one accepting a different number of arguments in its single abstract method. This will allow the transform() method's return value to have either 1, 2, 3, ... etc arguments.

To execute the code returned by the transform() method, we'll do:

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

Finally, in order to be able to execute the code in a generic way, the base functional interface defines a varargs method executeVariadic(Object... args), which will be implemented as a default method by every child functional interface, delegating to its execute method and casting the arguments as needed.


Now the long version...

Let's start by renaming your A interface to something more descriptive. As it defines a method called transform, let's name it Transformer.

Then, let's create a functional interface that will represent the transform method of the Transformer interface. Here it is:

@FunctionalInterface
public interface Transformation {

    void executeVariadic(Object... args);
}

This interface just defines one single abstract method (SAM) that receives an Object... varargs argument. It is there so that subinterfaces can override it.

Now, let's create a Transformation1 functional interface that extends the Transformation interface:

@FunctionalInterface
public interface Transformation1<A> extends Transformation {

    void execute(A a);

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

This Transformation1<A> functional interface is generic and defines the single abstract method execute, which takes one argument of type A. The executeVariadic method is overriden as a default method that delegates its execution to the execute method, casting the first argument accordingly. This cast generates a warning, but oh, well... we'd better learn to live with it.

Now, let's create an analogous interface with two generic type parameters and an execute method that receives two arguments whose types match the generic type parameters:

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

The idea is the same: the Transformation2 interface extends the Transformation interface and we override the executeVariadic method so that it is delegated to the execute method, casting the arguments accordingly (and suppressing the annoying warning).

For completeness, let's introduce the Transformation3 interface, which is analogous to the previous 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]);
    }
}

Hope the pattern is clear by now. You should create as many TransformationX interfaces as arguments you want to support for the transform method of your Transformer interface (A interface in your question, remember I've renamed it).

So far so good, I know this answer is long, but I needed to define the interfaces above so that they can now be used to put all the pieces together.

Remember your A interface? Let's not only keep its name changed to Transformer, but also the signature of its transform method:

@FunctionalInterface
public interface Transformer {

    Transformation transform();
}

So this is your base interface now. The transform method no longer has arguments, but returns a Transformation instead.

Let's see how to implement your B, C and D classes now. But first, allow me to rename them to TransformerB, TransformerC and TransformerD, respectively.

Here's TransformerB:

public class TransformerB implements Transformer {

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

The important thing here is the use of covariance in the return type of the transform method. And I'm using the Transformation1<String> type, which is a subtype of Transformation and indicates that, for the TransformerB class, the transform method returns a transformation that accepts one argument of type String. As the Transformation1 interface is a SAM type, I'm using a lambda expression to implement it.

Here's how to invoke the code inside the TransformerB.transform method:

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

b.transform() returns an instance of Transformation1, whose execute method is immediately invoked with the String argument it expects.

Now let's see the implementation of TransformerC:

public class TransformerC implements Transformer {

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

Again, covariance in the return type of the transform method allows us to return a concrete Transformation, in this case Transformation2<Integer, Character>.

Usage:

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

For the TransformerD example, I've used a three-argument transformation:

public class TransformerD implements Transformer {

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

Usage:

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

This is all type-safe, because the generic types can be specified in the TransformationX return type of each concrete transform method implementation. It's not possible to use primitive types, though, because primitive types cannot be used as generic type parameters.


Regarding how to call the transform method in a generic way, it's straightforward:

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

This is why the executeVariadic method exists. And it's overriden in each TransformationX interface, so that it can be used polymorphically, as in the code above.

Calling the callTransf method is straightforward too:

callTransf(b, "hello");
callTransf(c, 1, 'A');
callTransf(d, 12, 2.22, "goodbye");
Tanked answered 4/6, 2017 at 5:21 Comment(0)
D
1

What you are asking isn't possible. If interface method uses Varargs then others must too. So one solution would be to have both classes use this interface. Here is general idea:

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]);
    }
}

Now when you are calling method in B then you must convert string to char array:

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

When calling method in A you make sure to convert integer to char:

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

This isn't perfect solution but it is working one.

But a bit simpler solution without varargs is using just char array, but again you need to convert inputs to char array.

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]);
        }
    }

Anyway you do it, you will end up with a bit complicated code, even if using generics you must remember that 1 method takes 1 parameter and other one 2. I actually think that it would be best to simply make this methods separate.

Dine answered 3/6, 2017 at 18:13 Comment(1)
Thanks. It works, but seems to heavy for what I'm looking for.Titos
C
1

It's a very old question but I don't see a correct implementation in any of the solution. OP was going the right way and is the correct implementation but needs to be written like this -

public interface A<T>{
    public T transform(Object ... args);
}

public class B implements A{
    public void transform(Object ... args){
        System.out.println((String)args[0]);
    }
}

public class C implements A{
    public void transform(Object ... args){
        Integer a = (Integer)args[0];
        Integer b = (Integer)args[1];
        System.out.println(a+b);
    }
}


 public static void main(String [] vals){
 //Interface A
 A b = new B();
 A c = new C();
 b.transform("Hello");
 c.transform(new Integer(1), 'c');

}    

You will see it's importance if you use Spring or other DI framework then all you need to do is

@Inject
@Qualifier("B") // For Implementation class B
A b;


@Inject
@Qualifier("C") // For Implementation class C
A C

I see accepted answer is very convuluted and in the end, it is just directly calling the implementation class -
Ex:TransformerB b = new TransformerB;
b.transform();
What's the point of creating all the interfaces???

Centrosphere answered 27/2, 2022 at 1:24 Comment(0)
D
0

this is a very interesting Question.you can use method overloading concept if you know the maximum number of arguments coming.

lets say you know that at max user can give 2 parameters then you can do something like this.

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

if you dont know the maximum number of args you can implement in multiple ways. 1.create a collection and store them in collection object and pass the object as argument.

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

now pass this as argument to the function call as

objecReference.implementation(l)

2.using var arg methods. this is the very easy way to solve this kind of problems from java 1.8.

in implementation

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

now you can call this function with atleast 0 args like

    objecReference.implementation()
    objecReference.implementation(10)
    objecReference.implementation(10,20)
    objecReference.implementation(12,23,34,5,6)
Dumbfound answered 3/6, 2017 at 19:58 Comment(2)
Don't use raw types (List args= new List();) if you can avoid them.Cholla
yes.var args is the best way to implement this kind of requirements.Dumbfound
N
0

As per your requirement you want to override method from your interface in class B and C , but you cannot do the way you have done that.

One way to do is as :

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

    }

}
Nanaam answered 5/6, 2017 at 12:9 Comment(1)
You forgot to declare T - and this does not do what the OP wants to achieve.Cholla
C
0

One possible solution could be to use Marker Interfaces. A marker (or tagging) interface is an interface that has no methods or constants inside it. It provides run-time type information about objects.

Here is an example that uses Input interface as transform method parameter. An instance of a class that implements this marker interface can be used as transform method argument.

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());
  }
}
Couchant answered 12/4, 2021 at 13:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.