Как я могу сослаться на суперметод в классе Java, который реализует интерфейс, но не расширяет другой класс?

У меня есть несколько классов Java, которые расширяют различные реализации универсального интерфейса List. Они просто регистрируют все, что добавляется в список.

Список LoggingArrayList показан ниже. Как следует из названия, он расширяет ArrayList. Класс LoggingLinkedList идентичен классу, за исключением того, что он расширяет LinkedList.

Моя главная цель — избежать дублирования всего общего кода только для того, чтобы я мог использовать другой базовый класс. Я стараюсь максимально придерживаться принципа DRY (не повторяйся).

Прежде всего, пожалуйста, не предлагайте лучший способ регистрации. Это совсем не мое настоящее приложение. Это просто простой способ продемонстрировать проблему, с которой я столкнулся.

У меня два тесно связанных вопроса. Первый вопрос в заголовке. Как я могу сослаться на «супер» метод в классе Java, который реализует интерфейс, но не расширяет другой класс?

Класс LoggingArrayList, как показано ниже, работает нормально, но когда я изменяю объявление класса с ...extends ArrayList на ...implements List, тогда три ссылки на super.method() больше не вызываются, отсюда и мой первый вопрос.

Хороший ответ на мой второй вопрос почти сделает первый вопрос спорным. Второй вопрос заключается в следующем: есть ли способ объявить абстрактный базовый класс или, возможно, интерфейс, который расширяет List с реализациями по умолчанию различных методов add(), чтобы я мог просто расширить этот абстрактный базовый класс или реализовать этот интерфейс и указать только какой список будет основой для конкретного класса?

Например, я хотел бы сделать что-то вроде этого:

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
}

... тогда я мог бы просто реализовать LoggingList один раз для каждой конкретной реализации List, не дублируя весь общий код. Тогда конкретные классы могут выглядеть примерно так, без дополнительного кода внутри их фигурных скобок:

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

Проблема в том, что определение интерфейса, как я предложил, недопустимо (не будет компилироваться), а также ссылки на super.method(s) в коде, показанном ниже, недоступны, если я не сделаю LoggingList абстрактным подклассом вместо интерфейса а потом я снова оказываюсь там, где я сейчас.

Заранее спасибо за любые идеи о том, как выполнить мою СУХУЮ цель.

Вот весь мой класс 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);
    }
}

person David    schedule 04.12.2015    source источник
comment
Расширение коллекций всегда является плохой практикой. Для некоторых более сложных структур также есть ловушки, которых можно избежать, делегируя полномочия вместо расширения. Взгляните на эту статью о расширении классов< /а>.   -  person Dariusz    schedule 04.12.2015
comment
Статья хорошо объясняет вашу точку зрения. На самом деле, я уже рассматривал потенциал этой самой проблемы в случае двух методов addAll. Вы заметите, что я уже гарантирую, что вызывается моя собственная реализация add(), а не предполагаю, что базовый класс всегда будет вызывать ее. Я предполагаю, что подход делегата, по крайней мере, имеет то преимущество, что ему не нужно заменять какую-либо реализацию базового класса или делать какие-либо предположения об этом. Я могу просто добавить свое ведение журнала, не заботясь о том, что фактический список делает внутри.   -  person David    schedule 04.12.2015


Ответы (3)


Один из способов сделать это: делегат.

Вместо того, чтобы иметь несколько реализаций для разных типов списков, вы могли бы иметь только один

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., вы бы звонили superList.

Вам все равно придется адаптировать свои конструкторы от new LoggingLinkedList() к new LoggingList(new LinkedList());, но это не должно иметь большого значения...

person Jan    schedule 04.12.2015
comment
Это также называется декоратором или шаблоном проектирования оболочки: en.wikipedia.org/wiki/Decorator_pattern - person Puce; 04.12.2015
comment
Eclipse позволяет автоматически генерировать методы делегата, alt-s, m. Вы просто выбираете все методы вашего superList в появившемся окне, и все готово. - person Dariusz; 04.12.2015
comment
Спасибо за быстрый ответ. Я рассматривал такой подход, но тогда мне приходится перегружать все методы List только для того, чтобы вызывать соответствующие методы superList. Я бы предпочел использовать наследование и не связываться с незатронутыми методами, если это возможно. - person David; 04.12.2015
comment
Дело не в том, что перегрузить все методы List сложно. Как вы заметили, любая приличная IDE сделает за вас большую часть работы. Просто этот подход приводит к большому количеству избыточного кода, который в первую очередь скрывает реальную цель расширения класса. Полученный код труднее читать, и я все еще получаю просто другую форму повторения. - person David; 04.12.2015
comment
Но вы будете повторяться только один раз — для всех видов списков. Вместо несколько раз для каждого отдельного списка вам нужно обернуть. Есть причина, по которой я также отметил шаблоны дизайна вашего поста. И насколько я понял ваш вопрос, смысл был в том, чтобы добавить ведение журнала к некоторым, если не ко всем методам списка, и сделать это широко применимым ко многим различным реализациям списка? - person Jan; 04.12.2015
comment
Я согласен с тем, что повторять себя один раз — это лучше, чем делать это для каждого класса, хотя в любом случае я использую не так много реализаций List, и один или два правильно размещенных комментария могут прояснить, что цель состоит в том, чтобы только регистрировать добавить методы. Это достаточно распространенный шаблон проектирования, которому нетрудно следовать или реализовать. Я просто надеялся, что есть способ использовать параметр типа (L в моем примере), чтобы определить, что расширяет класс (ArrayList в моем примере). Я могу в конечном итоге пойти с этим подходом, если моя первоначальная идея неосуществима. - person David; 04.12.2015
comment
Кстати, мое реальное приложение будет выполнять некоторую проверку добавляемых элементов и только регистрировать аномалии, а также собирать некоторую статистику о содержимом по мере добавления элементов, поэтому список не нужно повторно просматривать, чтобы получить эту информацию позже. - person David; 04.12.2015
comment
Извините, наследование Java иногда отстой. Но АОП вам не поможет, так как это не ваши классы, так что аннотации к методам тоже не будут работать. - person Jan; 04.12.2015

Для этой цели можно использовать прокси-сервер JDK. Просто погуглите «как использовать прокси-класс jdk».

Здесь описан ваш вариант использования.

person Sergey Morozov    schedule 04.12.2015
comment
Спасибо Хирург. Это очень интересно, хотя это совсем не похоже на ответы, которые я ожидал. Я не видел этот класс раньше. Я обязательно изучу это и посмотрю, полезно ли это для моего приложения. Тем не менее, я хотел бы посмотреть, есть ли способ сделать что-то более традиционное, как мой первоначальный подход, только с правильным синтаксисом, если концепция возможна. - person David; 04.12.2015
comment
@Jan — динамические прокси-объекты прокси для реализации интерфейсов. Он может легко обернуть List и реализовать List. - person OldCurmudgeon; 04.12.2015

Просто для справки в будущем, вот полное рабочее решение, которое я собрал, используя прокси-подход, как предложил Сергей Морозов. Ядро его составляет менее 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);
        }
    }
}
person David    schedule 05.12.2015