Почему этот java-общий метод не компилируется? [Дубликат]

Я использую ниже код на сегодняшний день и дату базы данных.

(TIMESTAMPDIFF(MINUTE,NOW(),EndDate))%60 as Minutes

Здесь мы можем использовать HOUR (в течение часа -% 24), DAY (в течение дней - не нужно%) вместо MINUTE, если нам нужно.

34
задан Josh Stone 5 July 2015 в 17:56
поделиться

6 ответов

Под капотом

Используя некоторые скрытые функции javac, мы можем получить более подробную информацию о том, что происходит:

$ javac -XDverboseResolution=deferred-inference,success,applicable LambdaInference.java 
LambdaInference.java:16: Note: resolving method foo in type Foo to candidate 0
    Foo.foo(value -> true).booleanValue(); // Compile error here
       ^
  phase: BASIC
  with actuals: <none>
  with type-args: no arguments
  candidates:
      #0 applicable method found: <T>foo(Bar<T>)
        (partially instantiated to: (Bar<Object>)Object)
  where T is a type-variable:
    T extends Object declared in method <T>foo(Bar<T>)
LambdaInference.java:16: Note: Deferred instantiation of method <T>foo(Bar<T>)
    Foo.foo(value -> true).booleanValue(); // Compile error here
           ^
  instantiated signature: (Bar<Object>)Object
  target-type: <none>
  where T is a type-variable:
    T extends Object declared in method <T>foo(Bar<T>)
LambdaInference.java:16: error: cannot find symbol
    Foo.foo(value -> true).booleanValue(); // Compile error here
                          ^
  symbol:   method booleanValue()
  location: class Object
1 error

Это много информации, давайте сломаем его .

LambdaInference.java:16: Note: resolving method foo in type Foo to candidate 0
    Foo.foo(value -> true).booleanValue(); // Compile error here
       ^
  phase: BASIC
  with actuals: <none>
  with type-args: no arguments
  candidates:
      #0 applicable method found: <T>foo(Bar<T>)
        (partially instantiated to: (Bar<Object>)Object)
  where T is a type-variable:
    T extends Object declared in method <T>foo(Bar<T>)

фаза: применяется применимость фаза : фактические аргументы, переданные в аргументах типа-args: явные типы: потенциально применимые методы

имеет значение <none>, поскольку наша неявно типизированная лямбда не является , относящейся к применимости .

Компилятор разрешает ваш вызов foo единственному методу назван foo в Foo. Он частично был создан для Foo.<Object> foo (поскольку не было никаких фактических данных или типов-аргументов), но это может измениться на стадии отложенного вывода.

LambdaInference.java:16: Note: Deferred instantiation of method <T>foo(Bar<T>)
    Foo.foo(value -> true).booleanValue(); // Compile error here
           ^
  instantiated signature: (Bar<Object>)Object
  target-type: <none>
  where T is a type-variable:
    T extends Object declared in method <T>foo(Bar<T>)

экземплярная подпись: полностью созданная подпись foo. Это результат этого шага (в этот момент больше не будет сделан вывод о типе на подпись foo). target-type: контекст, в который делается вызов. Если вызов метода является частью назначения, он будет левым. Если вызов метода сам по себе является частью вызова метода, это будет тип параметра.

Поскольку ваш вызов метода свисает, нет целевого типа. Поскольку нет целевого типа, больше никаких выводов на foo и T не предполагается Object.


Анализ

Компилятор не использует неявно типизированные лямбда во время вывода. В определенной степени это имеет смысл. В общем, учитывая param -> BODY, вы не сможете скомпилировать BODY, пока у вас не будет типа для param. Если вы попытались вывести тип для param из BODY, это может привести к проблеме типа курица и яйцо. Возможно, некоторые улучшения будут сделаны в этом в будущих выпусках Java.


Решения

Foo.<Boolean> foo(value -> true)

Это решение обеспечивает явный тип аргумент foo (обратите внимание на раздел with type-args ниже). Это изменяет частичное копирование сигнатуры метода на (Bar<Boolean>)Boolean, что вы хотите.

LambdaInference.java:16: Note: resolving method foo in type Foo to candidate 0
    Foo.<Boolean> foo(value -> true).booleanValue(); // Compile error here
       ^
  phase: BASIC
  with actuals: <none>
  with type-args: Boolean
  candidates:
      #0 applicable method found: <T>foo(Bar<T>)
        (partially instantiated to: (Bar<Boolean>)Boolean)
  where T is a type-variable:
    T extends Object declared in method <T>foo(Bar<T>)
