1

I have two methods:

   public <T extends Component> void addComponent(BluePrint bluePrint, Class<T> type) throws InstantiationException, IllegalAccessException {
        AddComponent addComponent = addComponentMap.get(type);
        if (addComponent == null) {
            addScriptable(bluePrint, type); <--- fails here
        }
    }

if addComponentMap.get(type); returns null, i know implicitely that T is of type Scriptabe and need to call:

    private <T extends Scriptable> void addScriptable(BluePrint bluePrint, Class<T> type) throws InstantiationException, IllegalAccessException {
        scriptableSystems.add(new ScriptableSystem<T>());
    }

The issue is that the upper bound for T in the second method is Scriptable and in the first method its Component, therefore type "could" potentially be any component when addComponent is null.

Can i somehow narrow the constraint to Scritpable when addComponent is null? Or somehow explicitly say that when addComponent is null T will extend Scriptable, before calling addScriptable?

Worth mentioning perhaps is that Scriptable inherits from component.

Tagor
  • 937
  • 10
  • 30
  • 1
    Are the other components in the map also `Scriptable`? In that case, make `T extends Scriptable` in the first one, too. Otherwise, *you could just cast if you definitely know*. If you cannot guarantee that all components in the map are `Scriptable`, I'd say you are out of luck using only generics. – Malte Hartwig Oct 16 '17 at 14:56
  • Perhaps something like [this](https://stackoverflow.com/questions/745756/java-generics-wildcarding-with-multiple-classes). – Andrew S Oct 16 '17 at 14:57
  • @MalteHartwig The other components in the map are not Scriptable. How do i cast the variable type? What other methods could i use? – Tagor Oct 16 '17 at 15:09
  • @Tagor, I wrote an answer explaining how `Class.isAssignableFrom()` could help – Malte Hartwig Oct 16 '17 at 15:10

2 Answers2

1

The issue is that the upper bound for T in the second method is Scriptable and in the first method its Component, therefore type "could" potentially be any component when addComponent is null.

That's about right, but I'd put it more strongly: since Scriptable extends Component (as opposed to the other way around), type argument T in method addComponent() can always be a type that is not bounded above by Scriptable.

Can i somehow narrow the constraint to Scritpable when addComponent is null?

Sure. Supposing that you don't want a tighter bound in other cases, that's what casting is for:

public <T extends Component> void addComponent(BluePrint bluePrint, Class<T> type)
        throws InstantiationException, IllegalAccessException {
    AddComponent addComponent = addComponentMap.get(type);

    if (addComponent == null) {
        addScriptable(bluePrint, (Class<? extends Scriptable>) type);
    }
}

You will of course get a compiler warning about the cast. This is right and proper because that code depends on a condition that the compiler cannot verify.

Or somehow explicitly say that when addComponent is null T will extend Scriptable, before calling addScriptable?

That's exactly what you do say by casting. A cast of a value of reference type is effectively an assertion that you know more about the runtime type of that value than the compiler can prove.

Additionally, you can perform a runtime test if you'd rather have a filaure behavior different from a ClassCastException:

if (!Scriptable.class.isAssignableFrom(type)) {
    throw new MyChosenException();
}
John Bollinger
  • 160,171
  • 8
  • 81
  • 157
0

If you cannot narrow down the generic type of class in your map, you can still cast. And to be safer, you can use Class.isAssigneableFrom, which is an instanceof for classes (instead of objects):

public static void main(String[] args) {
    Map<String, Class<? extends Number>> map = new HashMap<>();
    Class<? extends Number> someNumberClass = map.get("Double");
    if (Integer.class.isAssignableFrom(someNumberClass)) {
        acceptInteger((Class<Integer>) someNumberClass);
    }
}

public static void acceptInteger(Class<Integer> c) { }

This doesn't even need the null check and could work for cases where addComponent is not null. In your example, you would do:

if (Scriptable.class.isAssignableFrom(type)) { // add the null check if necessary
   addScriptable(bluePrint, (Class<Scriptable>) type);
}
Malte Hartwig
  • 4,477
  • 2
  • 14
  • 30
  • @Tagor For a more detailed explanation, please read [John Bollinger's extensive answer to this question](https://stackoverflow.com/a/46773600/7653073). – Malte Hartwig Oct 16 '17 at 15:25
  • Nice! never heard of `isAssigneableFrom`, thanks for sharing :) – Tagor Oct 16 '17 at 15:50