インターフェイスを実装しているが別のクラスを拡張しないJavaクラスのスーパーメソッドを参照するにはどうすればよいですか?

How can I reference a super method in a Java class that implements an interface but does not extend another class?


質問 written by Jan @2015-12-04 09:36:33Z

: 6 : 3 : 1

汎用のListインターフェースのさまざまな実装を拡張するJavaクラスがいくつかあります。 彼らは単にリストに追加されたものをすべて記録します。

LoggingArrayListを以下に示します。 名前が示すように、ArrayListを拡張します。 LoggingLinkedListクラスは、LinkedListを拡張することを除いて同一です。

私の主な目的は、異なる基本クラスを使用できるように、すべての共通コードを複製する必要を回避することです。 私はできる限りDRY原則(自分自身を繰り返さないでください)を厳守しようとしています。

まず第一に、ロギングのより良い方法を提案しないでください。 それは私の本当のアプリケーションではありません。 これは、私が抱えている問題を簡単にデモする方法です。

2つの密接に関連する質問があります。 最初の質問はタイトルの質問です。 インターフェイスを実装しているが別のクラスを拡張しないJavaクラスの「スーパー」メソッドを参照するにはどうすればよいですか?

以下に示すLoggingArrayListクラスは正常に機能しますが、クラス宣言を... extends ArrayListから... implements Listに変更すると、super.method()への3つの参照が呼び出せなくなるため、最初の質問です。

2番目の質問に対する適切な回答は、最初の質問をほとんど意味のないものにします。 2番目の質問は次のとおりです。抽象基本クラスを宣言する方法、またはさまざまなadd()メソッドの既定の実装でListを拡張するインターフェイスを宣言する方法はありますか。どのようなリストが具体的なクラスの基礎になりますか?

たとえば、私はこのようなことをしたいと思います:

interface LoggingList<T extends Object, L extends List<T>> extends L
{
    // overloaded methods go here as shown below 
    //  with overloaded methods declared as default for the interface
}

...その後、すべての共通コードを複製せずに、Listの具体的な実装ごとにLoggingListを1回だけ実装できます。 具象クラスは次のようになり、中括弧内に追加のコードは必要ありません。

public class LoggingArrayList<T extends Object> implements LoggingList<T, ArrayList<T>> {}
public class LoggingLinkedList<T extends Object> implements LoggingList<T, LinkedList<T>> {}

問題は、私が提案したインターフェイス定義が無効であり(コンパイルされない)、また、LoggingListをインターフェイスではなく抽象サブクラスにしない限り、以下に示すコードのsuper.method(s)への参照が利用できないことですそして、今いる場所に戻ってしまいます。

DRYの目標を達成する方法に関するアイデアを事前に感謝します。

これが、LoggingArrayListクラス全体です。

public abstract class LoggingArrayList<T extends Object>
    extends ArrayList<T>
{
    protected void log(T e)
    {
        System.out.println(e == null ? "null" : e.toString());
    }

    @Override
    public boolean add(T e) {
        log(e);
        // How do I reference a super.method()
        // in a class that implements an interface
        // but does not extend another class?
        return super.add(e);
    }

    @Override
    public boolean addAll(Collection<? extends T> clctn) {
        boolean anyChanges = false;
        for(T e : clctn)
        {
            // ensure that we call our overridden version of add()
            //  so it gets logged.
            anyChanges = anyChanges || add(e);
        }
        return anyChanges;
    }

    @Override
    public boolean addAll(int i, Collection<? extends T> clctn) {
        for(T e : clctn)
        {
            // ensure that we call our overridden version of add() 
            //  so it gets logged.
            add(i, e);
            i++; // keep the inserted elements in their original order
        }
        return !clctn.isEmpty();
    }

    @Override
    public T set(int i, T e) {
        log(e);
        // How do I reference a super.method()
        // in a class that implements an interface
        // but does not extend another class?
        return super.set(i, e);
    }

    @Override
    public void add(int i, T e) {
        log(e);
        // How do I reference a super.method()
        // in a class that implements an interface
        // but does not extend another class?
        super.add(i, e);
    }
}
コメント 1

コレクションの拡張は常に悪い習慣です。より複雑な構造の場合、拡張する代わりに委任することで回避できる落とし穴もあります。クラスの拡張に関するこの記事をご覧ください

written by ダリウス @2015-12-04 09:31:55Z

コメント 2

この記事はあなたの主張を説明するのに良い仕事をしている。実際、2つのaddAllメソッドの場合、その問題の可能性をすでに検討しました。基本クラスが常にそれを呼び出すと仮定するのではなく、add()の独自の実装が呼び出されることをすでに保証していることに注意してください。少なくとも、デリゲートアプローチには、基本クラスの実装を置き換えたり、それについて仮定したりする必要がないという利点があると思います。実際のリストが内部で行っていることを気にすることなく、ログを追加することができます。

written by デビッド @2015-12-04 10:36:53Z

回答 1 written by Jan @2015-12-04 09:27:22Z
3

これを行う方法は、デリゲートです。

異なる種類のリストに複数の実装をする代わりに、1つだけ持つことができます

public class LoggingList<T extends Object> implements List<T>

  protected List<T> superList;

  public LoggingList(List<T> anotherList) {
     superList= anotherList;
  }

  protected void log(T e) {
      System.out.println(e == null ? "null" : e.toString());
  }

  @Override
  public boolean add(T e) {
      log(e);
      return superList.add(e);
  }

そして、 super.を呼び出す代わりにsuper. superList.呼び出しsuperList.

コンストラクターをnew LoggingLinkedList()からnew LoggingList(new LinkedList());に適応させる必要がありますnew LoggingList(new LinkedList()); -しかし、それは大したことではないはずです...