LambdaInference.java:16: Note: resolving method booleanValue in type Boolean to candidate 0
    Foo.<Boolean> foo(value -> true).booleanValue(); // Compile error here
                                    ^
  phase: BASIC
  with actuals: no arguments
  with type-args: no arguments
  candidates:
      #0 applicable method found: booleanValue()

Foo.foo((Value<Boolean> value) -> true)

Это решение явно вводит вашу лямбду, что позволяет учесть его применимость (примечание with actuals ниже). Это изменяет частичную инстанцировку сигнатуры метода на (Bar<Boolean>)Boolean, что вам нужно.

LambdaInference.java:16: Note: resolving method foo in type Foo to candidate 0
    Foo.foo((Value<Boolean> value) -> true).booleanValue(); // Compile error here
       ^
  phase: BASIC
  with actuals: Bar<Boolean>
  with type-args: no arguments
  candidates:
      #0 applicable method found: <T>foo(Bar<T>)
        (partially instantiated to: (Bar<Boolean>)Boolean)
  where T is a type-variable:
    T extends Object declared in method <T>foo(Bar<T>)
LambdaInference.java:16: Note: Deferred instantiation of method <T>foo(Bar<T>)
    Foo.foo((Value<Boolean> value) -> true).booleanValue(); // Compile error here
           ^
  instantiated signature: (Bar<Boolean>)Boolean
  target-type: <none>
  where T is a type-variable:
    T extends Object declared in method <T>foo(Bar<T>)
LambdaInference.java:16: Note: resolving method booleanValue in type Boolean to candidate 0
    Foo.foo((Value<Boolean> value) -> true).booleanValue(); // Compile error here
                                           ^
  phase: BASIC
  with actuals: no arguments
  with type-args: no arguments
  candidates:
      #0 applicable method found: booleanValue()

Foo.foo((Bar<Boolean>) value -> true)

То же, что и выше, но с немного отличающийся вкус.

LambdaInference.java:16: Note: resolving method foo in type Foo to candidate 0
    Foo.foo((Bar<Boolean>) value -> true).booleanValue(); // Compile error here
       ^
  phase: BASIC
  with actuals: Bar<Boolean>
  with type-args: no arguments
  candidates:
      #0 applicable method found: <T>foo(Bar<T>)
        (partially instantiated to: (Bar<Boolean>)Boolean)
  where T is a type-variable:
    T extends Object declared in method <T>foo(Bar<T>)
LambdaInference.java:16: Note: Deferred instantiation of method <T>foo(Bar<T>)
    Foo.foo((Bar<Boolean>) value -> true).booleanValue(); // Compile error here
           ^
  instantiated signature: (Bar<Boolean>)Boolean
  target-type: <none>
  where T is a type-variable:
    T extends Object declared in method <T>foo(Bar<T>)
LambdaInference.java:16: Note: resolving method booleanValue in type Boolean to candidate 0
    Foo.foo((Bar<Boolean>) value -> true).booleanValue(); // Compile error here
                                         ^
  phase: BASIC
  with actuals: no arguments
  with type-args: no arguments
  candidates:
      #0 applicable method found: booleanValue()

Boolean b = Foo.foo(value -> true)

Это решение обеспечивает явную цель для вызова вашего метода (см. target-type ниже). Это позволяет отложенному-инстанцированию выводить, что параметр типа должен быть Boolean вместо Object (см. instantiated signature ниже).

LambdaInference.java:16: Note: resolving method foo in type Foo to candidate 0
    Boolean b = Foo.foo(value -> true);
                   ^
  phase: BASIC
  with actuals: <none>
  with type-args: no arguments
  candidates:
      #0 applicable method found: <T>foo(Bar<T>)
        (partially instantiated to: (Bar<Object>)Object)
  where T is a type-variable:
    T extends Object declared in method <T>foo(Bar<T>)
LambdaInference.java:16: Note: Deferred instantiation of method <T>foo(Bar<T>)
    Boolean b = Foo.foo(value -> true);
                       ^
  instantiated signature: (Bar<Boolean>)Boolean
  target-type: Boolean
  where T is a type-variable:
    T extends Object declared in method <T>foo(Bar<T>)

