2010-08-17 2 views
3

API를 발전 시키려고합니다. 이 진화의 일부로 고급 클라이언트가 새 기능에 액세스 할 수 있도록 메소드의 리턴 유형을 서브 클래스 (전문화)로 변경해야합니다. 예 (추한을 무시 : 메소드의 반환 형식이의 서명의 일부이기 때문에자바 이진 호환성 - invokevirtual 의미를 사용하는 공변 반환 형식에 대한 제안 된 솔루션의 RFC

public interface ExtendedEntity extends Entity { 
    boolean b(); 
} 

public interface Intf2 extends Intf1 { 
    ExtendedEntity entity(); 
} 

public interface Main { 
    Intf2 intf(); 
} 

그러나, 클라이언트가 이미 컴파일 :

public interface Entity { 
    boolean a(); 
} 

public interface Intf1 { 
    Entity entity(); 
} 

public interface Main { 
    Intf1 intf(); 
} 

내가 지금 이런 ExtendedEntity, Intf2 및 홈페이지를 갖고 싶어 이전 버전의 코드는 링키지 오류를 보여줍니다 (메소드가 없습니다. iirc).

메인에 다른 리턴 유형으로 메소드를 추가하고 있습니다. 두 가지 메소드 하위 유형 반환) 동일한 구현 메소드 (서브 타입을 돌려 준다)에 맵됩니다. 주 - 내가 아는 한 JVM에서는 허용되지만 Java 스펙에서는 허용되지 않습니다.

내 솔루션은 으로 보이고 필요한 인터페이스를 추가하기 위해 Java 클래스 시스템에 악용 (필자는 다른 말도 없음)이 있습니다.

public interface Main_Backward_Compatible { 
    Intf1 intf(); 
} 

public interface Main extends Main_Backward_Compatible{ 
    Intf2 intf(); 
} 

이제 기존 고객은 (정확한 반환 형식과 방법은 유형 계층 구조에 존재하기 때문에) 올바른 방법은 invokevirtual 조회에 반환해야합니다 실제로 작동 구현은 하위 유형을 반환 하나가 될 것입니다 Intf2.

은 작동하려면입니다. 내가 고안 할 수있는 모든 테스트에서 (반사는 제외 -하지만 그 비트는 신경 쓰지 않는다) 일을했다.
항상 작동합니까? 내 추론 (invokevirtual에 대한)이 맞습니까?

또 다른 관련 질문 - "실제"바이너리 호환성을 확인하는 도구가 있습니까? 내가 찾은 유일한 방법은 각 메소드를 단독으로 살펴 보았지만 유형 계층 구조는 고려하지 않았습니다.

감사합니다.
란.

편집 - (계정 유형 계층 구조를 고려하지 않음) 내가 시도 "너무 좋지 않다"발견했습니다 도구 :

  1. Clirr 0.6.
  2. IntelliJ "APIComparator"플러그인.

편집 2 - 물론 내 클라이언트는 구현 클래스를 내 인터페이스 (서비스 생각) 작성에서 제외됩니다. 그러나 예제를 완성하려면 인터페이스 대신 추상 클래스 (Main의 경우)라고 생각하십시오.

답변

1

결국 solut 이온,하지만 그 전에 작동 증명.

0

기존 인터페이스를 전혀 변경하지 않는 것이 더 간단 할 것입니다. 새로운 인터페이스를 사용하는 사람이라면 어쨌든 새로운 코드를 작성할 것입니다.

기존의 Main.intf() 서명을 구현하면 Intf2의 인스턴스를 반환 할 수 있습니다.

선택적으로, 당신은 캐스팅이 필요없는 새로운 접근 제공 할 수있다 : 이것은 내가 꼼꼼하게 다 읽어 보지 않았 인정하는 것이 긴 충분했다

public interface Main2 extends Main { 
    Intf2 intf2(); 
} 
+0

이것은 한 단계 더 문제를 푸는 것입니다. Main (또는 Main2)을 어딘가에서 제공해야합니다. 이 모든 것을 상향식으로 전파 할 수는 있지만 사실은 엄청난 수의 인터페이스를 다루고 있으며 그 중 아무 것도 변경 (공장, 서비스, 공장, 서비스 등)과 관련이 없습니다. –

+1

Intf1을 반환하도록 선언 된 메서드에서 Intf2. 기존 인터페이스의 수정을 피할 수 있습니다. –

1

을, 그러나 당신이 실제로 활용을 할 수 있습니다 것 같아 여기 제네릭. 당신이 Intf1를 입력하면 나는 전문을 소개하면서 바이너리 호환성을 유지할 수 있다고 생각 :

public interface Intf1<T extends Entity> { 
    T entity(); //erasure is still Entity so binary compatibility 
} 

public interface Intf2 extends Intf1<ExtendedEntity> { //if even needed 
} 

public interface Main { 
    Intf1<ExtendedEntity> intf(); //erasure is still Intf1, the raw type 
} 

편집 # 1 : 바이너리 호환성을 유지하려고 할 때 몇 가지주의 사항이있다. 자세한 내용은 Generics Tutorial 6 및 10 장을 참조하십시오.

편집 # 2 : 그들은 더로에 사용하지 않기 때문에

public interface Main<T, I extends Intf1<T>> { 
    I intf(); //still has the same erasure as it used to, so binary compatible 
} 

오래된 고객이 다음 원시 홈페이지 유형을 사용할 수있을 것입니다 : 당신은뿐만 아니라 Main 입력에이 개념을 확장 할 수 있습니다

재 컴파일이 필요하며 새 클라이언트는 Main에 대한 참조를 입력합니다.

Main<ExtendedEntity, Intf2> myMain = Factory.getMeAMain(); 
Intf2 intf = myMain.intf(); 
+0

이전에 생각하지 않았기 때문에이 작업을 수행 할 수 없습니다. 이제 Intf1은 이미 공용 API에 있습니다. 새로운 인터페이스에 대해 - 매개 변수화 할 항목을 미리 알지 못하므로 다음에 변경 사항에 영향을 줄 필요가있을 때 같은 문제가 발생합니다. 또한 Intf1은 바이너리 호환성을 위반하기 때문에 일반으로 개조 할 수 없습니다 (일반 매개 변수는 변경할 수 있지만 클래스가 매개 변수화되었는지 여부는 변경할 수 없음). –

+0

@Ran : 나는 틀림없이 당신이 틀렸다는 것을 확신합니다. 예를 들어 콜렉션 API가 Java 1.5에서 제네릭으로 개조 된 경우 기존의 모든 코드를 다시 컴파일 할 필요가 없었습니다. 그래, 네가 잘못했다고 생각해. 이 문제에 대한 논의는 http://java.sun.com/j2se/1.5/pdf/generics-tutorial.pdf (Generics Tutorial) (특히 6.3 절 및 10 절)를 참조하십시오. –

+0

@ Mark : 흥미 롭습니다. 나는 이것을 다른 솔루션에 대해 고려해야 할 것이다. (항상 그렇듯이 실제 코드는 장난감 예제보다 복잡하다. Intf2를 반환 할 필요가있다. 메소드를 추가하기 때문이다.) 그러나 당신은 분명히 나에게서 +1을 가지고있다. –