Nano Hash - криптовалюты, майнинг, программирование

Ссылки на методы Java 8 и переопределенные методы

Я некоторое время использовал лямбда-выражения и ссылки на методы в Java 8, и есть одна вещь, которую я не понимаю. Вот пример кода:

    Set<Integer> first = Collections.singleton(1);
    Set<Integer> second = Collections.singleton(2);
    Set<Integer> third = Collections.singleton(3);

    Stream.of(first, second, third)
            .flatMap(Collection::stream)
            .map(String::valueOf)
            .forEach(System.out::println);

    Stream.of(first, second, third)
            .flatMap(Set::stream)
            .map(String::valueOf)
            .forEach(System.out::println);

Два потоковых конвейера делают одно и то же, они выводят три числа, по одному на строку. Разница в их второй строке, кажется, вы можете просто заменить имя класса в иерархии наследования, если у него есть метод (интерфейс Collection имеет метод по умолчанию «поток», который не переопределяется в интерфейсе Set). Я проверил, что происходит, если метод переопределяется снова и снова, используя эти классы:

private static class CustomHashSet<E> extends HashSet<E> {
    @Override
    public Stream<E> stream() {
        System.out.println("Changed method!");
        return StreamSupport.stream(spliterator(), false);
    }
}

private static class CustomCustomHashSet<E> extends CustomHashSet<E> {
    @Override
    public Stream<E> stream() {
        System.out.println("Changed method again!");
        return StreamSupport.stream(spliterator(), false);
    }
}

После изменения первого, второго и третьего назначений для использования этих классов я смог заменить ссылки на методы (CustomCustomHashSet::stream), и неудивительно, что они выводили отладочные сообщения во всех случаях, даже когда я использовал Collection::stream. Кажется, вы не можете вызвать супер, переопределенный метод со ссылками на методы.

Есть ли разница во времени выполнения? Что лучше: обратиться к интерфейсу/классу верхнего уровня или использовать конкретный известный тип (Set)? Спасибо!

Изменить: просто чтобы прояснить, я знаю о наследовании и LSP, мое замешательство связано с дизайном ссылок на методы в Java 8. Моя первая мысль заключалась в том, что изменение класса в ссылке на метод изменит поведение, которое он вызовет суперметод из выбранного класса, но как показали тесты, разницы нет. Изменение типов созданных экземпляров меняет поведение.


  • Я всегда представлял, что такие ссылки на методы (например, статическая ссылка на метод экземпляра) работают так же, как и в C++. Экземпляр добавляется как параметр, выполняя что-то вроде A::someMethod = (A a) -> a.someMethod(). Вы также можете передать подкласс A (LSP), после чего сработает динамическая диспетчеризация и вызовет переопределяющую реализацию. 17.09.2016
  • Можете ли вы опубликовать тест, который вы сделали с CustomHashSet и CustomCustomHashSet? Потому что даже при использовании ссылки на метод Collection::stream, если экземпляр объекта CustomHashSet, будет вызван именно CustomHashSet.stream(). Вы уверены, что построили новые CustomHashSet объекты? 17.09.2016
  • @Tunaki извините, если я недостаточно ясно выразился. То, что вы описали, это именно то, что произошло, если я изменил экземпляры на CustomHashSet, тогда CustomHashSet.stream() был вызван, печатая метод Changed! непосредственно перед печатью текущего номера. Мое замешательство связано с дизайном языка, моя первая мысль заключалась в том, что изменение класса в ссылке на метод изменит поведение, точно так же, как вы можете вызвать базовый метод с супер. Но тесты показали, что ничего не меняется, все зависит от конкретного типа инстанса. 17.09.2016

Ответы:


1

Даже ссылки на методы должны соответствовать принципу переопределения методов ООП. В противном случае код типа

public static List<String> stringify(List<?> o) {
    return o.stream().map(Object::toString).collect(Collectors.toList());
}

не будет работать, как ожидалось.

Что касается того, какое имя класса использовать для ссылки на метод: я предпочитаю использовать самый общий класс или интерфейс, который объявляет метод.

Причина в следующем: вы пишете свой метод для обработки коллекции Set. Позже вы увидите, что ваш метод также может быть полезен для набора Collection, поэтому вы соответствующим образом измените сигнатуру метода. Теперь, если ваш код в методе всегда ссылается на метод Set, вам также придется настроить ссылки на эти методы:

Из

public static <T> void test(Collection<Set<T>> data) {
    data.stream().flatMap(Set::stream).forEach(e -> System.out.println(e));
}

to

public static <T> void test(Collection<Collection<T>> data) {
    data.stream().flatMap(Collection::stream).forEach(e -> System.out.println(e));
}

вам также нужно изменить тело метода, тогда как если бы вы написали свой метод как

public static <T> void test(Collection<Set<T>> data) {
    data.stream().flatMap(Collection::stream).forEach(e -> System.out.println(e));
}

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

17.09.2016
  • Я полагаю, что нет никаких причин для того, чтобы ссылки на методы работали иначе, чем классические вызовы методов, когда дело доходит до наследования. Вы ответили на мой вопрос подробными примерами, я отмечаю это как принятый ответ. Спасибо! 18.09.2016

  • 2

    Set является Collection. Collection имеет метод stream(), поэтому Set имеет такой же метод, как и все реализации Set (например, HashSet, TreeSet и т. д.).

    Идентификация метода как принадлежащего какому-либо конкретному супертипу не имеет значения, поскольку он всегда будет разрешаться в фактический метод, объявленный реализацией объекта во время выполнения.


    См. принцип замещения Лисков:

    если S является подтипом T, то объекты типа T могут быть заменены объектами типа S без изменения каких-либо желаемых свойств этой программы.

    17.09.2016
    Новые материалы

    Кластеризация: более глубокий взгляд
    Кластеризация — это метод обучения без учителя, в котором мы пытаемся найти группы в наборе данных на основе некоторых известных или неизвестных свойств, которые могут существовать. Независимо от..

    Как написать эффективное резюме
    Предложения по дизайну и макету, чтобы представить себя профессионально Вам не позвонили на собеседование после того, как вы несколько раз подали заявку на работу своей мечты? У вас может..

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

    Как я автоматизирую тестирование с помощью Jest
    Шутка для победы, когда дело касается автоматизации тестирования Одной очень важной частью разработки программного обеспечения является автоматизация тестирования, поскольку она создает..

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

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

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..