Search code examples
javagenericsinheritanceunchecked-cast

Avoiding unchecked casts with generics with double extending classes?


I have the following code, which I just refactored to this today since I understand what <T extends Buffer> truly means, here is the simplified version:

public class Buffer {
    protected final int bufferType;
    protected final int bufferDataType;

    protected int bufferId;
    protected boolean created;

    public Buffer(final int bufferType, final int bufferDataType) {
        this.bufferType = bufferType;
        this.bufferDataType = bufferDataType;
    }

    public <T extends Buffer> T create() {
        assertNotCreated();
        bufferId = GL15.glGenBuffers();

        created = true;
        return (T)this;
    }

    public boolean hasBeenCreated() {
        return created;
    }

    private void assertNotCreated() {
        if (hasBeenCreated()) {
            throw new RuntimeException("Buffer has been created already.");
        }
    }
}

public class ArrayBuffer extends Buffer {
    public ArrayBuffer(final int bufferDataType) {
        super(GL15.GL_ARRAY_BUFFER, bufferDataType);
    }    
}

public class DynamicDrawArrayBuffer extends ArrayBuffer {
    public DynamicDrawArrayBuffer() {
        super(GL15.GL_DYNAMIC_DRAW);
    }
}

The warning happens at Buffer.create(), is it safe to suppress the warning? Is there any way to make it more safe?

The other requirement is that no clutter should be added to the calling/using code of this API, concretely this means that DynamicDrawArrayBuffer may not get generics attached to it.


Solution

  • This is obviously not type-safe. You can write

    ArrayBuffer a = new ArrayBuffer(0);
    DynamicDrawArrayBuffer d = a.create();
    

    and this will cause a ClassCastException. The return type of the create method is inferred from the call site. And the only information that is available at the call site here is that the return value must be a DynamicDrawArrayBuffer - so the ArrayBuffer is brutally casted to be one.

    I think you could easily exploit covariance here: The create method could always return the type of the class. The return type of the create method then is determined by the type information that you have when you call the method:

    public class BufferTest
    {
        public static void main(String[] args)
        {
            ArrayBuffer a = new ArrayBuffer();
            ArrayBuffer aa = a.create(); // Yes
            // DynamicDrawArrayBuffer ad = a.create(); // No
    
            DynamicDrawArrayBuffer d = new DynamicDrawArrayBuffer();
            ArrayBuffer da = a.create(); // Yes
            DynamicDrawArrayBuffer dd = d.create(); // Yes
    
            Buffer b = a;
            Buffer bb = b.create(); // Yes
            //ArrayBuffer ba = b.create(); // No
        }
    }
    
    class Buffer 
    {
        public Buffer create() 
        {
            // create etc...
            return this;
        }
    
    }
    
    class DynamicDrawArrayBuffer extends ArrayBuffer 
    {
        @Override
        public DynamicDrawArrayBuffer create()
        {
            super.create();
            return this;
        }
    }
    
    class ArrayBuffer extends Buffer 
    {
        @Override
        public ArrayBuffer create()
        {
            super.create();
            return this;
        }
    }