Подтвердить что ты не робот

Java 8 Ссылки на методы, вызываемые локальной переменной

Я нахожусь в процессе изучения Java 8, и я столкнулся с чем-то странным.

Рассмотрим следующий фрагмент:

private MyDaoClass myDao;

public void storeRelationships(Set<Relationship<ClassA, ClassB>> relationships) {
    RelationshipTransformer transformer = new RelationshipTransformerImpl();

    myDao.createRelationships(
            relationships.stream()
            .map((input) -> transformer.transformRelationship(input))
            .collect(Collectors.toSet())
    );
}

По сути, мне нужно сопоставить входной набор, называемый relationships с другим типом, чтобы соответствовать API используемого мной DAO. Для преобразования я хотел бы использовать существующий класс RelationshipTransformerImpl который я создаю в качестве локальной переменной.

Теперь вот мой вопрос:

Если бы я должен был изменить вышеуказанный код следующим образом:

public void storeRelationships(Set<Relationship<ClassA, ClassB>> relationships) {
    RelationshipTransformer transformer = new RelationshipTransformerImpl();

    myDao.createRelationships(
            relationships.stream()
            .map((input) -> transformer.transformRelationship(input))
            .collect(Collectors.toSet())
    );

    transformer = null;  //setting the value of an effectively final variable
}

Я бы, очевидно, получил ошибку компиляции, так как transformer локальной переменной больше не "эффективно финальный". Однако, если заменить лямбду ссылкой на метод:

public void storeRelationships(Set<Relationship<ClassA, ClassB>> relationships) {
    RelationshipTransformer transformer = new RelationshipTransformerImpl();

    myDao.createRelationships(
            relationships.stream()
            .map(transformer::transformRelationship)
            .collect(Collectors.toSet())
    );

    transformer = null;  //setting the value of an effectively final variable
}

Тогда я больше не получаю ошибку компиляции! Почему это происходит? Я думал, что два способа написания лямбда-выражения должны быть эквивалентны, но там явно происходит нечто большее.

4b9b3361

Ответ 1

JLS 15.13.5 может содержать объяснение:

Время оценки выражения ссылки метода более сложное, чем вычисление лямбда-выражений (§15.27.4). Когда ссылочное выражение метода имеет выражение (а не тип), предшествующее разделителю::, это подвыражение оценивается немедленно. Результат оценки сохраняется до тех пор, пока не будет вызван метод соответствующего типа функционального интерфейса; в этот момент результат используется как целевая ссылка для вызова. Это означает, что выражение, предшествующее разделителю::, оценивается только тогда, когда программа встречает ссылочное выражение метода и не переоценивается при последующих вызовах типа функционального интерфейса.

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

Ответ 2

Дикая догадка, но мне, вот что происходит...

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

  • создать поток из аргумента relationships;
  • reaffect transformer;
  • поток разворачивается.

Что генерируется во время компиляции, это сайт вызова; он связан только тогда, когда поток разворачивается.

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

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

Мои два цента...

Ответ 3

В первом примере на transformer ссылаются каждый раз, когда вызывается функция отображения, поэтому один раз для каждого отношения.

В вашем втором примере на transformer ссылаются только один раз, когда transformer::transformRelationship передается в map(). Так что не имеет значения, если это изменится потом.

Это не "два способа написания лямбда-выражения", а лямбда-выражение и ссылка на метод, две отличительные особенности языка.