コメント 1

これは、デコレータまたはラッパーデザインパターンとも呼ばれます: en.wikipedia.org/wiki/Decorator_pattern

written by Puce @2015-12-04 09:32:25Z

コメント 2

Eclipseでは、デリゲートメソッド、 alt-s、mを自動的に生成できます。表示されるウィンドウでsuperListすべてのメソッドを選択するだけで完了です。

written by ダリウス @2015-12-04 09:33:32Z

コメント 3

早速のお返事ありがとうございます。そのアプローチを検討しましたが、対応するsuperListメソッドを呼び出すために、すべてのListメソッドをオーバーロードする必要があります。可能であれば、影響を受けないメソッドを台無しにするのではなく、継承を使用したいと思います。

written by デビッド @2015-12-04 09:36:08Z

コメント 4

すべてのListメソッドをオーバーロードするのが難しいというわけではありません。あなたが指摘するように、まともなIDEはあなたのためにほとんどの仕事をします。このアプローチでは、最初にクラスを拡張するという本当の意味を曖昧にする多くの冗長なコードになってしまうだけです。結果として得られるコードは読みづらく、私は自分自身を繰り返さなければならないという別の形になってしまいます。

written by デビッド @2015-12-04 09:45:54Z

コメント 5

しかし、すべての種類のリストに対して、自分自身を一度だけ繰り返すことになります。異なるリストごとに数回ではなく、ラップする必要があります。投稿に「デザインパターン」というタグを付けた理由もあります。そして、私があなたの質問を理解した限り-ポイントはリストのすべてではないにしてもいくつかのメソッドにロギングを追加し、それを多くの異なるリスト実装に広く適用できるようにすることでしたか?

written by 1 @2015-12-04 09:52:00Z

回答 2 written by Sergey Morozov @2015-12-04 09:45:40Z
1

この目的のためにJDKプロキシを使用できます。 「jdkプロキシクラスの使用方法」をGoogleで検索するだけです。

ここでは、ユースケースについて説明します。

コメント 1

外科に感謝します。これは非常に興味深いですが、私が期待していた答えとはまったく異なります。私はこのクラスを見たことがありません。間違いなく調べて、アプリケーションに役立つかどうかを確認します。それでも、コンセプトが実現可能な場合は正しい構文を使用して、元のアプローチのような従来の方法を実行する方法があるかどうかを確認したいと思います。

written by デビッド @2015-12-04 10:20:29Z

コメント 2

@Jan-インターフェイスを実装するための動的プロキシプロキシオブジェクト。List 簡単にラップしてList 実装できます。

written by -OldCurmudgeon @2015-12-04 10:41:38Z

コメント 3

あなたが正しい。間違って理解しました。

written by @2015-12-04 10:43:12Z

回答 3 written by David @2015-12-05 07:32:50Z
1

将来の参考のために、Sergey Morozovが提案したプロキシアプローチを使用してまとめた完全な実用的なソリューションを以下に示します。 コアは50行未満のコードです。 最後に含まれるコードの半分以上は、実際の機能の単体テストです。

最終的にこのアプローチを自分の目的に使用するかどうかは定かではありませんが、非常に有用な演習でした。 それを提案してくれてありがとう、セルゲイ。

public class LoggingListProxyFactory
{
    protected static void log(String methodName, Object element)
    {
        System.out.println(methodName
                + ": ["
                + (element == null ? "null" : element.toString())
                + "]"
        );
    }

    public static <T extends Object> List<T> getProxy(final List<T> list) {
        return (List<T>) Proxy.newProxyInstance(
                list.getClass().getClassLoader(),
                new Class[]{ List.class },
                new LoggingInvocationHandler(list)
        );
    }

    private static class LoggingInvocationHandler<T>
            implements InvocationHandler
    {
        final List<T> underlyingList;

        public LoggingInvocationHandler(List<T> list) {
            this.underlyingList = list;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable
        {
            // These are the List interface methods that we want to log.
            // In every case, the new elements happen to be the last parameter.
            //
            // boolean add(Object e)
            // void add(int index, Object element)
            // boolean addAll(Collection c)
            // boolean addAll(int index, Collection c)
            // Object set(int index, Object element)
            String methodName = method.getName();
            if( ( "add".equals(methodName)
                | "addAll".equals(methodName)
                | "set".equals(methodName)
                )
                // a few additional probably unnecessary checks
                && args != null
                && args.length == method.getParameterCount()
                && method.getParameterCount() > 0
                )
            {
                log(methodName, args[args.length-1]);
            }
            return method.invoke(underlyingList, args);
        }
    }

    public void testGetProxy() {
        List<String>[] testLists = new List[] {
            new ArrayList<>(),
            new LinkedList<>()
        };
        for(List<String> aList : testLists)
        {
            List<String> proxy = LoggingListProxyFactory.getProxy(aList);

//          aList.add(42); // type is enforced at compile time
            aList.add(aList.getClass().getSimpleName());
            aList.add("unlogged");
            aList.add(null);

//          proxy.add(42); // type is enforced at compile time
            proxy.add(proxy.getClass().getSimpleName());
            // exercise each the methods that are being logged
            proxy.add("foo");
            proxy.add(0, "bar");
            proxy.add(null);
            proxy.addAll(aList);
            proxy.addAll(7, aList);
            proxy.set(5, "five");

            System.out.println();
            System.out.println(aList.getClass().getSimpleName()
                    + ".size() = " + aList.size());
            aList.stream().forEach(System.out::println);

            System.out.println();
            System.out.println("proxy.size() = " + proxy.size());
            proxy.stream().forEach(System.out::println);
        }
    }
}