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

2022-09-04 04:45:20

给定以下类:

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

[错误]未能在 project java-compiler-test 上执行 goal org.apache.maven.plugins:maven-compiler-plugin:3.3:compile (default-compile) on project java-compiler-test: 编译失败 [ERROR] 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在类型推断方面是否更具体,以至于java 1.8的eclipse编译器实际上不允许使用此代码,或者如果这是eclipse编译器中的回归。

根据编译器错误的文本,我倾向于说这是一种回归,但我不确定。

我已经确定了已经向jdt报告的以下两个错误,但我认为它们并不完全适用:
https://bugs.eclipse.org/bugs/show_bug.cgi?id=432603 https://bugs.eclipse.org/bugs/show_bug.cgi?id=430987

如果这是一种回归,这是否已经向jdt报告过?


答案 1

据我所知,这段代码应该编译,但当然不是没有未经检查的警告。

您已经声明了原始类型的变量,该变量是原始类型的子类型,其方法为 擦除 。serviceAbstractServiceServicevoid service(Base)void service(T)

因此,调用可能会调用 中声明的方法,当然,带有未经检查的警告,因为该方法是泛型的,并且没有发生类型参数的验证。service.service(base)void service(Base)ServiceT

这可能违反直觉,因为类型使用其擦除为的方法重写该方法,但此方法只能在泛型上下文中重写其他方法,而不能在原始类型继承关系中重写。AbstractServicevoid service(Derived)

或者,换句话说,类型不能重写方法,因为它对参数类型的限制性高于重写的超类型方法。

这也适用于泛型类型继承,但适用于不同的结果。如果您的变量具有 类型 ,则由于类型参数的约束,必须可赋值 。此类型是其子类型,其具有一个方法(as := ),该方法通过接受相同参数类型的方法覆盖(实现)。AbstractService<X>XDerivedAbstractService<X>Service<X>void service(X)TXAbstractService<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)if(base instanceof Derived) { … }

原始类型的存在只是为了与泛型之前的代码兼容,您应该避免使用它们,并且不要忽略原始类型用法引发的警告。


答案 2

这当然是关于类型安全

正如其他人所说,您需要将base转换为 Derived 才能使其满足 service(T 值) 的要求,因为编译器知道 AbstractService.service 的参数必须扩展 Derived,因此如果它不是 Derived,则无法拟合。

不幸的是,这摆脱了编译器错误,但它不能解决问题。这就是为什么你得到类型安全警告。

由于 的定义,实际上是,如果您修复此遗漏,则只会得到错误。AbstractServiceserviceAbstractService<? extends Derived>

这是因为 不 扩展 并且将 base 转换为 Derived 修复了 .AbstractService<? extends Derived>AbstractService<Derived>AbstractService<Derived>

查看下面的类以查看此示例。 不扩展 它扩展 。例如,您不能只将任何对象传递到 中,因为该对象不满足参数类型。ConcreteServiceAbstractService<Derived>AbstractService<RealDerived>DerivedConcreteService.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());
        }

    }
}

推荐