1

I have problem with this piece of code. When I want to insert into Map EnumElement consumer, it fails to compile with error error: type argument Enum is not within bounds of type-variable T. Problem is that I can't change *Value classes. Is there way how to make it working?

import java.util.*;
import java.util.function.*;

public class Main {
    public static void main(String[] args) {}
    
    private static final Map<Class<?>, Consumer<Element>> CONFIG_ELEMENTS = new HashMap<>();

    static {
        CONFIG_ELEMENTS.put(BooleanValue.class, element -> new BooleanElement((ConfigElement<Boolean, BooleanValue>) element));
        // CONFIG_ELEMENTS.put(EnumValue.class, element -> new EnumElement<>((ConfigElement<Enum, EnumValue<Enum>>) element)); // doesn't compile
    }
    
    
    private static class ConfigValue<T>{}
    
    private static class BooleanValue extends ConfigValue<Boolean> {}
    
    private static class EnumValue<T extends Enum<T>> extends ConfigValue<T> {}
    
    
    private static class Element{}
    
    private static class ConfigElement<S, T extends ConfigValue<S>> extends Element {}
    
    private static class BooleanElement extends ConfigElement<Boolean, BooleanValue> {
        BooleanElement(ConfigElement<Boolean, BooleanValue> value) {}
    }
    
    private static class EnumElement<T extends Enum<T>> extends ConfigElement<T, ConfigValue<T>> {
        EnumElement(ConfigElement<T, ConfigValue<T>> value) {}
    }
}
Yanny
  • 178
  • 12
  • I don't understand how `ConfigElement` from `EnumElement`), but `EnumValue>` is _not_ within bounds of `T extends Enum` of `EnumValue`. I find this weird – Eugene Jul 08 '20 at 15:36

2 Answers2

1

…Is there way how to make it working?…

I got your example working this way:

  1. public <V, U extends ConfigValue<V> >EnumElement(ConfigElement<V, U> value){}…
  2. …new EnumElement((ConfigElement<? extends Enum<?>, EnumValue<? extends Enum<?>>>) element )…

I added some skeletal implementation to a couple of your classes to confirm that the solution is usable:

…
static public void main( String ...args ){
        
    EnumValue< Foo > ev = new EnumValue< >( );
        
    ConfigElement< Foo, EnumValue< Foo > > ce = new ConfigElement< >( );
        
    EnumElement< Foo > ee = new EnumElement< >( ce );
        
    ee.setE( Foo.BAR );
        
    Foo bar = ee.getE( );
    
    out.println( bar );
    
    Consumer< Element > c = CONFIG_ELEMENTS.get( ev.getClass( ) );
    
    c.accept(ee);
}
…

If you want to check it compiles and runs for you, click the green Start button at the top of the online IDE project in the link above.

deduper
  • 1,944
  • 9
  • 22
  • well, you made a raw declaration now via `EnumElement` (`Enum` is raw), but either way your answer does not _explain_ why this happens. Even more to the point this `element -> new EnumElement<>((ConfigElement>) element)` will work; so to me you have not really answered much – Eugene Jul 08 '20 at 13:45
  • If you'd clicked on the link I shared, you'd have seen, @Eugene, I didn't change the original generic class declaration for _`EnumElement`_. The missing type variable section ( `>`) was a result of SO not parsing the literal `<>` characters that I pasted in instead of `<` and `>` entities. The OP's question was: „…_Is there way how to make it working?_ [sic]…“. I've answered that question by way of working code. What is up with your oddly _emotional_ reaction to me fulfilling the OP's request by implementing working code? SO bizarre. – deduper Jul 08 '20 at 18:30
  • sorry, I never meant to put it this way; it's just an interesting question (at least I found it as such) and was really expecting a broader answer, at least I hoped for. I've taken my time to look at what you did there in the link and ... yeah, that will work, though it's a bit dirty. It still works, agreed, so 1+ – Eugene Jul 08 '20 at 18:39
0

First of all let's make a few things clear. Your first CONFIG_ELEMENTS.put... is equivalent to (just making sure you understand this).

CONFIG_ELEMENTS.put(
        BooleanValue.class,
        element -> {
            new BooleanElement((ConfigElement<Boolean, BooleanValue>) element);
            return;
        }
);

Now the CONFIG_ELEMENTS map that you declare, knows that the value it holds is of type Element, that is the above can be written as:

CONFIG_ELEMENTS.put(
        BooleanValue.class,
        (Element element) -> {
            new BooleanElement((ConfigElement<Boolean, BooleanValue>) element);
            return;
        }
);

that is why the only way to make this work is to cast that element; because it is of type Element, but your BooleanElement takes a ConfigElement as input. That should be obvious.


The second CONFIG_ELEMENTS.put... explanation has to start with this:

private static class EnumElement<T extends Enum<T>> extends ConfigElement<T, ConfigValue<T>> {

    EnumElement(ConfigElement<T, ? extends ConfigValue<T>> value) {

    }
}

Notice the ? extends ConfigValue<T>> value part. You need that because generics are invariant, so in order to be able to pass something that extends ConfigValue, you need to properly declare the parameter type.

