“Lambdas”与“泛型”问题的优雅解决方案?

Elegant solution to the "Lambdas" vs "Generics" problem?

提问人:JayC667 提问时间:11/14/2023 最后编辑:user207421JayC667 更新时间:11/14/2023 访问量:80

问:

我在这里寻找一个更漂亮的解决方案。问题在于将泛型表达式和 Lambda 表达式组合在一起。

根据 Java 语言规范,这不可能以我想要的方式进行。

所以我要问你一个问题:你知道处理这个问题的更优雅的方法吗?有什么建议或常见做法吗?

我的“List”类,我需要很多函数:BiPredicate<T, T>

package stackoverflow.genericlambdas;

import java.util.ArrayList;
import java.util.function.BiPredicate;

public class MyListImpl<T> {

    private final ArrayList<T> mItems = new ArrayList<>();


    public void addItem(final T pItem) {
        mItems.add(pItem);
    }

    // assume my library is full of such methods
    public int getFirstIndexOf(final BiPredicate<T, T> pIdentifier, final T pItem) {
        for (int i = 0; i < mItems.size(); i++) {
            final T t = mItems.get(i);
            if (pIdentifier.test(t, pItem)) return i;
        }
        return -1; // not found
    }

}

我的“库”类,我想在其中存储模板:BiPredicate<T, T>

package stackoverflow.genericlambdas;

import java.util.Objects;
import java.util.function.BiPredicate;

public class Library {


    static public class CLUMSY_IDENTITY_CLASS<T> implements BiPredicate<T, T> {
        @Override public boolean test(final T p, final T q) {
            return p == q;
        }
    }
    static public class CLUMSY_EQUALITY_CLASS<T> implements BiPredicate<T, T> {
        @Override public boolean test(final T p, final T q) {
            return Objects.equals(p, q);
        }
    }


    static public final BiPredicate<T, T>   IDENTITY    = (p, q) -> p == q;                 // compilation error
    static public final BiPredicate<T, T>   EQUALITY    = (p, q) -> Objects.equals(p, q);   // compilation error


}

我的“usage”类,我想在其中使用这些模板:

package stackoverflow.genericlambdas;

import java.util.Objects;

public class GenericLamdasUse {

    public static void main(final String[] args) {
        final MyListImpl<String> list = new MyListImpl<>();
        list.addItem("aa");
        list.addItem("bb");
        list.addItem("cc");

        final String testItem = "cc";
        final String testItem2 = "c" + (Math.random() < 2 /* always true */ ? "c" : ""); // this builds a new string that is not == the compile constant String "cc"

        final int whatIKnowWorks1 = list.getFirstIndexOf((p, q) -> Objects.equals(p, q), testItem);
        final int whatIKnowWorks2 = list.getFirstIndexOf((p, q) -> p == q, testItem);

        final int whatIKnowWorks3 = list.getFirstIndexOf(new Library.CLUMSY_IDENTITY_CLASS<>(), testItem); // dont't want the re-instantiation
        final int whatIKnowWorks4 = list.getFirstIndexOf(new Library.CLUMSY_EQUALITY_CLASS<>(), testItem); // for ever time i would need that

        final Library.CLUMSY_IDENTITY_CLASS<String> clumsy5 = new Library.CLUMSY_IDENTITY_CLASS<>(); // dont't want the re-instantiation
        final Library.CLUMSY_EQUALITY_CLASS<String> clumsy6 = new Library.CLUMSY_EQUALITY_CLASS<>();
        final int whatIKnowWorks5 = list.getFirstIndexOf(clumsy5, testItem); // for ever time i would need that
        final int whatIKnowWorks6 = list.getFirstIndexOf(clumsy6, testItem); // also, the types <String> have to match each time

        final int whatIWant1 = list.getFirstIndexOf(Library.EQUALITY, testItem); // I want this, but: compilation error
        final int whatIWant2 = list.getFirstIndexOf(Library.IDENTITY, testItem); // I want this, but: compilation error
    }

}
java 泛型 lambda 匿名函数 anonymous-methods

评论

1赞 markspace 11/14/2023
这里的错误到底是什么?在哪一行,您收到什么错误消息?或者你看到了什么不良行为?
5赞 Slaw 11/14/2023
T必须在上下文中定义;它不能凭空而来。像这样的东西通常是通过静态方法实现的。例如,.public static <T> BiPredicate<T, T> equality() { return Objects::equals; }
0赞 Louis Wasserman 11/14/2023
你的问题是你需要使用函数,而不是常量。(使用常量甚至没有任何好处。
0赞 AndrewL 11/14/2023
考虑将 Kotlin 仅用于这部分开发?Kotlin 编译为 Java 字节码,并将函数(如您想要的谓词)视为一等公民,使您的用例变得非常容易。参考:typealias.com/concepts/first-class-function
1赞 Slaw 11/14/2023
我不知道像 but for 这样的方法。但是,该 lambda 是无状态的,因此如果 JVM 最终缓存单个实例,我不会感到惊讶。Object::equals==

答:

3赞 2 revsSlaw #1

如果需要使用类型变量参数化 s,则无法通过静态字段执行此操作,因为必须在作用域中定义类型变量。像这样的东西通常是通过静态方法实现的。BiPredicate

package stackoverflow.genericlambdas;

import java.util.Objects;
import java.util.function.BiPredicate;

public class Library {

    public static <T> BiPredicate<T, T> identity() {
        return (a, b) -> a == b;
    }

    public static <T> BiPredicate<T, T> equals()
        return Objects::equals;
    }
}

如果您不需要类型变量,请参阅 Sweeper 的答案

2赞 Sweeper 11/14/2023 #2

IDENTITY如果应用 PECS,实际上不需要泛型类型参数。EQUALITY

如果你像这样声明它们:

public static final BiPredicate<? super Object, ? super Object> IDENTITY = (p, q) -> p == q;
public static final BiPredicate<? super Object, ? super Object> EQUALITY = Objects::equals;

请注意,您应该在此处使用,因为 的两个类型参数表示它“使用”的类型。superBiPredicate

然后做同样的事情:getFirstIndexOf

public int getFirstIndexOf(final BiPredicate<? super T, ? super T> pIdentifier, final T pItem) {

现在,您可以简单地传递和到,而不会出现任何错误:IDENTITYEQUALITYgetFirstIndexOf

list.getFirstIndexOf(IDENTITY, "foo");

当然,如果你无法控制 ,那么你就不能这样做,并且需要声明一个在 Slaw 的答案中返回 like 的方法。getFirstIndexBiPredicate

public static <T> BiPredicate<T, T> identity() {
    return (p, q) -> p == q;
}

请注意,JLS 并不保证在调用此类方法时不会创建新对象,但实际上,它通常经过优化,不会创建新对象。

评论

0赞 JayC667 11/14/2023
哇,这也是一个很好的答案!太糟糕了,我不能同时接受两者;-)可悲的是,其中一些lambda实际上可能依赖于T。不过,我会牢记 PECS,记住这一点的有趣方式:-P