11

I have the following abstract generic data holder in my project (simplified):

public abstract static class Value<E> {

    E value;

    public void setValue(E value) {
        this.value = value;
    }

    public E getValue() {
        return this.value;
    }

    public String toString() {
        return "[" + value + "]";
    }
}

Along with an InputCollection which contains a list of Objects:

public static class InputCollection {

    private ArrayList<Object> values;

    public InputCollection() {
        this.values = new ArrayList<>();
    }

    public void addValue(Value<?> value) {
        System.out.println("addding " + value + " to collection");
        this.values.add(value);
    }

    public <D> D getValue(Value<D> value, D defaultValue) {
        int index = this.values.indexOf(value);
        if (index == -1) 
            return defaultValue;

        Object val = this.values.get(index);
        if (val == null) {
            return defaultValue;
        }

        return ((Value<D>)val).getValue();
    }
}

The idea behind this is to be able to define a set of final variables which implements this abstract Value<E> in a so-called 'state', like so:

public static final class Input<E> extends Value<E> {
    public static final Input<String> STRING_ONE = new Input<String>();
    public static final Input<Integer> INTEGER_ONE = new Input<Integer>();
}

Then, adding these variables to an instance of InputCollection, which in turn is shared by many 'states' or 'processes'. The value of an Input<E> can then be changed by a different state, and then be retrieved when needed by the original state. A kind of shared memory model.

This concept has been working fine for years (yea, this is legacy), but we recently started moving over to Java 8, and this created compilation errors, even though the implementation worked on Java 7.

Add the following main to the above code samples:

public static void main (String [] args) {

    InputCollection collection = new InputCollection();
    //Add input to collection
    collection.addValue(Input.STRING_ONE);
    collection.addValue(Input.INTEGER_ONE);

    //At some later stage the values are set
    Input.INTEGER_ONE.setValue(1);
    Input.STRING_ONE.setValue("one");

    //Original values are then accessed later
    long longValue = collection.getValue(Input.INTEGER_ONE, -1);

    if (longValue == -1) {
        System.out.println("Error: input not set");
    } else { 
        System.out.println("Input is: " + longValue);
    }
}

If the Compiler Compliance level in eclipse is set to 1.7, there is no compilation issues, and the output will correctly be:

addding [null] to collection
addding [null] to collection
Input is: 1

but if it is set to 1.8 compilation error Type mismatch: cannot convert from Integer to long on the line

long longValue = collection.getValue(Input.INTEGER_ONE, -1);

But if I access the value doing this:

long longVal = Input.INTEGER_ONE.getValue();

there are no compilation issues, which is confusing.

It can be solved with a cast, but this is used all over the project and would require quite a bit of mandatory testing to change every occurrence.

What changed in Java 8 that requires the cast? Did compilation get stricter somehow? And why does the compiler not moan if the value is access directly and not through the collection?

I read How do I convert from int to Long in Java? and Converting Integer to Long , but didn't really get satisfying answers to my question.

Community
  • 1
  • 1
Ian2thedv
  • 2,691
  • 2
  • 26
  • 47
  • 1
    Can you try compiling this with `javac`? I have also had this issue and I believe it's an eclipse bug. If I added the cast in my code, it emitted an "Unnecessary cast" warning... :) – Petr Janeček Nov 24 '14 at 13:34
  • I am unable to replicate on my machine. It works perfectly alright in mine with Java8. – Jatin Nov 24 '14 at 13:40
  • 2
    This is in fact a bug in the Eclipse internal compiler. Note: Eclipse does not use the `javac` command from an installed JDK, but uses an internal compiler. This compiler reports the error. Compiling it with the JDK tools works just fine. – Seelenvirtuose Nov 24 '14 at 13:41
  • I see now compiling with `javac` reports no error. Must be eclipse then. – Ian2thedv Nov 24 '14 at 13:53
  • 1
    Seems like the same issue as in http://stackoverflow.com/questions/25958687/type-inference-compiler-error-in-eclipse-with-java8-but-not-with-java7 (no answer there either, but a link to https://bugs.eclipse.org/bugs/show_bug.cgi?id=440019 ... Possibly also related: http://stackoverflow.com/questions/5385743/java-casting-is-the-compiler-wrong-or-is-the-language-spec-wrong-or-am-i-wron ) – Marco13 Nov 24 '14 at 14:21

2 Answers2

8

According to the JLS for Java 8 this should not happen:

5.1.2. Widening Primitive Conversion

19 specific conversions on primitive types are called the widening primitive conversions:

[..]

  • int to long, float, or double

[..]

5.1.8. Unboxing Conversion

[..]

  • From type Integer to type int

What should happen is an unboxing from Integer to int, and then a widening conversion to long. This is actually happening as expected in Oracle JDK (1.8.0.25).

I believe you came across a compiler bug into your JDK. You should probably try an updated version or file a bug with the maintainers.

Vlad
  • 10,602
  • 2
  • 36
  • 38
6

A known Eclipse bug: https://bugs.eclipse.org/bugs/show_bug.cgi?id=440019

Fixed in Eclipse 4.5 M3.

Petr Janeček
  • 37,768
  • 12
  • 121
  • 145
  • Wish i had a way to get the fix in STS 3.6.. is there ? – jayP Oct 15 '15 at 19:39
  • I'm not aware of it. I simply downloaded the new version and it's fine. But yes, I can imagine a setup where you don't want to upgrade (or can't). Try to contact the sts people somewhere. – Petr Janeček Oct 16 '15 at 07:05