Javaメソッドパラメータが有限の型のセットからの任意の型であることを要求できるかどうか疑問に思います。たとえば、2つ(またはそれ以上)の型が共通のメソッドを持つライブラリを使用しています。 、ただし、型階層で最も低い共通の祖先はObjectです。ここでの意味:
public interface A {
void myMethod();
}
public interface B {
void myMethod();
}
...
public void useMyMethod(A a) {
// code duplication
}
public void useMyMethod(B b) {
// code duplication
}
コードの重複を避けたい。私が考えるのは次のようなものです:
public void useMyMethod(A|B obj){
obj.myMethod();
}
Javaにはすでに同様のタイプの構文があります。例:
try{
//fail
} catch (IllegalArgumentException | IllegalStateException e){
// use e safely here
}
明らかに、これは不可能です。このような編集不可能なタイプ階層を使用して、適切に設計されたコードを実現するにはどうすればよいですか?
単一のメソッドinterface
でMyInterface
myMethod
を書くことができます。次に、有限集合の一部として検討するタイプごとに、次のようなラッパークラスを記述します。
class Wrapper1 implements MyInterface {
private final Type1 type1;
Wrapper1(Type1 type1) {
this.type1 = type1;
}
@Override
public void myMethod() {
type1.method1();
}
}
次に、型の有限セットの1つではなくMyInterface
を使用する必要があり、適切な型から適切なメソッドが常に呼び出されます。
これらのラッパークラスを実際に使用してメソッドmyMethod
を呼び出すには、次のように記述する必要があることに注意してください。
myMethod(new Wrapper1(type1));
セット内の各タイプのラッパークラスの名前を覚えておく必要があるため、これは少し醜いものになります。このため、MyInterface
を、ラッパー型を生成するいくつかの静的ファクトリを持つ抽象クラスに置き換えることをお勧めします。このような:
abstract class MyWrapper {
static MyWrapper of(Type1 type1) {
return new Wrapper1(type1);
}
static MyWrapper of(Type2 type2) {
return new Wrapper2(type2);
}
abstract void myMethod();
}
次に、コードを使用してメソッドを呼び出すことができます
myMethod(MyWrapper.of(type1));
このアプローチの利点は、使用するタイプに関係なくコードが同じであるということです。このアプローチを使用する場合は、implements MyInterface
宣言のWrapper1
をextends MyWrapper
に置き換える必要があります。
関数をパラメーターとしてuseMyMethod関数に渡すのはどうですか?
Java <8を使用している場合:
public interface A {
void myMethod();
}
public interface B {
void myMethod();
}
public void useMyMethod(Callable<Void> myMethod) {
try {
myMethod.call();
} catch(Exception e) {
// handle exception of callable interface
}
}
//Use
public void test() {
interfaceA a = new ClassImplementingA();
useMyMethod(new Callable<Void>() {
public call() {
a.myMethod();
return null;
}
});
interfaceB b = new ClassImplementingB();
useMyMethod(new Callable<Void>() {
public call() {
b.myMethod();
return null;
}
});
}
Java> = 8の場合、 ラムダ式 を使用できます:
public interface IMyMethod {
void myMethod();
}
public void useMyMethod(IMyMethod theMethod) {
theMethod.myMethod();
}
//Use
public void test() {
interfaceA a = new ClassImplementingA();
useMyMethod(() -> a.myMethod());
interfaceB b = new ClassImplementingB();
useMyMethod(() -> b.myMethod());
}
要件をモデル化する正しい方法は、AとBの両方が拡張するスーパータイプインターフェイスCでmyMethod()を宣言することです。次に、メソッドはタイプCをパラメーターとして受け入れます。説明する状況でこれを行うのに問題があるという事実は、クラス階層が実際にどのように動作するかを反映する方法でモデル化していないことを示しています。
もちろん、インターフェース構造を変更できない場合は、常にリフレクションを使用して変更できます。
public static void useMyMethod(Object classAorB) throws Exception {
classAorB.getClass().getMethod("myMethod").invoke(classAorB);
}
これはベストプラクティスを構成しないかもしれませんが、複製されたAとBのパーツを含む新しいクラス(Cと呼びます)を作成し、Cを取得する新しいメソッドを作成し、Aとを取得するメソッドを作成できますか? BはCインスタンスを作成し、新しいメソッドを呼び出しますか?
あなたが持っているように
class C {
// Stuff from both A and B
}
public void useMyMethod(A a) {
// Make a C
useMyMethod(c);
}
public void useMyMethod(B b) {
// Make a C
useMyMethod(c);
}
public void useMyMethod(C c) {
// previously duplicated code
}
これにより、AとBのメソッドに重複していないコードを保持することもできます(存在する場合)。
これは、テンプレートパターンによく似ています。
public interface A {
void myMethod();
}
public interface B {
void myMethod();
}
public class C {
private abstract class AorBCaller {
abstract void myMethod();
}
public void useMyMethod(A a) {
commonAndUseMyMethod(new AorBCaller() {
@Override
void myMethod() {
a.myMethod();
}
});
}
public void useMyMethod(B b) {
commonAndUseMyMethod(new AorBCaller() {
@Override
void myMethod() {
b.myMethod();
}
});
}
private void commonAndUseMyMethod(AorBCaller aOrB) {
// ... Loads of stuff.
aOrB.myMethod();
// ... Loads more stuff
}
}
Java 8では、はるかに簡潔です。
public class C {
// Expose an "A" form of the method.
public void useMyMethod(A a) {
commonAndUseMyMethod(() -> a.myMethod());
}
// And a "B" form.
public void useMyMethod(B b) {
commonAndUseMyMethod(() -> b.myMethod());
}
private void commonAndUseMyMethod(Runnable aOrB) {
// ... Loads of stuff -- no longer duplicated.
aOrB.run();
// ... Loads more stuff
}
}
動的プロキシを使用して、定義した共通インターフェースと、新しいインターフェースに準拠する他のインターフェースを実装するオブジェクトとの間にブリッジを作成できます。次に、useMyMethod
sにパラメーターを(動的プロキシとして)新しいインターフェイスに変換させ、新しいインターフェイスのみに関して共通のコードを記述させることができます。
これが新しいインターフェースになります。
interface Common {
void myMethod();
}
次に、この呼び出しハンドラーを使用して:
class ForwardInvocationHandler implements InvocationHandler {
private final Object wrapped;
public ForwardInvocationHandler(Object wrapped) {
this.wrapped = wrapped;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Method match = wrapped.getClass().getMethod(method.getName(), method.getParameterTypes());
return match.invoke(wrapped, args);
}
}
あなたはこのようなあなたの方法を持つことができます:
public void useMyMethod(A a) {
useMyMethod(toCommon(a));
}
public void useMyMethod(B b) {
useMyMethod(toCommon(b));
}
public void useMyMethod(Common common) {
// ...
}
private Common toCommon(Object o) {
return (Common)Proxy.newProxyInstance(
Common.class.getClassLoader(),
new Class[] { Common.class },
new ForwardInvocationHandler(o));
}
問題を単純化するために、既存のインターフェースの1つ(A
またはB
)を共通インターフェースとして使用することもできます。
(別の例を見てください ここ 、そしてこの主題に関する他のアイデアも見てください)
正しい方法は、Java Genericsを使用することです。
http://docs.Oracle.com/javase/tutorial/Java/generics/bounded.html を参照してください。