【问题标题】:Generic Functional Interface that accepts and returns same type接受并返回相同类型的通用功能接口
【发布时间】:2018-05-12 08:18:05
【问题描述】:

我正在寻找满足以下两个要求的功能接口:

  • 应该接受并返回相同的类型
  • 应在调用 FunctionalInterface 上的方法时推断类型

如果需求只是第一个,我可以创建一个简单的FunctionalInterface,如下所示:

@FunctionalInterface 
public interface MonoFunction<T> {
    T apply (T arg);
}

但是,这需要在使用接口时指定类型。但是,我希望推断类型。类似于下面的伪代码:

class A {
    int a;
}

class B {
    int b;
}

public static void main (String[] args) {
    A a;
    B b;

    MonoFunction foo = (obj) -> {
        system.out.println (obj)
        return obj;
    };

    a = foo.apply (new A());
    b = foo.apply (new B());
}

我如何实现这样的目标?

【问题讨论】:

  • Java 泛型不是这样工作的。您必须在变量声明中指定类型的泛型参数。
  • @Hashken 您是否愿意:(1) 在定义函数时声明类型(例如,MonoFunction&lt;A&gt; foo = ...)但在应用它时不声明,或者(2)失去类型安全性(例如,ClassCastException不当使用MonoFunction时会抛出)?我想我可以去工作。
  • 您正在寻找 UnaryOperator&lt;T&gt; 扩展 Function&lt;T, T&gt; 但我不确定您为什么希望自动推断类型。这行不通。
  • @pkpnd:如何实施选项 2(丢失类型安全)?

标签: java function lambda functional-interface


【解决方案1】:

您可以使用UnaryOperator&lt;T&gt;,但您必须事先定义您期望的类型。

UnaryOperator<A> foo = a -> {
    system.out.println(a);
    return a;
};

否则,只需将结果转换为变量类型:

a = (A) foo.apply (new A());
b = (B) foo.apply (new B());

【讨论】:

    【解决方案2】:

    使用返回函数的通用工厂方法:

    static <T> UnaryOperator<T> getFooFunction() {
        return obj -> {
            System.out.println(obj);
            return obj;
        };
    }
    
    public static void main (String[] args) {
        A a;
        B b;
    
        UnaryOperator<A> fooA = getFooFunction();
        a = fooA.apply(new A());
        UnaryOperator<B> fooB = getFooFunction();
        b = fooB.apply(new B());
        System.out.println(fooA==(Object)fooB);
    }
    

    请注意,getFooFunction() 不仅在语义上返回相同的函数,鉴于当前的实现(HotSpot/OpenJDK),它甚至会是同一个对象,因为您可以通过fooA==(Object)fooB 轻松测试,所以没有理由牺牲泛型的类型安全。

    这与您使用UnaryOperator.identity() 时发生的情况相同。

    【讨论】:

      【解决方案3】:

      其他答案已经讨论了如何使用UnaryOperator&lt;T&gt;。虽然该方法提供了 Java 泛型的类型安全性,但您仍然必须在创​​建 UnaryOperator 时指定类型。虽然在大多数情况下我会推荐 UnaryOperator 方法,但您特别询问(在 cmets 中)如何避免指定类型 &lt;T&gt;,即使您不得不放弃类型安全。

      您可以按如下方式实现MonoFunction(不安全,通常不推荐):

      public class MonoFunction {
          private UnaryOperator<Object> func;
      
          @SuppressWarnings("unchecked")
          public <T> MonoFunction(UnaryOperator<T> func) {
              this.func = (UnaryOperator<Object>) func;
          }
      
          @SuppressWarnings("unchecked")
          public <T> T apply(T obj) {
              return (T) func.apply(obj);
          }
      }
      

      请注意,这不是 @FunctionalInterface,因此您必须将 lambda 表达式放入 new MonoFunction(...) 调用中,如下所示:

      public class MonoFunctionTest {
          public static void main(String[] args) {
              A a;
              B b;
      
              MonoFunction foo = new MonoFunction((obj) -> {
                  System.out.println(obj);
                  return obj;
              });
      
              a = foo.apply(new A()); // toString in A
              b = foo.apply(new B()); // toString in B
      
              MonoFunction bad = new MonoFunction((A obj) -> {
                  System.out.println(obj);
                  return obj;
              });
      
              a = bad.apply(a); // toString in A
              b = bad.apply(b); // ClassCastException: B cannot be cast to A
          }
      }
      
      class A {
          public String toString() { return "toString in A"; }
      }
      class B {
          public String toString() { return "toString in B"; }
      }
      

      我再次强调,这是不安全的,而且得到一个ClassCastException 相对容易,如图所示。

      【讨论】:

        【解决方案4】:

        这可以在功能接口中使用泛型方法:

        @FunctionalInterface
        interface Identity {
            < T > T apply( T input );
        }
        

        不幸的是,这样定义的接口无法使用 lambda 函数实现。相反,它必须以旧的方式使用类来完成,最简洁的是使用匿名类:

        Identity id = new Identity() {
            public < T > T apply( T anyInput ){
                // do something
                return anyInput;
            }
        };
        

        这适用于任何输入:

        class A {
            int a = 1;
        }
        
        String outputString = id.apply( "one" );
        int outputInteger = id.apply( 1 );
        A outputClass = id.apply( new A() );
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-05-01
          • 1970-01-01
          • 2017-03-09
          • 1970-01-01
          相关资源
          最近更新 更多