Отказ от ответственности

Это поведение, которое происходит. Я не знаю, является ли это то, что указано в JLS. Я мог бы копаться и посмотреть, могу ли я найти точный раздел, который определяет это поведение, но нотация типа ввода дает мне головную боль.

Это также не полностью объясняет, почему изменение Bar для использования raw Value исправит эту проблему:

LambdaInference.java:16: Note: resolving method foo in type Foo to candidate 0
    Foo.foo(value -> true).booleanValue();
       ^
  phase: BASIC
  with actuals: <none>
  with type-args: no arguments
  candidates:
      #0 applicable method found: <T>foo(Bar<T>)
        (partially instantiated to: (Bar<Object>)Object)
  where T is a type-variable:
    T extends Object declared in method <T>foo(Bar<T>)
LambdaInference.java:16: Note: Deferred instantiation of method <T>foo(Bar<T>)
    Foo.foo(value -> true).booleanValue();
           ^
  instantiated signature: (Bar<Boolean>)Boolean
  target-type: <none>
  where T is a type-variable:
    T extends Object declared in method <T>foo(Bar<T>)
LambdaInference.java:16: Note: resolving method booleanValue in type Boolean to candidate 0
    Foo.foo(value -> true).booleanValue();
                          ^
  phase: BASIC
  with actuals: no arguments
  with type-args: no arguments
  candidates:
      #0 applicable method found: booleanValue()

По какой-то причине изменение этого параметра на использование raw Value позволяет отложенному экземпляру сделать вывод о том, что T является Boolean. Если бы мне пришлось размышлять, я бы предположил, что, когда компилятор пытается привязать лямбда к Bar<T>, он может сделать вывод, что T - Boolean, глядя на тело лямбда. Это означает, что мой предыдущий анализ неверен. Компилятор может выполнять вывод типа в теле лямбда, но только в переменных типа, которые только появляются в возвращаемом типе.

28
ответ дан Jeffrey 16 August 2018 в 06:43
поделиться

Легкий способ исправить это объявление типа в вызове метода foo:

Foo.<Boolean>foo(value -> true).booleanValue();

Редактировать: Я не могу найти конкретную документацию о почему это необходимо, почти так же, как и все остальные. Я подозревал, что это может быть из-за примитивных типов, но это было неправильно. Независимо от того, этот синтаксис вызывается с использованием целевого типа . Также Тип цели в Lambdas . Однако причины не ускользают от меня, я не могу найти документацию в любом месте о том, почему этот конкретный случай использования необходим.

Редактировать 2: Я нашел этот вопрос:

Вывод общего типа не работает с цепочкой методов?

Похоже, это потому, что вы цепляете методы здесь. Согласно комментариям JSR, указанным в принятом ответе, это было преднамеренное упущение функциональности, потому что у компилятора нет способа передать полученную информацию общего типа между цепными вызовами метода в обоих направлениях. В результате весь тип стираемого по времени до вызова booleanValue. Добавление целевого типа удаляет это поведение, предоставляя ограничение вручную, а не позволяя компилятору принять решение, используя правила, изложенные в JLS §18 , что, похоже, не упоминает об этом вообще. Это единственная информация, которую я мог бы придумать. Если кто-нибудь найдет что-нибудь лучше, я бы хотел его увидеть.

4
ответ дан Community 16 August 2018 в 06:43
поделиться
  • 1
    Я не уверен, нужна ли ссылка JLS. Мне просто интересно, почему вывод здесь не работает, и если есть какие-то изменения, которые я могу внести в API, чтобы заставить его работать. В идеале, я не хочу требовать от моих пользователей вручную указывать аргумент типа здесь. – Josh Stone 5 July 2015 в 05:46
  • 2
    Все дело в том, как они делают вывод типа с их алгоритмом и примитивами. Вы пытались вернуть Boolean.TRUE, а не просто правду? Я не перед IDE, я не могу проверить на данный момент. – Brian 5 July 2015 в 05:48
  • 3
    Возврат объекта, такого как String или Boolean.TRUE, дает такую ​​же проблему. – Josh Stone 5 July 2015 в 05:52
  • 4
    Если это была проблема с методами цепочки, я бы не ожидал, что изменение подписи к T apply(Value value); будет иметь какой-либо положительный эффект, но это произойдет. – sstan 5 July 2015 в 06:50
  • 5
    @Brian Этот синтаксис не называется целевым типом. Целевым типом будет Boolean b = Foo.foo(value -> true); Синтаксис, который вы описываете (Foo.<Boolean> foo(value->true)), известен как type witness . – Jeffrey 5 July 2015 в 18:59

