Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should the following code compile under Java 1.8

given the following class:

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

When building the class with the following 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>

in maven 3.4 it produces the following compile error:

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

When setting source and target level to 1.7 for the eclipse compiler or when using javac as the compiler there is no compile error reported.

The question is wheter JLS 1.8 is more specific about type inference such that this code is really not allowed as supposed by eclipse compiler for java 1.8 or if this is a regression in the eclipse compiler.

Based on the text of the compiler error I tend to say its a regression, but I am not sure.

I have identified the following two bugs already reported to jdt, but I think they do not apply exactly:
https://bugs.eclipse.org/bugs/show_bug.cgi?id=432603 https://bugs.eclipse.org/bugs/show_bug.cgi?id=430987

If this is a regression, has this already been reported to jdt?

like image 455
SpaceTrucker Avatar asked Apr 17 '15 13:04

SpaceTrucker


1 Answers

To my understanding, this code should compile, but of course not without an unchecked warning.

You have declared a variable service of the raw type AbstractService which is a subtype of the raw type Service which has a method void service(Base) which is the erasure of void service(T).

So the invocation service.service(base) may invoke that method void service(Base) declared in Service, of course, with an unchecked warning as the method is generic and no verification of the type parameter T happened.

This might be counter-intuitive as the type AbstractService overrides that method with a method whose erasure is void service(Derived) but this method can only override the other method in a generic context, not in a raw type inheritance relationship.

Or, in other words, a type can’t override a method in a way that it is more restrictive regarding parameter types than the overridden supertype method.

This also applies to the generic type inheritance but to a different outcome. If your variable had the type AbstractService<X>, then X must be assignable to Derived due to the constraint of the type parameter. This type AbstractService<X> is a subtype of Service<X> which has a method void service(X) (as T := X) which is overridden (implemented) by AbstractService<X> with a method void service(X) which accepts the same argument types.


Since there seems to be some confusion on your site, I want to emphasize that this has nothing to do with your if(… instanceof Derived) statement. As explained above, this behavior is due to the raw type usage, which means you are using AbstractService without an actual type argument and basically switching off the Generics type checking. This would even work if you had written

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

If you changed the declaration of the variable to

private AbstractService<Derived> service;

it won’t be a raw type anymore and the type checking will happen and service.service(base) will generate a compiler error, regardless of whether you enclose it with if(base instanceof Derived) { … } or not.

Raw types exist for compatibility with pre-Generics code only and you should avoid using them and not ignore warnings provoked by raw type usage.

like image 93
Holger Avatar answered Oct 31 '22 02:10

Holger