1

I have a class ExampleProperty that extends the superclass Property and in a separate class, GameObject, I have an ArrayList<Property> properties as well as the following method where Engine.createEngineObject(new ExampleProperty()) simply adds the new ExampleProperty() to a List and returns it back, propertyId is an inherited member of Property, and where properties is the list of Propertys:

public ExampleProperty addExampleProperty()
    {
        ExampleProperty temp = (ExampleProperty)Engine.createEngineObject(new ExampleProperty());
        temp.propertyId = properties.size();
        return temp;
    }

I want to generalize this method so I can pass in a Class<?> propertyClass and have it return an object the same type as propertyClass (and also perform the cast on line 3?). I'm afraid I'm not familiar enough with generalization yet so any help is appreciated :)

EDIT: This is the revised code after the answer was posted. I forgot to mention a bit of info but I worked through it so here's what I have. It's probably awful but it's mine, it works, and I'm proud of it:

public <T extends Property> T addProperty(Class<T> propertyClass) throws IllegalAccessException, InstantiationException {
        Property property = propertyClass.newInstance();
        if(propertyClass.getSuperclass() == Engine.class)
        {
            Engine temp = Engine.createEngineObject(propertyClass.newInstance());
            temp.propertyId = properties.size();
            return (T)temp;
        }
        T temp = propertyClass.newInstance();
        temp.propertyId = properties.size();
        return (T)temp;
    }
Jam
  • 476
  • 3
  • 9
  • See my comments on the answer you used to write your update - that was a bad answer to crib notes from, unfortunately. – rzwitserloot Oct 25 '20 at 19:51

2 Answers2

1

This might do the trick:

public <T extends Property> T createEngineObject(Class<T> propertyClass) throws IllegalAccessException, InstantiationException {
    Property property = propertyClass.newInstance();
    // do something...
    return property;
}

Although, passing Property instance seems like a better idea:

public <T extends Property> T createEngineObject(T property) {
    // do something...
    return property;
}
TeWu
  • 5,928
  • 2
  • 22
  • 36
  • Dude working on this stuff makes me feel so big brain, but after a bit of tweaking your solution works 100%. Thank you a bunch – Jam Oct 25 '20 at 19:43
0

Your central issue here is that the things you are doing to ExampleProperty aren't captured in a type. Therefore, you can't generalize this verbatim.

Let's list what you 'do' with the currently-hardcoded type (ExampleProperty), so that we know what to capture in types so we can generalize it:

  • You invoke its no-args constructor
  • You pass the result of that as first arg for a call to Engine.createEngineObject.
  • You cast the result of createEngineObject call to this type.
  • You refer to the propertyId field of this type.
  • You return the type.

Of all those operations, the most problematic one by far is the very first one: Constructors do not participate in the typing system. You can't declare 'has a no-args constructor' in a java type. At all. Whatsoever.

There are two solutions to the problem:

  1. Leave it stated solely in documentation with zero compile time guarantees, and use a java.lang.Class instance as vehicle so that the code knows where to fetch that constructor from. There will be no compile-time checking whatsoever that any type you pass into the genericsed form of this method in fact has a no-args constructor, or that it is non-abstract. If you fail these conditions, the only way you'll ever know is that some exception will occur at runtime.
  2. Use factories.

The right answer is of course the second one: Use factories. Capture the concept of 'a thing that can make ExampleProperty instances', because that can be conveyed in java's type system. We can use this factory as a vehicle for anything that applies to the entire type, if it comes up:

your generalized code:

public interface PropertyFactory<P extends Property> {
    P make();
}
// NB: You can replace the above with `java.util.function.Supplier` instead, but I'd make your own type.

public abstract class Property {
    int propertyId;
}

public <P extends Property> P addProperty(PropertyFactory<P> factory) {
    P temp = Engine.createEngineObject(factory.make());
    temp.propertyId = properties.size();
    return temp;
}

// in Engine.java
public <P extends Property> P createEngineObject(P property) {
    // do stuff here
    return property;
}

then to use:

private static final PropertyFactory<ExampleProperty> EXAMPLE_MAKER = ExampleProperty::new;

ExampleProperty ep = addProperty(EXAMPLE_MAKER);
rzwitserloot
  • 85,357
  • 5
  • 51
  • 72