The rest of the explanation is my pure educated guessing, because I don't truly understand what is going on here (I have some hints that this has something to do with using raw types and the fact that T from EnumElement and T from EnumValue are different type variables)...

The easiest way to make this work, IMO, is to change this:

private static class EnumValue<T extends Enum> extends ConfigValue<T> {}

to using a raw Enum.

EDIT

Here is full example that was requested in comments:

public class DeleteMe {


    private static final Map<Class<?>, Consumer<Element>> CONFIG_ELEMENTS = new HashMap<>();

    static {
        CONFIG_ELEMENTS.put(BooleanValue.class, element -> new BooleanElement((ConfigElement<Boolean, BooleanValue>) element));
        CONFIG_ELEMENTS.put(EnumValue.class,
                            element -> new EnumElement<>((ConfigElement<Enum, EnumValue<Enum>>) element)); // doesn't compile
    }


    private static class BooleanValue extends ConfigValue<Boolean> {}

    private static class BooleanElement extends ConfigElement<Boolean, BooleanValue> {

        BooleanElement(ConfigElement<Boolean, BooleanValue> value) {}
    }

    private static class ConfigValue<T> {

    }

    private static class EnumValue<TT extends Enum> extends ConfigValue<TT> {}


    private static class Element {}

    private static class ConfigElement<S, T extends ConfigValue<S>> extends Element {}


    static class EnumElement<T extends Enum<T>> extends ConfigElement<T, ConfigValue<T>> {

        EnumElement(ConfigElement<T, ? extends ConfigValue<T>> value) {

        }

        private T e;
        
        public void setE(T e) { this.e = e; }

        public T getE() { return e; }
    }


    static public void main(String... args) {

        EnumValue<Foo> ev = new EnumValue<>();
        ConfigElement<Foo, EnumValue<Foo>> ce = new ConfigElement<>();
        EnumElement<Foo> ee = new EnumElement<>(ce);
        ee.setE(Foo.BAR);
        Foo bar = ee.getE();
        System.out.println(bar);
        Consumer<Element> c = CONFIG_ELEMENTS.get(ev.getClass());
        c.accept(ee);
    }

    enum Foo {
        BAR
    }

}
Eugene
  • 117,005
  • 15
  • 201
  • 306
  • It would be very constructive for me — _and the entire community_ — if you would, please, implement that change? Then after making that change, demonstrate how all the different generic classes would be instantiated to actual parameterized types; using compatible type arguments. You know what I mean, Eugene? See [*my answer*](https://stackoverflow.com/a/62788840/4465539) for the kind of example instantiations that I'm requesting. TIA. – deduper Jul 08 '20 at 19:06
  • Thx Eugene. In your original answer you said: „the above can be written as:… `(Element element) -> {…}`“. But you didn't make that change in the edited _full example_ code you pasted. Also you said: „change this: `private static class EnumValue extends ConfigValue {}` to using a raw `Enum`“. But you didn't make that change either. But you did make this change : `EnumElement(ConfigElement> value) …`. Which is effectively synonymous with the change I made: `… >EnumElement(ConfigElement value)…`. So I'm flattered ;) – deduper Jul 08 '20 at 19:47
  • @deduper 1) `(Element element)` is useless, it was only presented to prove a point; nothing more. 2) what do you mean I did not change to a raw enum? And this? `private static class EnumValue extends ConfigValue {}` 3) I don't know about the flattering part also, because you made this work by casting to a `ConfigElement` that uses wildcards, _everywhere_; I am not a fan of that. But it's good that you feel good :) – Eugene Jul 08 '20 at 19:57
  • „And this? `private static class EnumValue extends ConfigValue {}`“. Yeah. That. Changing the name of a type variable from `T` to `TT` does not „_change it to using a raw `Enum`_“. This is a peculiar comment: „_you made this work by casting to a ConfigElement that uses wildcards, everywhere_“. I didn't _cast_ anything. The **only** casts in [*the code I submitted*](https://www.browxy.com/#USER_297836) are the ones that were originally there. That is, unless you're using the word _cast_ to mean something different than what it is widely known to mean in the Java literature? – deduper Jul 08 '20 at 20:40
  • @deduper the fact that I changed to `TT` to denote that these are different types from the _other_ `T` does not change the fact that `Enum` is still raw. The original post that did not compile, did not had wildcards, you introduced them... Anyway, I'll stop commenting now. Have a good day. – Eugene Jul 08 '20 at 20:50
  • I see now. I might've read what you wrote too fast; or something got lost in translation. „_did not had [sic] wildcards, you introduced them_“. I'm too humble a guy to take credit for the introduction of wildcards, my dude. We owe that honor to folks like [_Gilad Bracha, Mads Torgersen, Erik Ernst and Christian Plesner Hansen_](https://docs.oracle.com/javase/specs/jls/se14/html/jls-5.html#jls-5.1.10). Why have wild cards in Java if they're not intended to be used when they're needed? You used them yourself in your answer where you determined they were needed. So I'm sure you know what I mean. – deduper Jul 08 '20 at 21:21