简体   繁体   中英

Type variables with limited scope in Java

I'm wondering if it's possible to introduce type variables within the scope of a method in Java. That is, limiting their scope within the method body.

However, rather than trying to describe the problem in the abstract, let my illustrate with my concrete problem. I have a couple of classes that look a bit like this:

public class Settings {
    public static abstract class Setting<T> {
        public T value;
        public abstract T fallbackvalue();
    }

    private final List<Setting<?>> settings;
}

I now want to write a function, in Settings , for setting the value of all the Settings to their fallback values as provided by the abstract method. My first thought would be to do it like this:

public void reset() {
    for(Setting<?> setting : settings)
        setting.value = setting.fallbackvalue();
}

However, on second thought it is rather obvious why this does not work; the capture of <?> for setting.value is not the same capture as for setting.fallbackvalue() . Ergo, I need some way to unify the captures. It is possible to solve it like this:

private static <T> void reset1(Setting<T> s) {
    s.value = setting.fallbackvalue();
}

public void reset() {
    for(Setting<?> setting : settings)
        reset1(setting);
}

The explicit type variable <T> for reset1 unifies the captures conveniently, but it's obviously the ugliest thing in the world to introduce this function, pollute the namespace, clutter the screen and make the code less readable merely to satisfy the type system.

Is there no way I can do this within the body of reset ? What I'd like to do is simply something like this:

public void reset() {
    for(Setting<?> setting : settings) {
        <T> {
            Setting<T> foo = setting;
            foo.value = foo.fallbackvalue();
        }
    }
}

It's not the prettiest thing in the world, but at least to my eyes it is far less strainful than the variant above. Alas, it's not possible; but what is possible, then?

There's no way to do what you're asking without changing other aspects of your code. However (to solve your particular issue), you can write a reset method in the inner Setting class:

public void reset() {
    value = fallbackvalue();
}

Then your loop (in the reset method of the Settings class) would simply be:

for (Setting<?> setting : settings)
    setting.reset();

No...

Although wildcard capture does introduce new type variables, they are only available to the compiler; there's no way for programmer to access them directly.

Currently, only class/method can introduce type variables. Therefore the only way to convert an expression type with wildcards to a type without wildcard is to pass the expression through a method (or a constructor with diamond inference new Foo<>(setting) which is essentially the same mechanism)

Your reset1 thing is the generally accepted way to do it. It's called a "capture helper", and is an often-cited pattern in generics. It usually comes up in a situation like the following:

public void swap(List<?> list, int i, int j) { // swap elements i and j of the list
    // how to write?
}

In this case, you need to both get something out of the parameterized type, and put something back in of that type. A wildcard just won't let you do that. This is just like your case, because you are also getting something out and putting something in. We know that the type must be the same, but wildcards are too weak to enforce that. Only an explicit type variable can allow us to do it:

public <T> void swap(List<T> list, int i, int j) {
    T tmp = list.get(i);
    list.set(i, list.get(j));
    list.set(j, tmp);
}

However, we don't want this extraneous <T> that is only used in only one place in the argument list. To the outside world, swap(List<?> list, int i, int j) should work perfectly fine. That we need to use this <T> type parameter is an implementation detail that nobody needs to know. So to hide it, we wrap the generic function with the function that takes the wildcard:

private <T> void swap_private(List<T> list, int i, int j) { ... }
public void swap(List<?> list, int i, int j) {
    swap_private(list, i, j);
}

It does seem a waste, but that's how it is.

Given the analogous situation between your situation and this one, and the fact that the capture helper is the canonical solution in this situation, I can tell you with confidence that, no, there is no better way to do it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM