简体   繁体   中英

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.

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;
    }
}

Even more simplified version:

public class Buffer {
    public <T extends Buffer> T create() {
        return (T)this;
    }

    public static class FooBuffer extends Buffer {}
    public static class BarBuffer extends FooBuffer {}

    public static void main(String... args) {
        BarBuffer b = new FooBuffer().create();
    }
}

The main method compiles without warnings, running the code results in ClassCastException . The warning in create points to real lack of typesafety in the code.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM