3

I have a stanza of code that is repeatedly used. Here are two examples:

public java.sql.Struct createStruct(String typeName, Object[] attributes) {
  String searchPath = getSearchPath();
  String name = setSearchPathToSchema(typeName);
  Struct ret = delegate().createStruct(name.toLowerCase(), attributes);
  setSearchPath(searchPath);
  return ret;
}

public java.sql.Array createArray(String typeName, Object[] elements) {
  String searchPath = getSearchPath();
  String name = setSearchPathToSchema(typeName);
  Array ret = delegate().createArray(name.toLowerCase(), elements);
  setSearchPath(searchPath);
  return ret;
}

You can see that these two methods have the general form:

public <T> createXXX(String typeName, Object[] objects) {
  String searchPath = getSearchPath();
  String name = setSearchPathToSchema(typeName);
  T ret = delegate().createXXX(name.toLowerCase(), objects);
  setSearchPath(searchPath);
  return ret;
}

Where T is the return type of some set of functions createXXX that have a common signature but different return type.

I'm pretty sure how I would do this in Javascript, F#, C#, Scala or any number of other languages.

I just can't seem to wrap my head around how to do this in Java in a type-safe manner that allows me to call each createXXX method so that the setup and tear-down can happen in one block of code, rather than sprinkled throughout each wrapper method.

I know I could use reflection, but am looking for a way to do this using lambdas, if possible, or whatever structure makes the most sense in Java.

Something like this, where the method to be called is passed as the third parameter (I know this code is completely malformed in Java):

public Struct createStruct(String typeName, Object[] attributes) {
  return createTypeWithCorrectSearchPath<>(
           typeName, 
           attributes, 
           delegate().createStruct
  );
}

public Struct createArray(String typeName, Object[] elements) {
  return createTypeWithCorrectSearchPath<>(
           typeName, 
           elements, 
           delegate().createArray
  );
}

private <T> T createTypeWithCorrectSearchPath(
                String typeName, 
                Object[] objects, 
                [SOME-METHOD-HERE]) {
  String searchPath = getSearchPath();
  String name = setSearchPathToSchema(typeName);
  T ret = [SOME-METHOD-HERE](name, objects);
  setSearchPath(searchPath);
  return ret;
}

I've read the apparently duplicate questions:

  1. How do I pass a method as a parameter in Java 8?
  2. How do I pass method as a parameter in Java?
  3. Java Pass Method as Parameter

As well as some questions about method references with generics:

  1. Java 8 Method reference with generic types
  2. Java method reference to a method with generic parameter

For whatever reason it's not gelling for me, so I'm risking asking a question that will immediately be marked as duplicate in the hopes that someone will take pity and help me out...

UPDATE 2015-02-06

While both Louis Wasserman and Neuron gave basically the same answer, it was Louis that more pedantically offered a pattern that worked best for me... with an explicit example of how to pass the method reference.

Here's what ended up working for me:

interface SearchPathCreator<T> {
  T create(String typeName, Object[] objects) throws SQLException;
}

private <T> T createTypeWithCorrectSearchPath(
                String typeName,
                Object[] objects,
                SearchPathCreator<?> creator) throws SQLException {
  String searchPath = getSearchPath();
  String name = setSearchPathToSchema(typeName);
  //noinspection unchecked
  T ret = (T) creator.create(name.toLowerCase(), objects);
  setSearchPath(searchPath);
  return ret;
}

Which is called just like Louis suggested:

public Struct createStruct(
                String typeName,
                Object[] attributes) throws SQLException {
    return createTypeWithCorrectSearchPath(
             typeName,
             attributes,
             delegate()::createStruct
    );
}
Community
  • 1
  • 1
rbellamy
  • 5,683
  • 6
  • 38
  • 48

3 Answers3

5
interface MyMethodType<T> {
  T method(String name, Object[] objects);
}


private <T> T createTypeWithCorrectSearchPath(
            String typeName, 
            Object[] objects, 
            MyMethodType<T> impl) {
    String searchPath = getSearchPath();
    String name = setSearchPathToSchema(typeName);
    T ret = impl.method(name, objects);
    setSearchPath(searchPath);
    return ret;
}

createTypeWithCorrectSearchPath(typeName, objects, delegate()::createStruct);

The crucial bits are a) creating your own interface type, though I suppose you could use BiFunction<String, Object[]>, b) using method references with ::.

Louis Wasserman
  • 191,574
  • 25
  • 345
  • 413
  • @ajb The OP's question seemed to indicate passing in `delegate()::createStruct` as fine, below the "Something like this" part. I based my answer on that assumption. If the OP hadn't had that example, I would've moved `Delegate` into the interface method. – Louis Wasserman Feb 06 '16 at 20:36
  • This answer got me the furthest. When I use this pattern verbatim, the method reference `delegate()::createStruct` in `createTypeWithCorrectSearchPath` shows `Bad return type in method reference: cannot convert `java.sql.Struct` to `T`. I've updated my question with the structure that ended up working. – rbellamy Feb 06 '16 at 23:37
  • 2
    That seems...really weird. It seems much more likely to me that you want an explicit type argument rather than the unchecked cast you seem to have ended up with. – Louis Wasserman Feb 07 '16 at 00:21
  • 1
    Since this search path stuff seems to be about restoring a previous state, I’d rather use `String searchPath = getSearchPath(); try { return impl.method( setSearchPathToSchema(typeName), objects); } finally { setSearchPath(searchPath); }` – Holger Feb 07 '16 at 11:26
  • @LouisWasserman I would prefer an explicit type argument, but what I ended up with was what was required by the compiler. I'm guessing that the compiler can't determine what T is supposed to be at compile time (in this case it could be a `Struct` or an `Array`), and therefore requires the use of a wildcard. But that's just me guessing... – rbellamy Feb 10 '16 at 07:34
2

You can solve this problem by passing objects which have the sole purpose of performing that one function. Here is a little example to you give you an idea. Create an interface for the type of function. You need to use generics as I did here:

public interface Creator<A>{
    A create(String name, Object[] attributes);
}

Then define your method which takes functions of the type you just specified:

public <A> A create(String typeName, Object[] attributes, Creator<A> creator){
    String searchPath = getSearchPath();
    String name = setSearchPathToSchema(typeName);
    A ret = creator.create(name.toLowerCase(), attributes);
    setSearchPath(searchPath);
    return ret;
}

The most convenient way are anonymous classes where you define the interfaces implementation in line:

java.sql.Struct struct = create("foo bar", new Object[]{"att1", "att2"}, new Creator<Struct>() {
    @Override
    public Struct create(String name, Object[] attributes) {
        return //your implementation..
    }
})

if you can't put the implementation into an anonymous class, because you need to access delegate().createXXX(...), simply put the definition of the class implementing the interface into the a scope where your method becomes accessible.

Neuron
  • 5,141
  • 5
  • 38
  • 59
2

Louis' answer is on the right track; however, it's not clear to me that delegate() would be available at the point where the create method is called. If it isn't available, then you'll need a three-argument interface method.

I don't know what type delegate() returns, but suppose it's Delegate. One thing to note is that if Delegate has an instance method createArray(String s, Object[] objects), you can use Delegate::createArray as a method reference for a functional interface for a function with three arguments. The first argument would be a Delegate. Thus:

interface MyMethodType<T> {
    T method(Delegate delegate, String name, Object[] objects);
}

Now, in your createTypeWithCorrectSearchPath method, you would call the interface like this:

impl.method(delegate(), name.toLowerCase(), objects);

The first parameter of the call would become the instance on which the two-argument instance method operates. That is, if the actual parameter is Delegate::createArray, it would be called like

delegate().createArray(name.toLowerCase(), objects);

Unlike in Louis' answer, you have to define your own interface here, because there's no built-in TriFunction class in Java 8.

See Section 15.13.3 of the JLS for a complete description of how method references can be used. This particular one is listed in the paragraph starting "If the form is ReferenceType :: [TypeArguments] Identifier".

EDIT: After taking another look at the question, I see that createTypeWithCorrectSearchPath was intended to be a private method, and not called from the outside. So this answer probably isn't applicable. I'm leaving it here, though, because it might be a useful answer in some similar situations.

ajb
  • 31,309
  • 3
  • 58
  • 84