2015-04-17 3 views
14

дал следующий класс:Если следующий код компилируется в 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> 

в мавена 3.4 производит следующие компиляции ошибка:

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.3:compile (default-compile) on project java-compiler-test: Compilation failure [ERROR] C:\Users\abrieg\workingcopy\java-compiler-test\src\main\java\FooTest.java:[25] The method service(FooTest.Base) in the type FooTest.Service is not applicable for the arguments (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

Это нормально компилируется с JAVAC - проблема, вероятно, затмит конкретные ... – assylias

+0

@assylias ну, вот в чем вопрос. JLS - это ссылочный, а javac-скрипт javac или eclipse jdt реализует его правильно. – SpaceTrucker

+1

Но почему? сервис ожидает, что T расширяет Derived, но получает базовый объект (хотя он проверен на фактический экземпляр Derived и может получить кастинг. –

ответ

2

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

Вы объявили переменную service из сырого типаAbstractService который является подтипом сырого типаService, который имеет метод void service(Base) который является стирание void service(T).

Таким образом, вызов service.service(base) может вызвать этот метод void service(Base) объявленных в Service, конечно же, с непроверенной предупреждения как метод является общим и никакой проверки параметра типа T не произошла.

Это может быть нелогичным как тип AbstractService переопределяет этот метод с методом которого стирание void service(Derived), но этот метод может только переопределить другой метод в родовом контекста, а не в отношениях наследования в сырого типа.

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

Это также относится к наследованию общего типа, но к другому результату. Если ваша переменная имела тип AbstractService<X>, то X должен быть назначен Derived из-за ограничения параметра типа. Этот тип AbstractService<X> является подтипом Service<X>, который имеет метод void service(X) (как T: = X), который переопределен (реализован) на AbstractService<X> по методу void service(X), который принимает те же типы аргументов.


Поскольку, как представляется, некоторая путаница на вашем сайте, я хочу подчеркнуть, что это не имеет ничего общего с вашим if(… instanceof Derived) заявление. Как объяснялось выше, это происходит из-за использования исходного типа, что означает, что вы используете AbstractService без фактического аргумента типа и в основном отключите проверку типа Generics. Это будет работать даже если бы вы написали

public void bar(Base base) { 
    service.service(base); // UNCHECKED invocation 
} 

Если вы изменили объявление переменной в

private AbstractService<Derived> service; 

не будет сырье типа больше и проверка типов будет и service.service(base) будет генерировать ошибку компилятора, независимо от того, заключите ли вы ее с if(base instanceof Derived) { … } или нет.

Сырые типы существуют для совместимости только с предварительно дженериков кода, и вы должны избегать их использования, а не игнорировать предупреждения, спровоцированные сырого типа использования.

+0

Было бы неплохо, если бы вы могли ссылаться на соответствующие разделы jls для ваших утверждений. Однако остается вопрос, может ли компилятор/должен/должен/использовать информацию, полученную им от оператора if, содержащего выражение instanceof. – SpaceTrucker

+1

Я просмотрел спецификацию, но не смог найти ни одного, сжатого пункта для ссылки (и ссылка на двадцать мест и высказывание «сделать вывод» было бы немного бессмысленным). Что касается утверждения 'if', ответ прост: он не имеет ни малейшего влияния. Только тип времени компиляции получателя (здесь переменная 'service') имеет значение. Btw. легко проверить, что все компиляторы показывают такое же поведение, если вы удаляете оператор 'if'. – Holger

+0

Но, очевидно, 'javac' использует информацию в выражении if, потому что компилирует код без ошибок. Итак, 'javac' может быть неправильным с 1.5, а компилятор eclipse jdt верен, так как он поддерживает java 1.8? – SpaceTrucker

0

Это, конечно, о типобезопасности

Как уже сказал, что вы должны бросить базу для Derived, чтобы получить его, чтобы удовлетворить требования к службе (T значения), потому что компилятор знает, что аргумент AbstractService.service должен распространяться Производится, и поэтому, если он не является производным, он не подходит.

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

Из-за определения AbstractService, service на самом деле AbstractService<? extends Derived>, и если вы исправите это упущение, вы просто получите ошибку.

Это связано с тем, что AbstractService<? extends Derived> не распространяется на AbstractService<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()); 
     } 

    } 
} 
Смежные вопросы