Лямбда-выражения Java, приведение и компараторы

Я искал исходный код Java для Map интерфейс и столкнулся с этим небольшим фрагментом кода:

    /**
     * Returns a comparator that compares {@link Map.Entry} in natural order on value.
     *
     * <p>The returned comparator is serializable and throws {@link
     * NullPointerException} when comparing an entry with null values.
     *
     * @param <K> the type of the map keys
     * @param <V> the {@link Comparable} type of the map values
     * @return a comparator that compares {@link Map.Entry} in natural order on value.
     * @see Comparable
     * @since 1.8
     */
    public static <K, V extends Comparable<? super V>> Comparator<Map.Entry<K,V>> comparingByValue() {
        return (Comparator<Map.Entry<K, V>> & Serializable)
            (c1, c2) -> c1.getValue().compareTo(c2.getValue());
    }

Из объявления метода я получаю, что это универсальный метод, который возвращает Comparator типа, который либо выводится из записей карты, переданных ему, либо явно предоставленных в методе.

Что действительно сбивает меня с толку - это возвращаемое значение. Похоже, что лямбда-выражение

(c1, c2) -> c1.getValue().compareTo(c2.getValue());

явно приведен к Comparator<Map.Entry<K, V>>, Это правильно?

Я также заметил, что видимый состав включает & Serializable, Я никогда не видел интерфейс, объединенный с классом в приведении ранее, но он выглядит следующим образом в компиляторе:

((SubClass & AnInterface) anObject).interfaceMethod();

Хотя следующее не работает:

public class Foo {
    public static void main(String[] args) {
        Object o = new Foo() {
            public void bar() {
                System.out.println("nope");
            }
        };
        ((Foo & Bar) o).bar();
    }   
}

interface Bar {
    public void bar();
}

Итак, два вопроса:

  1. Как работает добавление интерфейса в группу? Это только принуждает возвращаемый тип метода интерфейса?

  2. Можете ли вы привести лямбда-выражение к Comparator? Что еще они могут быть брошены как? Или лямбда-выражение, по сути, просто Comparator? Может кто-нибудь прояснить все это?

3 ответа

Решение

Как работает добавление интерфейса в группу?

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

Это только принуждает возвращаемый тип метода интерфейса?

Это на самом деле определяет тип, который лямбда будет собирать во время выполнения. Существует LambdaMetaFactory, которая получает этот тип во время выполнения и генерирует дополнительный код, если тип включает Serializable,

Можете ли вы привести лямбда-выражение к Comparator?

Вы можете приводить только ссылку на тип, которым уже является объект. В этом случае вы определяете лямбда, которая должна быть создана Comparator, Вы можете использовать любой тип, который имеет только один абстрактный метод.

Или лямбда-выражение по сути просто компаратор?

Один и тот же лямбда-код можно использовать (копировать + вставлять) в разных контекстах и ​​на разных интерфейсах без изменений. Это не должно быть Comparator как вы увидите во многих других примерах в JDK.

Один я нахожу интересным это count метод на Stream,

Хотя Питер дал отличный ответ, позвольте мне добавить еще для большей ясности.

Лямбда получает свой точный тип только во время инициализации. Это основано на типе цели. Например:

Comparator<Integer> comp = (Integer c1, Integer c2) -> c1.compareTo(c2);
BiFunction<Integer, Integer, Integer> c = (Integer c1, Integer c2) -> c1.compareTo(c2);
Comparator<Integer> compS = (Comparator<Integer> & Serializable) (Integer c1, Integer c2) -> c1.compareTo(c2);

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

Но учтите, что как только тип установлен (во время инициализации), он больше не может быть изменен. Он впитывается на уровне байт-кода. Так что, очевидно, вы не можете пройти c к методу, который ожидает Comparator потому что после инициализации они похожи на обычные объекты Java. (Вы можете посмотреть на этот класс, чтобы поиграть и генерировать лямбды на ходу)


Так в случае:

(Comparator<Map.Entry<K, V>> & Serializable)
            (c1, c2) -> c1.getValue().compareTo(c2.getValue());

Лямбда инициализируется с целевым типом Comparator и Serializable. Обратите внимание, что возвращаемый тип метода просто Comparator, но потому что Serializable также записывается в него во время инициализации, его всегда можно сериализовать, даже если это сообщение теряется в сигнатуре метода.

Теперь обратите внимание, что приведение к лямбде отличается от ((Foo & Bar) o).bar();, В случае лямбды, вы инициализируете лямбда с его типом как объявленным целевым типом. Но с ((Foo & Bar) o).bar();вы вводите переменную o быть Foo и Bar. В первом случае вы устанавливаете тип. В последнем случае у него уже есть тип, и вы пытаетесь везти его на что-то другое. Следовательно, в первом случае он создает исключение ClassCastException, поскольку не может преобразовать o в Bar

Как работает добавление интерфейса в группу?

Для объекта, как обычно. Для лямбды, объяснено выше.

Это только принуждает возвращаемый тип метода интерфейса?

Нет. Java не имеет структурных типов. Так что нет специального типа, основанного на методе. Он просто пытается бросить o как для SO1 а также Bar и это не удается из-за последнего

Можете ли вы привести лямбда-выражение к Comparator? Что еще они могут быть брошены как? Или лямбда-выражение по сути просто компаратор? Может кто-нибудь прояснить все это?

Как объяснено выше. Лямбда может быть инициализирована для любого FunctionalInterface на основе того, что все интерфейсы имеют право на эту лямбду. В приведенных выше примерах вы, очевидно, не можете инициализировать (c1, c2) -> c1.compareTo(c2) предикату

Согласно Спецификации языка Java, оператор приведения (независимо от того, что находится в скобках) может быть ReferenceType, за которым следует один или несколько терминов AdditionalBound, то есть один или несколько типов интерфейса. Кроме того, в спецификации также указывается, что это ошибка времени компиляции, если тип операнда во время компиляции никогда не может быть приведен к типу, указанному оператором приведения в соответствии с правилами преобразования приведения.

В твоем случае Foo не реализует Bar, но этот факт не может быть очевидным во время компиляции, поэтому вы получите ClassCastException потому что, хотя метод, определенный в анонимном классе, имеет ту же сигнатуру, что и в Barобъект явно не реализует Bar, Более того, методы, определенные в анонимных классах, являются скрытыми, если они не вызываются в том же операторе, что и при их определении, т.е.

new MyClass() {
  void doSomethingAwesome() { /* ... */ }
}.doSomethingAwesome();

работает, но это не так:

MyClass awesome = new MyClass() {
  void doSomethingAwesome() { /* ... */ }
};
// undefined method, does not compile!
awesome.doSomethingAwesome();
Другие вопросы по тегам