Я не знаю, почему, но вам нужно добавить отдельный тип возвращаемого значения:

public class HelloWorld{
static class Value<T> {
}

@FunctionalInterface
interface Bar<T,R> {
      R apply(Value<T> value); // Return type added
}

static class Foo {
  public static <T,R> R foo(Bar<T,R> callback) {
      return callback.apply(new Value<T>());
  }
}

void test() {
  System.out.println( Foo.foo(value -> true).booleanValue() ); // No compile error here
}
     public static void main(String []args){
         new HelloWorld().test();
     }
}

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

1
ответ дан fukanchik 16 August 2018 в 06:43
поделиться
  • 1
    Это приближает нас, но value в выражении лямбда выведено на Value<Object> вместо Value<Boolean>, что все равно вызовет проблемы в моем реальном сценарии, где тело выражения лямбда содержит больше кода ... – Josh Stone 5 July 2015 в 05:28
  • 2
    Не уверен, что вы имеете в виду. Я предполагаю, что вам нужно ввести подстановочный знак, если вы хотите вызвать методы на T static class Value<T extends Boolean>, interface Bar<T extends Boolean,R>, public static <T extends Boolean,R> R foo(Bar<T,R> callback) и т. Д. – fukanchik 5 July 2015 в 05:48
  • 3
    Конечно, замените Boolean своим классом. – fukanchik 5 July 2015 в 05:48
  • 4
    Кажется, что-то вроде этого может работать, за исключением того, что тип возврата из лямбды может быть чем угодно (контролируется пользователями). Boolean - всего лишь пример ... – Josh Stone 5 July 2015 в 05:55

Проблема

Значение будет вызывать тип Value<Object>, потому что вы неправильно интерпретировали лямбда. Подумайте об этом, как вы называете лямбдой непосредственно методом применения. Итак, что вы делаете:

Boolean apply(Value value);

, и это правильно выводится на:

Boolean apply(Value<Object> value);

, поскольку вы не указали тип значения.

Простое решение

Правильно вызовите лямбда:

Foo.foo((Value<Boolean> value) -> true).booleanValue();

это будет выведено на:

Boolean apply(Value<Boolean> value);

(My) Рекомендуемое решение

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

Я создал общий интерфейс обратного вызова, общий класс Value и UseClass, чтобы показать, как его использовать.

Интерфейс обратного вызова

/**
 *
 * @param <P> The parameter to call
 * @param <R> The return value you get
 */
@FunctionalInterface
public interface Callback<P, R> {

  public R call(P param);
}

Класс значения

public class Value<T> {

  private final T field;

  public Value(T field) {
    this.field = field;
  }

  public T getField() {
    return field;
  }
}

Использование класса Class

public class UsingClass<T> {

  public T foo(Callback<Value<T>, T> callback, Value<T> value) {
    return callback.call(value);
  }
}

TestApp с основным

public class TestApp {

