2017-11-30 1 views
3

정확히 하나의 추상 메소드가있는 몇 개의 인터페이스가 있다고 가정합니다. 약간의 트릭이 나 람다에 대한 인터페이스 A를 사용하여 제한하고 그렇게 할 시도에서 빌드를 실패 해킹 :특정 인터페이스에서 람다 제한

interface A { 
    int c(); 
} 

interface B { 
    int c(); 
} 

public class Main { 
    public static void main(String... args) { 
     A a =() -> 42; 
     B b =() -> 42; 
    } 
} 

짧은 질문 : 이러한 인터페이스를 갖는, 나는 그것으로 람다를 선언 할 수 있습니까? 더러운 것이 든 더러운 것이 든 환영합니다 ('더티'는 편집/바이트 코드 수준의 해킹을 의미합니다. 소스 코드와 바람직하게는 공개 계약에 영향을주지 않습니다).

긴 이야기 : 일부 인터페이스 구현 자의 경우 계약의 일부로 equals/hashCode을 정의하는 것이 좋습니다. 또한 빌드시에 자동으로 equals/hashCode을 생성합니다.

이 문맥에서 람다는 말썽 꾸러기입니다. 인터페이스 A의 일반 및 익명 구현자를 위해 .class 파일을 찾고 빌드시 바이트 코드를 계측 할 수 있습니다. 람다에게는 런타임에 생성 된 VM 익명 클래스가 있습니다. 그러한 클래스에 영향을 미치는 것은 빌드시에는 불가능한 것처럼 보이기 때문에 적어도 특정 인터페이스 집합에 대해서는 그러한 경우를 금지해야합니다.

+1

"긴 이야기"에서 이미 모든 이유를 설명했습니다. 충분히 상세하지 않니? – skapral

+0

잘 람다는'equals'와'hashCode'를 가질 수 없으므로, 왜이 인터페이스가'@ FunctionalInterface'로 사용되는 것을 금지합니까? – Eugene

+0

@Eugene, equals와 hashCode를 정의하면 인터페이스 'A'가 제공하는 계약의 일부라고 상상해보십시오. 그것의 javadoc에서 말하고있는 것처럼 -'인터페이스 A의 모든 구현자는 equals와 hashCode를 정의해야하고 지침은 ... 도구 X가 생성 할 수있다 '. 그리고 누군가는 람다 (lambdas)와 함께 계약을 맹렬히 깨우고 도구 X는 그것에 대해 아무 것도 할 수 없습니다. 인터페이스 'A'를 정의 할 때 함수 인터페이스라고 생각하지 않았습니다. 나는 그것이 하나의 방법을 가진 인터페이스 일 뿐이라고 원했다. – skapral

답변

2

이 내 솔루션에서 봐 주시기 바랍니다 :

package com.example.demo; 

public class LambdaDemo { 

    public static void main(String[] args) { 
     //doesn't compile 
     //LambdaRestrictedInterface x =() -> {}; 
     LambdaRestrictedInterface y = new Test(); 
     y.print(); 
    } 

    private static class Test implements LambdaRestrictedInterface { 
     @Override 
     public void print() { 
      System.out.println("print"); 
     } 
    } 

    public interface MyInterface { 
     void print(); 
    } 

    public interface LambdaRestrictedInterface extends MyInterface { 
     @Override 
     default void print() { 
      //hack prevents lambda instantiating 
     } 
    } 
} 

https://dumpz.org/2708733/

아이디어는 기본 IMPL 발신자로부터

편집과 부모 인터페이스를 무시하는 것입니다 몇 가지 고려 후를, 나는 이 정답을 받아들이기로 결정했기 때문에 (정식으로는 내 요구 사항에 가장 적합하고 구현하기에는 약간의 비용이 듭니다) 공식적인 추가 사항이 있습니다. 실제로, 최소 계기는 인터페이스가 람다 형식으로 사용되는 것을 막기에 충분하므로 기본 구현을 추상 메소드에 추가하는 것입니다.

+0

귀하의 예는 나에게 좋은 힌트를주었습니다. 이론적으로, 나는 단지 인터페이스 'A'의 메소드를 디폴트로 만들고 런타임 예외를 던지면 lambda 타입이되는 것을 막을 수 있습니다. 그것은 꽤 잘할 것입니다. 단점은 개발자에게는 투명하지 않으며 인터페이스의 코드에는 약간의 영향을 미친다는 것입니다. 그러나 나는 그걸로 살아갈 수 있다고 생각합니다. – skapral

+0

네, 회사/팀에 적용된 접근법의 문제입니다. 즉 런타임 예외가 필요한 경우라면 괜찮습니다. 개인적으로 컴파일 시간 검사를 선호합니다. –

+0

물론 우리 모두는 유형 안전성과 빠른 속도로 실패 할 수있는 접근 방식을 좋아합니다. 그러나 인생은 힘들다. 내가 처음에 일종의'@ NonFunctionalInterface'를 개발하는 것이 왜 그렇게 어려운지 궁금합니다. – skapral

2

조금 놀아 보니 invokedynamic 호출의 desc 필드에 구현중인 인터페이스가 들어있는 것처럼 보입니다. 당신이 빌드 타임 해킹을 할 수 있다면 그래서

mv.visitInvokeDynamicInsn("run", "()Ljava/lang/Runnable;", new Handle...

: 예를 들어, 때 나는 Runnable를 간단한 () -> {}을 만든 다음 ASM's Bytecode Outline 플러그인을 통해 통과은 "ASM-는 ified"호출처럼 보였다 호출 사이트에서 (필자가 할 수 있다고 생각하지 않는 주석이 아닌 lambda-able으로 주석을 표시하는 것과는 대조적으로) 먼저 허용되지 않는 인터페이스 세트를 컴파일 한 다음 invokedynamic ' 그 세트에 대한 desc.

+0

(필자는 실제로이 작업을 전혀 해본 적이 없기 때문에 이것을 커뮤니티 위키로 사용하고 있습니다. 답변의 세균 만 제공하고 있습니다.) – yshavit

+0

답변을 주셔서 감사합니다. 그것은 실행 가능해 보인다. non-lambda 인터페이스에 주석을다는 것은 실제로 문제가 아닙니다. 나는 아직도 세트를 어떻게 든 결정해야한다. – skapral

+0

'hashCode' /'equals' 메쏘드를 인터페이스에 삽입 할 수 없기 때문에 람다 생성 사이트를 원하는 구현 클래스를 생성 할 수있는 다른 부트 스트랩 메소드로 리다이렉트 (또는 상속받을 수퍼 클래스를 지원)해야합니다. 그러나 그것은 가능합니다. – Holger

관련 문제