3

APIを進化させようとしています。この進化の一環として、高度なクライアントが新しい機能にアクセスできるようにするには、メソッドの戻り型をサブクラス(特殊化)に変更する必要があります。例(醜いものは無視してください:

public interface Entity {
  boolean a();
}

public interface Intf1 {
  Entity entity();
}

public interface Main {
  Intf1 intf();
}

私は今、次のようにExtendedEntity、Intf2、およびMainを使用したいと考えています。

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

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

public interface Main {
  Intf2 intf();
}

ただし、メソッドの戻り型はそのシグネチャの一部であるため、以前のバージョンのコードですでにコンパイルされているクライアントは、リンケージエラーを示します(メソッドがiircで見つかりません)。

私がやりたいのは、別の戻りタイプを持つメソッドをMainに追加することです。2つのメソッド(1つはスーパータイプを返し、もう1つはサブタイプを返す)は、同じ実装メソッド(サブタイプを返す)にマップする必要があります。注-私が理解している限り、これはJVMで許可されていますが、Java仕様では許可されていません。

私の解決策は、必要なインターフェースを追加するためにJavaクラスシステムを悪用しているようです(私はそれについて他の言葉はありません)。

public interface Main_Backward_Compatible {
  Intf1 intf();
}

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

これで、古いクライアントは正しいメソッドをinvokevirtualルックアップに返します(正しいリターンタイプのメソッドがタイプ階層に存在するため)。実際に機能する実装は、サブタイプIntf2を返す実装になります。

これはうまくいくようです。私が考案できたすべてのテストで(リフレクションを除いて-しかし、私はそのビットについては気にしません)、それ機能しました。
それは常に機能しますか?私の推論(invokevirtualについて)は正しいですか?

そして、別の関連する質問-「実際の」バイナリ互換性をチェックするためのツールはありますか?私が見つけた唯一のものは、それ自体で各メソッドを調べますが、タイプ階層を考慮していません。

ありがとう、
蘭。

編集-私が試したツールで「あまり良くない」ことがわかりました(タイプ階層を考慮しないでください):

  1. Clirr0.6。
  2. IntelliJ「APIComparator」プラグイン。

Edit2-もちろん、私のクライアントは私のインターフェースへの実装クラスを作成することを禁じられています(サービスを考えてください)。ただし、例を完全なものにしたい場合は、インターフェイスではなく抽象クラス(Main用)を検討してください。

4

3 に答える 3

1

結局、ソリューションは必要ありませんでしたが、その前にそれが機能することを証明しました。

于 2011-05-26T13:47:46.260 に答える
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:バイナリ互換性を維持しようとする場合、いくつかの注意点があります。詳細については、ジェネリックスチュートリアルの第6章と第10章を参照してください。

編集#2:

この概念をタイピングMainにも拡張できます。

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();
于 2010-08-17T14:25:07.697 に答える
0

既存のインターフェースをまったく変更しない方が簡単です。新しいインターフェースを使用している人は、とにかく新しいコードを書くことになります。

既存のMain.intf()シグニチャの実装は、Intf2のインスタンスを返すことができます。

オプションで、キャストを必要としない新しいアクセサーを提供できます。

public interface Main2 extends Main {
  Intf2 intf2();
}
于 2010-08-17T14:14:27.400 に答える