  public static void main(String[] args) {
    Value<Boolean> boolVal = new Value<>(false);
    Value<String> stringVal = new Value<>("false");

    Callback<Value<Boolean>, Boolean> boolCb = (v) -> v.getField();
    Callback<Value<String>, String> stringCb = (v) -> v.getField();

    UsingClass<Boolean> usingClass = new UsingClass<>();
    boolean val = usingClass.foo(boolCb, boolVal);
    System.out.println("Boolean value: " + val);

    UsingClass<String> usingClass1 = new UsingClass<>();
    String val1 = usingClass1.foo(stringCb, stringVal);
    System.out.println("String value: " + val1);

    // this will give you a clear and understandable compiler error
    //boolean val = usingClass.foo(boolCb, stringVal);
  }
}
1
ответ дан NwDev 16 August 2018 в 06:43
поделиться
  • 1
    Этот ответ, похоже, немного уходит от сценария примера, что упрощает реальный API. Две вещи: метод foo должен быть статическим и должен принимать один аргумент. – Josh Stone 5 July 2015 в 17:37
  • 2
    @JoshStone Какой API вы говорите. То, что я сделал, в JavaFX немного напоминает обратный вызов для Factory Cell Value Factory. То, что вы хотите, статическое, не проблема, я думаю. Но только одно значение? Извините, но тогда вам, вероятно, нужен предикат , а не функция или обратный вызов. – NwDev 5 July 2015 в 17:40
  • 3
    Стоит упомянуть еще раз - это будет частью публичного API, поэтому он должен быть прост в использовании. Пользователь должен иметь возможность предоставить базовую лямбда, например value -> true, для которой значение будет выведено на Value<Boolean>, и метод, принимающий лямбда, вернется Boolean. Любые другие изменения прекрасны, но они должны оставаться на месте, чтобы использовать API. – Josh Stone 5 July 2015 в 17:40
  • 4
    Хм, мне API должен быть гибким и абстрактным. То, что вы сделали в Bar, работает с конкретным типом, это совсем не гибко. – NwDev 5 July 2015 в 17:45
  • 5
    Это просто пример, иллюстрирующий проблему. Целью настоящего API является не гибкость, а простота, поэтому мы хотим, чтобы foo принимал один аргумент, чтобы сделать эту работу. – Josh Stone 5 July 2015 в 17:49

Как и другие ответы, я также надеюсь, что кто-то умнее может указать , почему компилятор не может сделать вывод, что T - Boolean.

Один путь чтобы помочь компилятору поступать правильно, без каких-либо изменений в вашем существующем дизайне класса / интерфейса, явным образом объявляю тип формального параметра в вашем лямбда-выражении. Итак, в этом случае, явно объявив, что тип параметра value равен Value<Boolean>.

void test() {
  Foo.foo((Value<Boolean> value) -> true).booleanValue();
}
2
ответ дан sstan 16 August 2018 в 06:43
поделиться
  • 1
    Я могу немного изменить дизайн класса / интерфейса, пока вызов Foo.foo выводит тип возвращаемого значения boolean, а value в лямбда выведен на Value<Boolean>. Я не хочу показывать, так как это часть публичного API и требует, чтобы приведение создало проблему удобства использования. – Josh Stone 5 July 2015 в 05:49
  • 2
    Чтобы быть ясным, мой пример не выполняет кастинг. Он просто явно описывает формальный тип параметра лямбда-выражения, а не выводит тип. Но да, я также хотел бы знать, почему вы не можете просто вывести правильный тип в этом случае. – sstan 5 July 2015 в 06:57

Вывод по типу lambda не может зависеть от тела лямбда.

Компилятор сталкивается с трудной задачей, пытаясь понять неявные лямбда-выражения

    foo( value -> GIBBERISH )

Тип value должен быть выведен первым до того, как GIBBERISH может быть скомпилирован, поскольку в целом интерпретация GIBBERISH зависит от определения value.

(В вашем специальном случае GIBBERISH оказывается простой константой, независимой от value.)

Javac должен сначала вывести Value<T> для параметра value; в контексте нет ограничений, поэтому T=Object. Затем тело лямбда true скомпилировано и распознается как логическое, совместимое с T.

После того, как вы внесли изменение в функциональный интерфейс, тип параметра лямбда не требует вывода; T остается неосновным. Затем тело лямбда скомпилировано, и тип возврата выглядит как Boolean, который устанавливается как нижняя граница для T.


Еще один пример, демонстрирующий проблему

<T> void foo(T v, Function<T,T> f) { ... }

foo("", v->42);  // Error. why can't javac infer T=Object ?

T, считается String; тело лямбды не принимало участия в выводе.

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


Существуют и другие примеры, когда тело лямбда, как представляется, обеспечивает однозначные ограничения, однако компилятор не может использовать эту информацию , В Java сначала должны быть определены типы параметров лямбда, прежде чем можно будет рассмотреть тело. Это преднамеренное решение. Напротив, C # хочет попробовать разные типы параметров и посмотреть, что делает компиляцию кода. Java считает это слишком рискованным.

В любом случае, когда неявная лямбда не выполняется, что происходит довольно часто, укажите явные типы для лямбда-параметров; в вашем случае (Value<Boolean> value)->true

5
ответ дан ZhongYu 16 August 2018 в 06:43
поделиться
Другие вопросы по тегам:

Похожие вопросы: