Дженерики Java 8 Метод неприменим для аргументов в Eclipse.

Во время миграции нашей кодовой базы с java 1.7 на 1.8 мы получили сообщение об ошибке «Метод... неприменим для аргументов» в нескольких местах кода, все они следуют одному и тому же шаблону использования дженериков.

В настоящее время мы используем в основном Eclipse Mars (4.5.2) на Windows 7, но можем подтвердить поведение и с Neon (4.6). И Javac, и ecj с уровнем соответствия 1.7 могут скомпилировать наш код без ошибок.

Вот минимальный, полный и проверяемый пример:

public class ComplexInterfaceTest {

  public static class Foo {}

  public interface Bar {
    void print();
  }

  public static class SubFooBar extends Foo implements Bar {
    public void print() {
      System.out.println(this.getClass().getSimpleName());
    }
  }

  public static class FooBar<T extends Foo & Bar> {
    public static <T extends Foo & Bar> FooBar<T> makeFooBar() {
      return new FooBar<>();
    }

    public void create(T value) {
      value.print();
      return;
    }
  }

  public static class Base<T extends Foo> {}

  public static class Subclass extends Base<SubFooBar> {
    public void doSomething(SubFooBar value) {
//      FooBar.<SubFooBar>makeFooBar().create(value);
      FooBar.makeFooBar().create(value);
    }
  }

  public static void main(String[] args) {
    new Subclass().doSomething(new SubFooBar());
  }

}

Теперь переключение закомментированных строк в методе doSomething приводит к компиляции кода, поэтому у нас есть обходной путь. Тем не менее сообщение об ошибке кажется неправильным, поскольку класс SubFooBar расширяет Foo и реализует Bar, поэтому он выполняет контракт <T extends Foo & Bar>, который требуется в <T extends Foo & Bar> FooBar<T> makeFooBar(), поэтому на самом деле T IMO должен быть привязан к SubFooBar.

Я искал похожие вопросы и нашел следующие: Различия в выводе типов JDK8 javac/ Eclipse Luna? Тип Ошибка компилятора вывода в Eclipse с Java8, но не с Java7

Это заставляет меня думать, что это может быть ошибка ecj. В этом курсе я тоже смотрел Eclipse Bugzilla но ничего похожего не нашел, видел вот эти:

  • 430686 проверено исправлено - у меня нет
  • 440019 имеет отношение к скалярному типу - мой нет
  • 492838, 448793 имеют отношение к подстановочным знакам – мой нет

Теперь Eclipse Bugzilla дискуссии полны подробностей о внутренней работе ecj, за которыми я не всегда могу уследить. Что я понимаю, так это общее мнение, что компилятор Eclipse должен строго следовать JLS, а не javac (в тех случаях, когда это неправильно), поэтому это не обязательно должна быть ошибка в ecj. Если бы это не было ошибкой ecj, то компиляция кода должна была бы быть ошибкой javac.

Что меня интересует, так это - для тех, кто может проанализировать процесс вывода типа моего фрагмента кода - должен ли код скомпилироваться или я допустил ошибку при кодировании?

ИЗМЕНИТЬ

Как я и обещал опубликовать результаты моего отчета в Eclipse Bugzilla: дефект имеет идентификатор № 497905 (Стефан Херрманн разместил ссылку в своем комментарии под принятым ответом) и в настоящее время предназначен для версии 4.7.


person Tomasz Stanczak    schedule 14.07.2016    source источник
comment
Я только что опубликовал отчет об ошибке на Eclipse Bugzilla, здесь будут размещены их ответы.   -  person Tomasz Stanczak    schedule 14.07.2016


Ответы (1)


В методе

public void doSomething(SubFooBar value) {
  FooBar.makeFooBar().create(value);
}

параметр типа T метода makeFooBar() никогда не будет выводиться как SubFooBar. Тот факт, что впоследствии вы собираетесь передать экземпляр SubFooBar методу create, не влияет на тип предшествующего выражения вызова FooBar.makeFooBar().

Это не меняется с целевой типизацией Java 8, поскольку эта новая функция не работает с выражением получателя вызова связанного метода.

Таким образом, во всех версиях тип, выводимый для T вызова makeFooBar(), будет типом пересечения Foo & Bar, поэтому тип результата будет FooBar<Foo&Bar>. Это также то, что делает вывод Eclipse, даже если всплывающая подсказка в редакторе может отображать что-то еще.

Это означает, что вы можете передать экземпляр SubFooBar методу create, так как FooBar<Foo&Bar>.create(…) ожидает экземпляр Foo&Bar, а SubFooBar расширяет Foo и реализация Bar совместима.

Можно продемонстрировать, что Eclipse выводит тот же тип, что и все другие компиляторы, путем вставки соответствующего приведения типа.

public void doSomething(SubFooBar value) {
  FooBar.makeFooBar().create((Foo&Bar)value);
}

устраняет ошибку компилятора. Таким образом, проблема здесь не в выводе типа, а в том, что эта версия Eclipse считает, что SubFooBar нельзя присвоить Foo & Bar.

person Holger    schedule 14.07.2016
comment
да, это также соответствует моему анализу (bugs.eclipse.org/bugs /show_bug.cgi?id=497905#c1) - person Stephan Herrmann; 14.07.2016
comment
Таким образом, поскольку T должен быть Foo&Bar, а SubFooBar — Foo&Bar, код должен быть скомпилирован. Полезно знать, спасибо! - person Tomasz Stanczak; 14.07.2016
comment
Я дал ему некоторое время, чтобы впитаться — я думаю, что не так много, что можно добавить: хотя я пропустил правильную причину, дело в том, что мой код кажется правильным, поэтому проблема будет нацелена на Eclipse версию 4.7 . - person Tomasz Stanczak; 15.07.2016