Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is this esoteric generics error a compiler bug or a new restriction? (inferred type does not conform to upper bounds)

I updated from Java 8u5 to 8u45 and some previously working code no longer compiles. Problem is, half the time when this happens, it was a deliberate change, so I can't figure out whether it's a bug or not.

(I also tested down to u25 and every version does the same thing as u45.)

But essentially, it has to do with multiple return points from a method. e.g.:

import java.sql.Connection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

public class CompilerIssue
{
    public Set<String> test(int value)
    {
        return perform(connection -> {
            if (value % 2 == 0)
            {
                return Collections.<String>emptySet();
            }
            else
            {
                return new HashSet<>(10);
            }
        });
    }

    <V> V perform(BusinessLogic<V> logic)
    {
        // would usually get a connection
        return null;
    }

    interface BusinessLogic<V>
    {
        V execute(Connection connection) throws Exception;
    }
}

javac gives:

Error:(12, 23) java: incompatible types: inferred type does not conform to upper bound(s)
    inferred: java.util.Set<? extends java.lang.Object>
    upper bound(s): java.util.Set<java.lang.String>,java.lang.Object

IDEA, as is usual with this sort of thing, can't see any problem.

I already know the fix - replace HashSet<> with HashSet<String>. But we used this sort of structure pervasively through our code, so before I spend time changing it all over, I would like to know: is this is a bug, or was the old behaviour the bug?

(If it's a bug, then I have to report a bug to Oracle. If it's a feature, then I have to report a bug to IDEA about it thinking it's OK.)

like image 298
Hakanai Avatar asked Apr 30 '15 00:04

Hakanai


1 Answers

As far as I can see it is a bug and is from Oracle, it is not inferring correctly the type from the target type (the type that the sentence return expects to return) instead, it is somehow comparing the upper bound of both return sentences: (return Collections.<String>emptySet(); and return new HashSet<>(10);).

In the second return statement the upper bound of just new HashSet<>(10) would be Object, but the type could be inferred from the return type that is indeed <String>. Try just as a test commenting out the first return statement and return null instead and compile:

public class CompilerIssue
{
    public Set<String> test(int value)
    {
        return perform(connection -> {
            if (value % 2 == 0)
            {
                return null; // COMMENTED OUT FOR TESTING PURPOSES Collections.<String>emptySet();
            }
            else
            {
                return new HashSet<>(10);
            }
        });
    }

    <V> V perform(BusinessLogic<V> logic)
    {
        // would usually get a connection
        return null;
    }

    interface BusinessLogic<V>
    {
        V execute(Connection connection) throws Exception;
    }
}

this would compile and it would correctly infer the correct value type of HashSet<>(10) should be String.

like image 66
morgano Avatar answered Oct 24 '22 09:10

morgano