Question

Suppose we have a class like this:

public class xx {

    public interface Foo<T> {
        T getValue();
        void setValue(T value);
    }

    public void resetFoos(Iterable<Foo<?>> foos) {
        for (Foo<?> foo : foos)
            foo.setValue(foo.getValue());
    }
}

It will fail to compile even though intuitively it seems like it "should":

xx.java:10: setValue(capture#496 of ?) in xx.Foo<capture#496 of ?> cannot be applied to (java.lang.Object)
        foo.setValue(foo.getValue());

The reason is that foo does not have a bound generic type, so the compiler doesn't "know" that the output of foo.getValue() is compatible with the input of foo.setValue().

So to fix this you have to create a new method just for the purpose of binding the generic type parameter in the for() loop:

public class xx {

    public interface Foo<T> {
        T getValue();
        void setValue(T value);
    }

    public void resetFoos(Iterable<Foo<?>> foos) {
        for (Foo<?> foo : foos)
            this.resetFoo(foo);
    }
    
    // stupid extra method here just to bind <T>
    private <T> void resetFoo(Foo<T> foo) {
        foo.setValue(foo.getValue());
    }
}

This has always annoyed me. Plus, it seems like there can be a simple solution.

My question: Is there any "good" reason why the java language shouldn't be extended to allow generic type declarations on variable declarations? For example:

public class xx {

    public interface Foo<T> {
        T getValue();
        void setValue(T value);
    }

    public void resetFoos(Iterable<Foo<?>> foos) {
        for (Foo<?> foo : foos) {
            final <T> Foo<T> typedFoo = foo;
            foo.setValue(foo.getValue());
        }
    }
}

or, more concisely in this case of a for() loop:

public class xx {

    public interface Foo<T> {
        T getValue();
        void setValue(T value);
    }

    public void resetFoos(Iterable<Foo<?>> foos) {
        for (<T> Foo<?> foo : foos)
            foo.setValue(foo.getValue());
    }
}

I'm wondering if some compiler wizard can explain why this would either be too hard, or can (and should) be done.

EDIT:

In response to this suggested solution:

public <T> void resetFoos(Iterable<Foo<T>> foos) {
    for (Foo<T> foo : foos) {
        foo.setValue(foo.getValue());
    }
}

This method signature doesn't allow Foos with various generic types to be reset together. In other words, trying to pass in an Iterable<Foo<?>> causes a compile error.

This example demonstrates the problem:

public static class FooImpl<T> implements Foo<T> {
    
    private T value;
    
    public FooImpl(T value) { this.value = value; }
    
    @Override public T getValue() { return value; }
    
    @Override public void setValue(T value) { this.value = value; }
}

public static <T> void resetFoos(Iterable<Foo<T>> foos) {
    for (Foo<T> foo : foos) {
        foo.setValue(foo.getValue());
    }
}

public static void main(String[] args) {
    
    final Foo<Object> objFoo = new FooImpl<>(new Object());
    final Foo<Integer> numFoo = new FooImpl<>(new Integer(42));
    final Foo<String> strFoo = new FooImpl<>("asdf");
    
    List<Foo<?>> foos = new ArrayList<>(3);
    foos.add(objFoo);
    foos.add(numFoo);
    foos.add(strFoo);
    
    resetFoos(foos); // compile error
    
    System.out.println("done");
}

The compile error reads:

method resetFoos cannot be applied to given types;

required: Iterable<Foo<T>>

found: List<Foo<?>>

reason: no instance(s) of type variable(s) T exist so that argument type List<Foo<?>> conforms to formal parameter type Iterable<Foo<T>> where T is a type-variable: T extends Object declared in method <T>resetFoos(Iterable<Foo<T>>)

(using sun-jdk-1.7.0_10 via ideone.com)

Was it helpful?

Solution

Basically, type variables can only currently have two scopes in Java: 1) class scope, or 2) method scope. You're asking, why not allow another scope -- scope of a local block of code (in this case, the inside of a for loop.

Yes, in some cases this would be helpful. And it would be not too hard to add it to the language. However, these are pretty rare cases, and is more likely to confuse people than help. Also, a relatively simple and effective workaround exists, as you have already discovered -- move that local block scope to a private helper function, which can then use a method-scope type variable:

public void resetFoos(Iterable<Foo<?>> foos) {
    for (Foo<?> foo : foos) {
        resetFoo(foo);
    }
}

private <T> void resetFoo(Foo<T> foo) {
    foo.setValue(foo.getValue());
}

Yes, this might make the code less efficient by making extra method calls, but that is a minor concern.

OTHER TIPS

You can make the resetFoos method itself generic:

public <T> void resetFoos(Iterable<Foo<T>> foos) {
    for ( Foo<T> foo : foos)
        foo.setValue(foo.getValue());
}

That way the compiler knows that the T from foo.getValue() is the same T for foo.setValue().

That's just the workaround. I do not know why the compiler doesn't let you declare generics at a variable level such as final <T> Foo<T> typedFoo = foo;; I just know that you can't declare it at the variable level. However, here, the method level is sufficient.

It probably could be done, possibly with some edge cases. But really, the reason it's not done is simply that the JLS people stopped short of doing it.

A type system isn't going to be able to infer everything about your program. It'll do a bunch, but there'll always be some place where a compiler/type checker won't know something that you the human can prove. So, the people who design compilers and type checkers have to decide how far to go in terms of inferring and being clever -- and no matter where they stop, someone will always be able to pose a question like this one: "why didn't they go this extra step?"

My guess -- and it's only a guess -- is that this inference didn't make it because of some mix of (a) it's easy to work around (as rgettman shows), (b) it complicates the language, (b.1) it introduces tricky edge cases, some of which may even be incompatible with other features of the language, (c) the designers of the JLS didn't feel they had time to work on it.

Every change to java language will be very hard.

Your example is not really about the need of a type variable. The compiler already creates a type variable through wildcard capture, it's just that the type variable isn't available to the programmer. There might be several remedies

  1. let the programmer access the type variable. This isn't an easy task. We need to invent some bizzare syntax and semantics. The weirdness and complexity may not justify the benefit.

  2. shared capture conversion. Right now, every expression goes through capture conversion separately. The spec does not recognize that foo is foo so the two expressions should share the same capture conversion. It is doable on some simple cases, for example, an effectively final local variable should be capture-converted only once, not on every access.

  3. inferred variable type - var foo2 = foo; The type of foo2is inferred from the right hand side, which actually undergoes capture conversion first, therefore foo2's type will be Foo<x> for some x, with a new type variable x (still undenotable). Or use var directly on foo - for(var foo: foos). Inferred variable type is a mature/tested technique, and its benefit is far broader than solving this problem. So I'll vote for this one. It might be coming in java 9, if we can live that long.

The problem is that in the statement in question:

foo.setValue(foo.getValue());

the two occurrences of ? can (as far as the compiler knows) be bound to different types. It may seem silly that the compiler can't figure out that one ? (that is returned by getValue()) isn't the same as the ? that is expected in setValue. But in other situations, the result is not so clear-cut. The language requires that type parameters be named the same if they are supposed to be the same.

A fine workaround is provided in rgettman's answer.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top