简体   繁体   中英

How to pass generic method reference as parameter?

I would like to pass generic method reference as a parameter in java method.

Just for an example I have prepared some abstract

public abstract class AbstractFoobar{

    abstract String getLiteral();
}

and 2 classes that its extend. Classes have String parameter constructor.

public class Bar extends AbstractFoobar{

    String literal;

    public Bar(String literal) { this.literal = literal; }

    public String getLiteral() { return literal; }
}

public class Foo extends AbstractFoobar{

    String literal;

    public Foo(String literal) { this.literal = literal; }

    public String getLiteral() { return literal; }
}

I have a simple generic method that creates new instance of the Objects. The creation bases on Function creator parameter

public <T extends AbstractFoobar> T foo(T foobar, Function<String, T> creator) {
    return creator.apply("foo" + foobar.getLiteral());
}

This example works perfectly when the method foo is executed with specific method reference Bar::new.

@Test
void test()
    Bar bar = new Bar("bar");
    Bar foobar = foo(bar, Bar::new);
    assertEquals(Bar.class, foobar.getClass());
    assertEquals("foobar", foobar.getLiteral());
}

But I dont know how to pass the reference method through the wrapper method fooGenerator

public <T extends AbstractFoobar> T fooGenerator(T foobar) {
    //#1 return foo(foobar, T::new);
    //#2 return foo(foobar, foobar.getClass().getConstructor(String.class));        
}

#1 Compiler cannot instantiate the type T

#2 the method foo(..., Function<>) is not applicable for the argument Constructor<>

A very important, but never emphasized enough, point about Java generics is that they are just a fancy way to avoid writing explicit casts . They are nothing more, nothing less.

So, if you can't write:

public AbstractFoobar fooGenerator(AbstractFoobar foobar) {
    return foo(foobar, /* something here, involving AbstractFoobar and casts */::new);
}

// Call site
Bar foobar = (Bar) foo(bar);

then you can't do it with generics. And there isn't such a thing, because constructors do not participate in inheritance: AbstractFoobar::new would create an instance of AbstractFoobar (if it could be instantiated), not a subclass.

For you current AbstractFoobar definition, you can't do anything better than just invoking foo directly, with the explicit arguments.


The only way you could do it with a single parameter is if the AbstractFoobar had a factory method on it, eg

public abstract class AbstractFoobar<A extends AbstractFoobar<A>> {
    // ...
    abstract A newInstance(String arg);
}

public class Bar extends AbstractFoobar<Bar> {
    // ...
    Bar newInstance(String arg) { return new Bar(arg); }
}

which you could then use like:

public <T extends AbstractFoobar<T>> T fooGenerator(T foobar) {
  return foo(foobar, foobar::newInstance);
}
Bar foobar = foo(bar, Bar::new);

is valid because you reference the constructor for a specific class.

Whereas:

foo(foobar, T::new)

tries to reference the constructor of a generic type. And that is simply not possible. Remember about type erasure, in the (very) end, there are just Object s here, and casts. T::new simply does not denote something meaningful.

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