[java] 람다를 비교하는 방법이 있습니까?

람다 식 (클로저)을 사용하여 정의 된 객체 목록이 있다고 가정 해 보겠습니다. 비교할 수 있도록 검사하는 방법이 있습니까?

내가 가장 관심있는 코드는

    List<Strategy> strategies = getStrategies();
    Strategy a = (Strategy) this::a;
    if (strategies.contains(a)) { // ...

전체 코드는

import java.util.Arrays;
import java.util.List;

public class ClosureEqualsMain {
    interface Strategy {
        void invoke(/*args*/);
        default boolean equals(Object o) { // doesn't compile
            return Closures.equals(this, o);
        }
    }

    public void a() { }
    public void b() { }
    public void c() { }

    public List<Strategy> getStrategies() {
        return Arrays.asList(this::a, this::b, this::c);
    }

    private void testStrategies() {
        List<Strategy> strategies = getStrategies();
        System.out.println(strategies);
        Strategy a = (Strategy) this::a;
        // prints false
        System.out.println("strategies.contains(this::a) is " + strategies.contains(a));
    }

    public static void main(String... ignored) {
        new ClosureEqualsMain().testStrategies();
    }

    enum Closures {;
        public static <Closure> boolean equals(Closure c1, Closure c2) {
            // This doesn't compare the contents 
            // like others immutables e.g. String
            return c1.equals(c2);
        }

        public static <Closure> int hashCode(Closure c) {
            return // a hashCode which can detect duplicates for a Set<Strategy>
        }

        public static <Closure> String asString(Closure c) {
            return // something better than Object.toString();
        }
    }

    public String toString() {
        return "my-ClosureEqualsMain";
    }
}

유일한 해결책은 각 람다를 필드로 정의하고 해당 필드 만 사용하는 것입니다. 호출 된 메서드를 인쇄하려면을 사용하는 것이 좋습니다 Method. 람다 식을 사용하는 더 좋은 방법이 있습니까?

또한 람다를 인쇄하여 사람이 읽을 수있는 것을 얻을 수 있습니까? this::a대신 인쇄 하는 경우

ClosureEqualsMain$$Lambda$1/821270929@3f99bd52

뭔가 얻을

ClosureEqualsMain.a()

또는 사용 this.toString및 방법.

my-ClosureEqualsMain.a();



답변

이 질문은 사양 또는 구현과 관련하여 해석 될 수 있습니다. 분명히 구현이 변경 될 수 있지만 그럴 경우 코드를 다시 작성할 수 있으므로 두 가지 모두에 답할 것입니다.

또한 수행하려는 작업에 따라 다릅니다. 최적화를 원하십니까, 아니면 두 인스턴스가 동일한 기능을 수행하는지 (또는 동일하지 않은지) 확실한 보장을 찾고 있습니까? (후자의 경우, 두 함수가 같은 것을 계산하는지 여부를 묻는 것과 같은 간단한 문제조차도 결정 불가능하다는 점에서 계산 물리학과 충돌 할 것입니다.)

사양 관점에서 언어 사양은 람다 식을 평가 (호출하지 않음) 한 결과가 대상 기능 인터페이스를 구현하는 클래스의 인스턴스임을 약속합니다. 결과의 정체성이나 앨리어싱 정도에 대해 약속하지 않습니다. 이것은 더 나은 성능을 제공하기 위해 구현에 최대한의 유연성을 제공하기위한 것입니다 (이는 람다가 내부 클래스보다 빠를 수있는 방법입니다. 우리는 내부 클래스가 “must create unique instance”제약 조건에 묶여 있지 않습니다.)

따라서 기본적으로 사양은 참조 동일 (==) 인 두 개의 람다가 동일한 함수를 계산한다는 점을 제외하고는 많은 것을 제공하지 않습니다.

구현 관점에서 좀 더 결론을 내릴 수 있습니다. 람다를 구현하는 합성 클래스와 프로그램의 캡처 사이트 간에는 (현재 변경 될 수 있음) 1 : 1 관계가 있습니다. 따라서 “x-> x + 1″을 캡처하는 두 개의 개별 코드 비트는 서로 다른 클래스에 매핑 될 수 있습니다. 그러나 동일한 캡처 사이트에서 동일한 람다를 평가하고 해당 람다가 캡처되지 않는 경우 동일한 인스턴스를 얻게되며 이는 참조 동등성과 비교할 수 있습니다.

람다가 직렬화 가능하면 성능과 보안을 희생하는 대가로 상태를 더 쉽게 포기할 수 있습니다 (무료 점심 없음).

평등의 정의를 수정하는 것이 실용적 일 수있는 한 영역은 메서드 참조를 사용하는 것입니다. 리스너로 사용하고 적절하게 등록을 해제 할 수 있기 때문입니다. 이것은 고려 중입니다.

나는 당신이 얻고 자하는 것은 : 두 개의 람다가 동일한 기능적 인터페이스로 변환되고 동일한 동작 함수로 표현되고 동일한 캡처 인수를 갖는 경우 동일하다는 것입니다.

불행히도 이것은 (직렬화 할 수없는 람다의 경우, 그 구성 요소를 전혀 얻을 수없는 경우) 어렵고 충분하지 않습니다 (2 개의 개별적으로 컴파일 된 파일이 동일한 람다를 동일한 기능적 인터페이스 유형으로 변환 할 수 있기 때문에). 말할 수 없을 것입니다.)

EG는 이러한 판단을 내릴 수 있도록 충분한 정보를 노출해야하는지 여부와 람다가 더 선택적인 equals/hashCode 또는 더 설명적인 toString을 . 결론은 우리가이 정보를 호출자에게 제공하기 위해 성능 비용을 지불 할 의향이 없다는 것입니다 (나쁜 절충, .01 % 혜택을받는 것에 대해 사용자의 99.99 %를 처벌).

에 대한 결정적인 결론에 toString도달하지 못했지만 나중에 다시 검토 할 수 있도록 공개되었습니다. 그러나이 문제에 대해 양측에서 좋은 주장이있었습니다. 이것은 슬램 덩크가 아닙니다.


답변

labmdas를 비교하기 위해 일반적으로 인터페이스를 확장 Serializable한 다음 직렬화 된 바이트를 비교합니다. 별로 좋지는 않지만 대부분의 경우 작동합니다.


답변

나는 폐쇄 자체에서 그러한 정보를 얻을 가능성을 보지 못합니다. 클로저는 상태를 제공하지 않습니다.

그러나 메소드를 검사하고 비교하려면 Java-Reflection을 사용할 수 있습니다. 물론 성능과 예외로 인해 그다지 아름다운 해결책은 아닙니다. 하지만 이렇게하면 메타 정보를 얻을 수 있습니다.


답변