Если следующий код компилируется под Java 1.8
задан следующий класс:
public class FooTest {
public static class Base {
}
public static class Derived extends Base {
}
public interface Service<T extends Base> {
void service(T value);
}
public abstract class AbstractService<T extends Derived> implements Service<T> {
public void service(T value) {
}
}
private AbstractService service;
public void bar(Base base) {
if(base instanceof Derived) {
service.service(base); // compile error at this line
}
}
}
При создании класса со следующим pom.xml
:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mgm-tp</groupId>
<artifactId>java-compiler-test</artifactId>
<version>0.0.1-SNAPSHOT</version>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<compilerId>eclipse</compilerId>
</configuration>
<dependencies>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-compiler-eclipse</artifactId>
<version>2.5</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
в maven 3.4 он производит следующую ошибку компиляции:
[ERROR] Не удалось выполнить цель org.apache.maven.plugins: maven-compiler-plugin: 3.3: компиляция (компиляция по умолчанию) в проекте java-compiler-test: сбой компиляции [ERROR] C:\Users\abrieg\workcopy\java-compiler-test\src\main\java\FooTest.java: [25] Служба метода (FooTest.Base) в типе FooTest.Service неприменима для аргументы (FooTest.Base)
При настройке источника и целевого уровня на 1.7 для компилятора eclipse или при использовании javac
в качестве компилятора не сообщается об ошибке компиляции.
Вопрос заключается в том, что JLS 1.8 более специфичен в отношении вывода типа, так что этот код действительно не допускается, как предполагалось компилятором eclipse для java 1.8, или если это регрессия в компиляторе eclipse.
Основываясь на тексте ошибки компилятора, я склонен сказать, что это регрессия, но я не уверен.
Я определил следующие две ошибки, уже сообщаемые jdt, но я думаю, что они не применяются точно:
https://bugs.eclipse.org/bugs/show_bug.cgi?id=432603
https://bugs.eclipse.org/bugs/show_bug.cgi?id=430987
Если это регрессия, это уже сообщалось в jdt?
Ответы
Ответ 1
Насколько я понимаю, этот код должен компилироваться, но, конечно, не без непроверенного предупреждения.
Вы указали переменную service
необработанного типа AbstractService
, которая является подтипом необработанного типа service
, который имеет метод void service(Base)
, который является стиранием void service(T)
.
Таким образом, вызов service.service(base)
может вызывать этот метод void service(Base)
, объявленный в service
, конечно, с непроверенным предупреждением, поскольку метод является общим и не проверяет параметр типа T
.
Это может быть противоречивым, поскольку тип AbstractService
переопределяет этот метод с методом, стирание которого void service(Derived)
, но этот метод может переопределять другой метод в общем контексте, а не в отношении отношения типа исходного типа.
Или, другими словами, тип can not переопределяет метод таким образом, что он более ограничивает типы параметров, чем переопределенный метод супертипа.
Это также относится к наследованию общего типа, но к другому результату. Если ваша переменная имела тип AbstractService<X>
, то X
должен быть назначен Derived
из-за ограничения параметра типа. Этот тип AbstractService<X>
является подтипом Service<X>
, который имеет метод void service(X)
(as T
: = X
), который переопределяется (реализуется) AbstractService<X>
с помощью метода void service(X)
, который принимает тот же типы аргументов.
Так как на вашем сайте существует некоторая путаница, я хочу подчеркнуть, что это не имеет никакого отношения к вашему оператору if(… instanceof Derived)
. Как объяснялось выше, это поведение связано с использованием типа raw, что означает, что вы используете AbstractService
без фактического аргумента типа и в основном отключите проверку типа Generics. Это будет даже работать, если вы написали
public void bar(Base base) {
service.service(base); // UNCHECKED invocation
}
Если вы изменили объявление переменной на
private AbstractService<Derived> service;
он больше не будет сырым типом, и проверка типа произойдет, а service.service(base)
создаст ошибку компилятора, независимо от того, заключите ли вы ее с помощью if(base instanceof Derived) { … }
или нет.
Существуют только типы сырья для совместимости только с кодом Pre-Generics, и вы должны избегать их использования и не игнорировать предупреждения, вызванные использованием типа raw.
Ответ 2
Это, конечно, о типах
Как говорили другие, вам нужно отбросить базу до Derived, чтобы она удовлетворяла требованиям к сервису (значение T), поскольку компилятор знает, что этот аргумент для AbstractService.service должен расширять Derived, и поэтому, если он не является производным, он не может соответствовать.
К сожалению, это избавляет от ошибки компилятора, но это не устраняет проблему. поэтому вы получаете предупреждение о безопасности типа.
Из-за определения AbstractService
, service
на самом деле AbstractService<? extends Derived>
, и если вы устраните это упущение, вы просто получите сообщение об ошибке.
Это связано с тем, что AbstractService<? extends Derived>
не расширяет AbstractService<Derived>
, а база casting до Derived исправляет проблему для AbstractService<Derived>
.
Посмотрите на классы ниже, чтобы увидеть пример этого. ConcreteService
не расширяется AbstractService<Derived>
, он расширяет AbstractService<RealDerived>
. Вы не можете передать только Derived
в ConcreteService.service(RealDerived base)
, например, вы не можете передать объект FalseDerived
, потому что это не удовлетворяет типу аргумента.
Истина заключается в том, что где-то что-то должно действительно определять, что такое T, чтобы мы могли исправить проблему безопасности типа.
Выполнение этого в общем случае требует некоторого обмана, такого как класс, который связывает фактический тип аргумента с фактическим типом службы, так что кастинг может выполняться с помощью вида typeafe.
Как следует
public class FooTest {
public static class Base {
}
public static class Derived extends Base {
}
public interface Service<T extends Base> {
void service(T value);
}
public abstract static class AbstractService<T extends Derived> implements Service<T> {
public void service(T value) {
}
}
// The following class defines an argument B that and ties together the type of the service with the type of the referenced stored class which enables casting using the class object
public abstract static class Barrer<B extends Derived>{
private AbstractService<B> service;
private Class<B> handledClass;
protected Barrer(Class<B> handledClass, AbstractService<B> service){
this.handledClass = handledClass;
this.service = service;
}
public void bar(Base base) {
if(handledClass.isAssignableFrom(base.getClass())) {
service.service(handledClass.cast(base)); // compile error at this line
}
}
}
// the following classes provide concrete implementations and the concrete class to perform the casting.
public static class RealDerived extends Derived{}
public static class FalseDerived extends Derived{}
public static class ConcreteService extends AbstractService<RealDerived>{
}
public static class ConcreteBarrer extends Barrer<RealDerived> {
protected ConcreteBarrer() {
super(RealDerived.class,new ConcreteService());
}
}
}