2015-04-17 15 views
14

cho lớp sau:đoạn mã sau nên biên dịch theo 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 
     } 
    } 
} 

Khi xây dựng các lớp học với các pom.xml sau:

<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> 

trong maven 3.4 nó tạo ra lỗi biên dịch sau:

[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)

Khi đặt nguồn và cấp mục tiêu thành 1,7 cho trình biên dịch eclipse hoặc khi sử dụng javac làm trình biên dịch không có lỗi biên dịch được báo cáo.

Câu hỏi là wheter JLS 1.8 cụ thể hơn về kiểu suy luận sao cho mã này thực sự không được cho phép như trình biên dịch eclipse cho java 1.8 hoặc nếu đây là hồi quy trong trình biên dịch eclipse.

Dựa trên văn bản của lỗi trình biên dịch, tôi có xu hướng nói hồi quy của nó, nhưng tôi không chắc chắn.

tôi đã xác định được hai lỗi sau đây đã báo cáo với JDT, nhưng tôi nghĩ rằng họ không áp dụng chính xác:
https://bugs.eclipse.org/bugs/show_bug.cgi?id=432603 https://bugs.eclipse.org/bugs/show_bug.cgi?id=430987

Nếu đây là một hồi quy, đã này đã được báo cáo JDT?

+1

Đó biên dịch tốt với javac - vấn đề này có lẽ eclipse cụ thể ... – assylias

+0

@assylias tốt, đó là câu hỏi. JLS là tài liệu tham khảo và jether javac hoặc trình biên dịch jdt eclipse thực hiện nó đúng. – SpaceTrucker

+1

Nhưng tại sao? dịch vụ mong đợi một T mở rộng có nguồn gốc, nhưng nhận được một đối tượng cơ sở (mặc dù nó được kiểm tra để thực sự là một thể hiện của Derived, và có thể được đúc. –

Trả lời

2

Để hiểu biết của tôi, mã này nên biên dịch, nhưng tất nhiên là không có cảnh báo không được chọn.

Bạn đã tuyên bố một biến service của kiểu thôAbstractService mà là một subtype của kiểu thôService trong đó có một phương pháp void service(Base) đó là xóa các void service(T).

Vì vậy, gọi service.service(base) có thể gọi là phương pháp void service(Base) khai báo trong Service, tất nhiên, với một kiểm soát cảnh báo như phương pháp này là chung chung và không có xác minh của các loại tham số T xảy ra.

Điều này có thể gây phản tác dụng trực quan như các loại AbstractService ghi đè rằng phương pháp với một phương pháp mà tẩy xoá là void service(Derived) nhưng phương pháp này chỉ có thể ghi đè lên các phương pháp khác trong một bối cảnh generic, không phải trong một kiểu thô mối quan hệ thừa kế.

Hoặc nói cách khác, một loại không thể ghi đè phương thức theo cách hạn chế hơn đối với các loại thông số so với phương pháp siêu kiểu được ghi đè.

Điều này cũng áp dụng cho kế thừa kiểu chung nhưng với kết quả khác. Nếu biến của bạn có loại AbstractService<X> thì X phải được gán cho Derived do sự ràng buộc của thông số loại. Loại này AbstractService<X> là loại phụ của Service<X> có phương thức void service(X) (như T: = X) được ghi đè (được triển khai) theo AbstractService<X> với phương thức void service(X) chấp nhận cùng loại đối số.


Vì dường như có một số nhầm lẫn trên trang web của bạn, tôi muốn nhấn mạnh rằng điều này không liên quan gì đến tuyên bố if(… instanceof Derived) của bạn. Như đã giải thích ở trên, hành vi này là do sử dụng loại thô, có nghĩa là bạn đang sử dụng AbstractService mà không có đối số loại thực tế và về cơ bản tắt kiểm tra loại Generics. Điều này thậm chí có thể làm việc nếu bạn đã viết

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

Nếu bạn đã thay đổi lời tuyên bố của biến để

private AbstractService<Derived> service; 

nó sẽ không thể là một liệu loạinữa và việc kiểm tra loại sẽ xảy ra và service.service(base) sẽ tạo ra một lỗi trình biên dịch, bất kể bạn có kèm theo nó với if(base instanceof Derived) { … } hay không.

loại

Raw tồn tại để tương thích với pre-Generics chỉ mã và bạn nên tránh sử dụng chúng và không bỏ qua cảnh báo kích động bởi kiểu thô sử dụng.

+0

Nó sẽ là tốt đẹp nếu bạn có thể tham khảo các phần thích hợp của jls cho báo cáo của bạn. Tuy nhiên vẫn có câu hỏi nếu trình biên dịch có thể/nên/phải sử dụng các thông tin mà nó thu được từ câu lệnh if có chứa biểu thức instanceof. – SpaceTrucker

+1

Tôi đã xem qua đặc điểm kỹ thuật nhưng không thể tìm thấy một đoạn văn ngắn, đơn nhất để liên kết tới (và liên kết với hai mươi địa điểm và nói rằng "rút ra kết luận" sẽ là một chút vô nghĩa). Về câu lệnh 'if', câu trả lời rất đơn giản: nó không có ảnh hưởng nhỏ. Chỉ có kiểu biên dịch của người nhận (ở đây biến 'service') quan trọng. Btw. thật dễ dàng để kiểm tra xem tất cả trình biên dịch có hiển thị cùng một hành vi nếu bạn loại bỏ câu lệnh 'if' hay không. – Holger

+0

Nhưng rõ ràng 'javac' không sử dụng thông tin trong câu lệnh if, vì nó biên dịch mã mà không có lỗi. Vì vậy, 'javac' có thể sai kể từ 1.5 và trình biên dịch jdt eclipse là chính xác vì nó hỗ trợ java 1.8? – SpaceTrucker

0

Điều này tất nhiên là về typesafety

Như những người khác đã nói rằng bạn cần phải cast cơ sở để nguồn gốc để có được nó để đáp ứng các yêu cầu về dịch vụ (giá trị T) vì trình biên dịch biết rằng lý lẽ để AbstractService.service phải mở rộng Có nguồn gốc và vì vậy nếu nó không phải là nguồn gốc nó không thể phù hợp.

Thật không may điều này sẽ loại bỏ lỗi trình biên dịch nhưng nó không khắc phục được sự cố. đó là lý do tại sao bạn nhận được cảnh báo an toàn loại.

Do định nghĩa của AbstractService, service thực sự là AbstractService<? extends Derived> và nếu bạn sửa lỗi này, bạn sẽ chỉ nhận được lỗi.

Điều này là do AbstractService<? extends Derived> không mở rộng AbstractService<Derived> và truyền cơ sở cho Đã phát sinh khắc phục sự cố cho AbstractService<Derived>.

Xem các lớp bên dưới để xem ví dụ về điều này. ConcreteService không mở rộng AbstractService<Derived> nó mở rộng AbstractService<RealDerived>. Bạn không thể vượt qua bất kỳ chỉ Derived thành ConcreteService.service(RealDerived base), ví dụ, bạn không thể vượt qua một đối tượng FalseDerived bởi vì điều đó không đáp ứng loại đối số.

Sự thật là một nơi nào đó phải thực sự xác định T là gì để chúng tôi có thể khắc phục vấn đề an toàn loại.

Làm cho nó thường đòi hỏi một số thủ thuật như một lớp liên kết với nhau loại đối số thực tế với loại dịch vụ thực tế để việc đúc có thể được thực hiện một cách an toàn.

Như sau

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()); 
     } 

    } 
} 
Các vấn đề liên quan