5

I am trying to make a method that takes an Enum value and returns an object that is casted to a class based on that Enum value. For example, I have an Enum called ComponentType:

public enum ComponentType
{
    HEALTH(HealthComponent.class),
    HUNGER(HungerComponent.class);

    private Class<? extends Component> componentClass;

    private ComponentType(Class<? extends Component> componentClass)
    {
        this.componentClass = componentClass;
    }

    public Class<? extends Component> getComponentClass()
    {
        return componentClass;
    }
}

"HealthComponent" and "HungerComponent" are two classes that both extend a class called "Component". What is inside them is not important for this question.

An Entity would have a list of Components that they are assigned (For example, one entity may have hunger, while another may have health, and another may have both).

My objective is to create a method inside the Entity that when a value from the ComponentType Enum is passed in, a Component object casted to the corresponding class type for that value is returned. So if you passed in ComponentType.HEALTH, the method would return an object casted to HealthComponent. Here is what I am trying, but it is not working: (Edit: see below)

public <T extends Component> T getComponentByType(ComponentType type)
{
    Class<? extends Component> componentClass = type.getComponentClass();
    for(Component component : componentList)
    {
        if(component.getClass() == componentClass)
        {
            return (T) componentClass.cast(component);
        }
    }
    return null;
}

For the above method, when passing in a type of ComponentType.HEALTH:

entity.getComponentByType(ComponentType.HEALTH);

an object casted to "Component" rather than "HealthComponent" would be returned. I want it to return an object casted to HealthComponent, not Component.

Is there any way to do this? I feel like this should be possible. My reason for trying to find a way to do this is because it seems like doing all this casting:

HealthComponent component = (HealthComponent) entity.getComponentByType(ComponentType.HEALTH);

is a bit wasteful since the method could (hopefully) assume what I want to cast to by the ComponentType passed in.

Edit (More info):

Taking a closer look at the results, I noticed that my method above some-what works. What I mean by this is that in Eclipse, if I type out:

component = entity.getComponentByType(ComponentType.HEALTH);

(the component variable is not defined yet) and then hover over getComponentByType to see what it is returning, it says it is returning <Component> Component

However, if I manually define the variable type (most of the time I just let Eclipse create the variables for me) like this:

HealthComponent component = entity.getComponentByType(ComponentType.HEALTH);

And then hover over getComponentByType to see what it is returning, it says it is returning <HealthComponent> HealthComponent and it does compile and run. So it technically works, but not in the way I'd like it to. This is a small issue because if I tell Eclipse to create a local variable for me in the first example, it would create a variable of type "Component" which I would have to manually change.

8
  • So, what happens when you compile and run that code? Commented Oct 17, 2014 at 21:01
  • @JBNizet see my edits above. Commented Oct 17, 2014 at 21:15
  • It's a generic method, and its returned type can't be inferred by the type of its argument by the compiler. So it can only be inferred by the type of the variable it's assigned to. You can always use entity.<HealthComponent>getComponentByType(ComponentType.HEALTH). Commented Oct 17, 2014 at 21:18
  • @JBNizet Okay, I guess that will have to do then. Thanks! Commented Oct 17, 2014 at 21:20
  • You seem to be confusing the type of an expression with the class of an object. Casting affects only the former. An expression evaluating to a reference of type Component can refer to an object whose class is a sub-type of Component. Commented Oct 17, 2014 at 21:21

2 Answers 2

2

You want the compile-time return type of getComponentByType() to depend on the parameter that you use to select the component. This is possible with generics, but only if the parameter actually carries the compile-time type information that you need.

Unfortunately, you can't add type parameters to enum values (see this question), but if you look at your own code again, you might realize that you already have an object that fittingly describes which component you want to get, and that happens to carry the type information we need: the Class<> object of each component type!

So, here's my suggestion for your function (not compiled or tested, beware, my Java might be rusty):

public <T extends Component> T getComponentByType(Class<T> type)
{
    for(Component component : componentList)
    {
        if(component.getClass() == type)
        {
            return (T)component;
        }
    }
    return null;
}
Sign up to request clarification or add additional context in comments.

Comments

0

I'm 4 years late to the party, but I came across it because I had a similar situation and wanted a nice solution.

It's possible to solve this by giving the enum a way to extract and cast instead of relying on an entity trying to figure out the type.

For example, you can add a method to your ComponentType enum like this:

public <C extends Component> C fromEntity(Entity entity) {
    List<Component> componentList = entity.getComponentList();
    // add your existing iteration and type casting logic here
}

And then later instead of trying this:

HealthComponent component = (HealthComponent) entity.getComponentByType(ComponentType.HEALTH);

You can write something like this

 HealthComponent component = ComponentType.HEALTH.fromEntity(entity);

In my case, I wanted to have a map with an enumerated key, and went with a solution like this:

https://gist.github.com/hiljusti/5240d42e827b77e65f41f0a7dfac4406

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.