Solved Generic type problem

Discussion in 'Spigot Plugin Development' started by Sataniel, Jan 8, 2020.

  1. Hey guys,
    I have a problem with generic types and can't figure out why. Hope you can help me.

    StatusModifier<T>#getValue() returns a T and I'd like to implement a method that can get a StatusModifier from an object without the need to cast. This is what I have:

    Code (Text):
        default <V> StatusModifier<V> getStatusModifier(Class<V> type, String key) {
            for (StatusModifier<?> status : getStatusModifiers()) {
                if (!type.isInstance(status.getValue())) {
                    continue;
                }
                if (status.getKey().equals(key)) {
                    return (StatusModifier<V>) status;
                }
            }
            return null;
        }
    However calling this gives me an unchecked warning:
    Code (Text):
    StatusModifier<Double> test = gui.<Double>getStatusModifier(Double.class, ~);
    The IDE says "required: StatusModifier<Double>" and consequently,
    Code (Text):
    Double test = gui.<Double>getStatusModifier(Double.class, ~).getValue();
    still needs a cast which makes all of this kind of pointless.

    Do you know a fix?

    ~Best regards
    Sataniel
     
  2. Code (Text):

    Double test = gui.getStatusModifier(Double.class, "MasterKey").getValue()
     
    This should do the generic thing. Could you post your StatusModifier class if the above snippet doesn't work.
     
  3. Yes, I also expected this to work.
    https://github.com/DRE2N/Vignette/b...thon/vignette/api/context/StatusModifier.java
    https://github.com/DRE2N/Vignette/b...thon/vignette/api/context/Contextualized.java
    These are the classes with the only difference that Contextualized has the #getStatusModifier(Class<V>, String) method I posted in the OP.
     
  4. No. Both are in the class I'm using.

    Edit: By the way, I'm using this method to get an enum value:
    Code (Text):
        public static <E extends Enum<E>> E getEnum(Class<E> enumClass, String valueName) {
            if (enumClass == null || valueName == null) {
                return null;
            }
            try {
                return Enum.valueOf(enumClass, valueName);
            } catch (IllegalArgumentException exception) {
                return null;
            }
        }
    And
    Code (Text):
    Material test = getEnum(Material.class, Material.AIR.name());
    works fine without warnings.
     
    #5 Sataniel, Jan 8, 2020
    Last edited: Jan 8, 2020
  5. Could you update it to your latest state? Could be another branch, so that master keeps stable.
     
  6. I think there is no solution without migrating to a Map<String, StatusModifier<?>>. Second thing I recommend is moving most of the implementation out of the interface.
    Sorry
     
  7. The reason why this doesn't work is because Java is unable to figure out on its own that, when you filter out every StatusModifier that is not a StatusModifier<V>, you will be left with only StatusModifier<V>s. Since a cast from StatusModifier<?> to StatusModifier<V> might be unsafe, you're left with this warning.
    As for why your enum example does not give such issues, it's quite simple: at no point do you do such a cast. You make a call to Enum.valueOf, which in turn gives you your enum value by the given name. The Enum class itself doesn't have such warnings either, since it internally retrieves values from a Map<String, T>, where T is the same as your Class<T>. So, from Class<T> in your method to Map<String, T> in Java's Class (same T) to a single T from that Map (same T), back to your method as return value (same T). At no point was any conversion necessary.
    From a larger perspective, no conversion needs to happen, since the Class only has one type of data to store: T. This makes sense, since if you have an enum - let's call it MyEnum - all constants declared in there, will be of type MyEnum. You can't have a constant in MyEnum, that is suddenly of YourEnum. Therefore, the Map only needs to store values of T (in this case T would be MyEnum).
    This differs from your example, where your internal storage can store multiple types of StatusModifier, which means that they will be stored as StatusModifier<?>. This means that you could have a StatusModifier<String> and a StatusModifier<Integer>, but we don't know that, since any information we may have had about this generic is lost.
     
    • Informative Informative x 2
  8. Thank you anyway for your time! However I believe having the convenience function implemented as default methods in the interface is fine. The JDK also uses default methods fairly much in its interfaces, like e.g. Collection#removeIf(), #stream(), #iterator(), List#replaceAll().