Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Any reason why Java shouldn't allow generic type declarations on a variable declaration?

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)

like image 818
Archie Avatar asked Apr 08 '13 19:04

Archie


People also ask

How do I restrict a generic type in Java?

Whenever you want to restrict the type parameter to subtypes of a particular class you can use the bounded type parameter. If you just specify a type (class) as bounded parameter, only sub types of that particular class are accepted by the current generic class.

Why is the raw type allowed in Java?

When generics were introduced into Java, several classes were updated to use generics. Using these class as a "raw type" (without specifying a type argument) allowed legacy code to still compile. "Raw types" are used for backwards compatibility.

What can not be a generic type?

Almost all reference types can be generic. This includes classes, interfaces, nested (static) classes, nested interfaces, inner (non-static) classes, and local classes. The following types cannot be generic: Anonymous inner classes .


2 Answers

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.

like image 77
newacct Avatar answered Oct 07 '22 12:10

newacct


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.

like image 35
rgettman Avatar answered Oct 07 '22 13:10

rgettman