是否应在Java 1.8下编译以下代码

时间:2015-04-17 13:21:02

标签: java eclipse java-8 language-lawyer jls

给出以下课程:

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中它产生以下编译错误:

  

[错误]无法执行目标org.apache.maven.plugins:maven-compiler-plugin:3.3:在项目java-compiler-test上编译(default-compile):编译失败   [错误] C:\ Users \ abrieg \ workingcopy \ java-compiler-test \ src \ main \ java \ FooTest.java:[25] FooTest.Service类型中的方法服务(FooTest.Base)不适用于参数(FooTest.Base)

当eclipse编译器的源和目标级别设置为1.7或者使用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?

2 个答案:

答案 0 :(得分: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),由{{1}覆盖(实现)使用方法AbstractService<X>接受相同的参数类型。


由于您的网站似乎存在一些混淆,我想强调这与您的void service(X)声明无关。如上所述,此行为是由于原始类型用法,这意味着您使用if(… instanceof Derived)而没有实际类型参数,并且基本上关闭了泛型类型检查。如果你写了

,这甚至会起作用
AbstractService

如果您将变量的声明更改为

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

它将不再是原始类型,并且将进行类型检查,private AbstractService<Derived> service; 将生成编译器错误,无论您是否将其与service.service(base)括起来或不

原始类型仅与pre-Generics代码兼容,您应该避免使用它们,而不要忽略原始类型使用引发的警告。

答案 1 :(得分:0)

这当然是关于类型安全的

正如其他人所说,你需要将基础转换为Derived以使其满足服务要求(T值),因为编译器知道AbstractService.service的参数必须扩展Derived,所以如果它不是Derived,它就不适合

不幸的是,它摆脱了编译器错误,但它无法解决问题。这就是你得到类型安全警告的原因。

由于AbstractService的定义,service实际上是AbstractService<? extends Derived>,如果你修复了这个遗漏,你就会收到错误。

这是因为AbstractService<? extends Derived>未扩展AbstractService<Derived>并且基础衍生到Derived修复了AbstractService<Derived>的问题。

查看下面的课程以查看此示例。 ConcreteService不延伸AbstractService<Derived>它延伸AbstractService<RealDerived>。您不能将任何Derived传递给ConcreteService.service(RealDerived base),例如,您不能传递FalseDerived对象,因为它不满足参数类型。

事实是,在某些地方必须实际定义T是什么,以便我们可以解决类型安全问题。

通常需要一些技巧,比如将实际类型的参数与服务的实际类型联系在一起的类,以便可以以类型安全的方式完成转换。

如下

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

    }
}