简体   繁体   中英

Call method on template argument without knowing a base class in Java

I would like to write a generic algorithm, which can be instantiated with different objects. The objects are coming from 3rdparty and they have no common base class. In C++, I just write the generic algorithm as a template which takes the particular object as its argument. How to do it in Java?

template <class T>
class Algorithm
{
    void Run(T& worker)
    {
        ...
        auto value = workder.DoSomething(someArgs);
        ...
    }
};

In C++, I don't need to know anything about the T, because the proper types and availability of methods are checked during compilation. As far as I know, in Java I must have a common base class for all my workers to be able to call methods on them. Is it right? Is there a way how to do similar stuff in Java?

I can't change my 3rdparty workers, and I don't want to make my own abstraction of all workers (including all types which the workers are using, etc.).

Edit:

Since I want to write the generic algorithm only once, maybe it could be a job for some templating language which is able to generate Java code (the arguments to the code template would be the workers)?

My solution: In my situation, where I cannot change the 3rdparty workers, I have chosen Java code generation. I have exactly the same algorithm, I only need to support different workers which all provides identical interface (classes with same names, same names of methods, etc.). And in few cases, I have to do a small extra code for particular workers.

To make it more clear, my "workers" are in fact access layers to a proprietary DB, each worker for a single DB version (and they are generated). My current plan is to use something like FreeMaker to generate multiple Java source files, one for each DB version, which will have only different imports.

The topic to look into for you: generics

You can declare a class like

public class Whatever<T> {

which uses a T that allows for any reference type. You don't need to further "specialize" that T mandatorily. But of course: in this case you can only call methods from Object on instances of T .

If you want to call a more specific method, then there is no other way but somehow describing that specification. So in your case, the reasonable approach would be to introduce at least some core interfaces.

In other words: there is no "duck typing" in Java. You can't describe an object by only saying it has this or that method. You always need a type - and that must be either a class or an interface.

Duck typing isn't supported in Java. It can be approximated but you won't get the convenience or power you're used to in C++.

As options, consider:

  • Full-on reflection + working with Object - syntax will be terrible and the compiler won't help you with compilation checks.
  • Support a pre-known set of types and use some sort of static dispatching, eg a big switch / if-else-if block, a type -> code map, etc. New types will force changing this code.
  • Code generation done during annotation processing - you may be able to automate the above static-dispatch approach, or be able to create a wrapper type to each supported type that does implement a common interface. The types need to be known during compilation, new types require recompilation.

EDIT - resources for code generation and annotation processing:

If you really don't have any way to get it done correctly with generics you may need to use reflection.

class A {
    public String doIt() {
        return "Done it!";
    }
}

class B {
    public Date doIt() {
        return Calendar.getInstance().getTime();
    }
}

interface I {
    public Object doIt();
}

class IAdapter implements I {
    private final Object it;

    public IAdapter(Object it) {
        this.it = it;
    }


    @Override
    public Object doIt() {
        // What class it it.
        Class<?> itsClass = it.getClass();
        // Peek at it's methods.
        for (Method m : itsClass.getMethods()) {
            // Correct method name.
            if (m.getName().equals("doIt")) {
                // Expose the method.
                m.setAccessible(true);
                try {
                    // Call it.
                    return m.invoke(it);
                } catch (Exception e) {
                    throw new RuntimeException("`doIt` method invocation failed", e);
                }
            }
        }
        // No method of that name found.
        throw new RuntimeException("Object does not have a `doIt` method");
    }
}

public void test() throws Exception {
    System.out.println("Hello world!");
    Object a = new IAdapter(new A()).doIt();
    Object b = new IAdapter(new B()).doIt();
    System.out.println("a = "+a+" b = "+b);
}

You should, however, make every effort to solve this issue using normal type-safe Java such as Generics before using reflection.

In Java all your Workers must have a method DoSomething(someArgs), which doesn't necessarily imply that they extend the same base class, they could instead implement an interface Worker with such a method. For instance:

public interface Worker {
    public Double DoSomething(String arg1, String arg2);
}

and then have different classes implement the Worker interface:

One implementation of Worker :

public class WorkerImplA implements Worker{
    @Override
    public Double DoSomething(String arg1, String arg2) {
        return null; // do something and return meaningful outcome
    }
}

Another implementatin of Worker :

public class WorkerImplB implements Worker{
    @Override
    public Double DoSomething(String arg1, String arg2) {
        return null; // do something and return meaningful outcome
    }
}

The different WorkerImpl classes do not need to extend the same common base class with this approach, and as of JavaSE 8 interfaces can have a default implementation in any method they define.

Using this approach Algorithm class would look like:

public class Algorithm {

    private String arg1;
    private String arg2;

    public Algorithm(String arg1, String arg2){
        this.arg1 = arg1;
        this.arg2 = arg2;
    }

    public void Run(Worker worker){
        worker.DoSomething(arg1, arg2);
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM