【问题标题】:Using Java Lambdas like C++-style function pointers?使用像 C++ 风格的函数指针这样的 Java Lambda?
【发布时间】:2017-10-11 02:21:52
【问题描述】:

总的来说,我对 lambdas 和 Java8 还很陌生,所以如果这是一个直截了当的问题,我深表歉意。

我目前有一个类,我想用一堆 getter 对它的 final 做一些复杂的匹配:

public final class Foo {
  public String getName();
  public Type getType();
  public List<Bar> getBarList();
  ....
}

我写了一个小的帮助类来进行匹配:

public class MatchParameter<T> {
  boolean expectMatch = true;
  T valueToCheck;

  public MatchParameter(T value, boolean shouldMatch) {
    this.expectMatch = shouldMatch;
    this.valueToCheck = value;
  }

  public boolean matches(Predicate<T> matcher) {
    return matcher.test(valueToCheck) == expectMatch;
  }
}

然后我有一个地方可以创建各种MatchParameters 用于检查名称或检查List&lt;Bar&gt; 是否包含特定的Bar

MatchParameter<String> nameParam = new MatchParameter(inputName, shouldMatch);
MatchParameter<Bar> barParam = new MatchParameter(inputBar, shouldMatch);
....

目前,我一直无法记住每个 MatchParameter 正在寻找的内容。最终我的检查代码看起来像:

if(nameParam.matches(name -> currentFoo.getName().matches(name)) {
  ...
}
if(barParam.matches(bar -> currentFoo.getBarList().contains(bar)) {
  ...
}
...

在每种情况下,我传递的谓词都是一个 lambda,它有一些 currentFoo.&lt;accessorFns&gt; 的迭代,最终采用类型 T,并返回一个布尔值。我真正想做的是将访问器的逻辑移动到 MatchParameter 的创建中。

有没有一种方法可以指定一个可以在构造时传入的函数指针?我希望它是某种可运行的,需要(Foo,T)

在 C++ 中,我会在 MatchParameter 的局部变量中添加一个接受两个输入(FooT)的本地函数指针:

 bool (*checkFn)(Foo f, T input);

然后我可以为每个实例创建一个匿名函数,并将我的 MatchParameter matches 实现更新为:

public boolean matches(Foo f) {
  return checkFn(f, valueToCheck) == expectMatch;
}

我的实例化是:

MatchParameter<String> nameParam = new MatchParameter<>(
  inputName, 
  shouldMatch, 
  (Foo f, String input) -> boolean { return f.getName().matches(input); }
);

在 Java 中是否有等效的方法来实现这一点?

【问题讨论】:

  • 当然。创建一个接受FooT 并返回boolean 的接口。在你的构造函数中接受它。
  • 我不完全理解你在做什么,但你为什么需要MatchParameter 类?看起来它只是在基本的if 语句中增加了更多内容。
  • 如果同时定义谓词,第一个参数是多余的。为什么不new MatchParameter&lt;&gt;(shouldMatch, f -&gt; f.getname().matches(inputName))
  • @shmosel 在这种情况下你是绝对正确的哈。我从我的实际代码中显着简化了它,MatchParameter 实际上也需要知道它与其他(在我的示例中未列出)函数匹配的值。感谢您指出这一点!

标签: java lambda java-8 function-pointers


【解决方案1】:

如果您的函数应该始终返回布尔值,那么您可以将 BiPredicate 传递给构造函数:

public class MatchParameter<T> {
    boolean expectMatch = true;

    T valueToCheck;

    BiPredicate<Foo, T> predicate;

    public MatchParameter(T value, boolean shouldMatch, BiPredicate<Foo, T> function) {
        this.expectMatch = shouldMatch;
        this.valueToCheck = value;
        this.predicate = predicate;
    }

    public boolean matches(Predicate<T> matcher) {
        return matcher.test(valueToCheck) == expectMatch;
    }
}

然后您可以按如下方式实例化对象:

MatchParameter<String> nameParam = new MatchParameter<>(inputName, shouldMatch,
    (foo, input) -> f.getName().matches(input)
);

最后,您可以创建一个函数来测试BiPredicateFoo 对象:

public boolean matches(Foo f) {
    return predicate.test(f, valueToCheck) == expectMatch;
}

【讨论】:

  • BiPredicate 会更合适。
  • “匿名函数”是什么意思?
  • 我不确定,我主要是从 OP 中复制了这个术语,但在这种情况下它是不正确的。
  • BiPredicate(和你原来的 BiFunction)正是我想要的。谢谢!
  • 不客气!如果您返回 boolean 以外的类型,BiFunction 将很有用。
【解决方案2】:

我认为MatchParameter 类是没有意义的,因为既不需要封装给定的输入值也不需要封装条件。相反,我将根据Foo 的某些属性的条件创建一个Predicate&lt;Foo&gt;,该属性需要匹配给定的输入值。这可以通过接收以下内容的静态方法来实现:

这个静态方法的返回值是Predicate&lt;Foo&gt;。代码如下:

public static <T, U, V> Predicate<T> matchesFor(
    Function<T, U> extractor,
    BiPredicate<U, V> condition,
    V inputValue,
    boolean shouldMatch) {

    return t -> condition.test(extractor.apply(t), inputValue) == shouldMatch;
}

// A handy overload
public static <T, U, V> Predicate<T> matchesFor(
    Function<T, U> extractor,
    BiPredicate<U, V> condition,
    V inputValue) {

    return matchesFor(extractor, condition, inputValue, true);
}

然后,无论您在哪里创建匹配器,请改用 matchesFor 方法:

Predicate<Foo> nameMatcher = matchesFor(
    Foo::getName,    // if Foo's name
    String::matches, // matches
    SOME_REGEX);     // this specific input regex

Predicate<Foo> barsMatcher = matchesFor(
    Foo::getBarList, // if Foo's Bar list
    List::contains,  // contains
    SOME_BAR);       // this specific input Bar

最后,在您的循环中,只需使用上面创建的谓词:

if (nameMatcher.test(currentFoo)) {
    // ...
}

if (barsMatcher.test(currentFoo)) {
    // ...
}

【讨论】:

  • 这比if (currentFoo.getName().matches(SOME_REGEX))好多少?
  • 我认为这更像是一个演示问题
  • 将匹配器数据封装在单个对象中可能很有用,但您的示例并未真正证明这一点。
  • 另外,听起来OP的真实用例比较复杂,所以可能无法重构。请参阅我自己的comment 问题。
  • 这似乎还是有点不必要。简单谓词foo -&gt; foo.getName().matches(SOME_REGEX) 有什么问题? OP 似乎专注于将测试值 (input) 与谓词分开,而您的解决方案将谓词与其目标属性 (foo.name) 分开,这似乎不是本意。附带说明一下,Guava 提供了与 Predicates.compose() 非常相似的功能。
猜你喜欢
  • 1970-01-01
  • 2019-05-22
  • 1970-01-01
  • 1970-01-01
  • 2015-03-22
  • 2013-12-19
  • 2016-01-07
  • 2016-08-02
相关资源
最近更新 更多