javagenericsbuilder-pattern

Unchecked conversion warning with generics


I don't quite understand, why my code has to do a unchecked conversion and how I can fix that.

I am implementing immutable objects with the builder pattern for which I implemented the "Immutable" interface with the inner interface "Builder".

Each immutable class implements the Immutable interface and implements a inner static class Builder, which implements the Builder interface.

All this works fine.

Now, I am implementing a bunch of very simple classes that don't really need a builder, but I still want to implement the Immutable interface, so the objects of those classes are instances of "Immutable", but I don't want to implement empty builders without any functionality for each class. I'd rather have an abstract class in which to implement one simple builder for all the simple classes. The builder will just store the original object and return it through the build() method, so the Immutable interface is implemented completely

The build() method of the builder has to return an object of the implementing class, though. So I added generics.

public interface Immutable {
    public interface Builder<T> {
        public T build();
    }
    public <T> Builder<T> builder();
}


public interface Interface extends Immutable {

    public interface BuilderInterface<T> extends Immutable.Builder<T> {

    }
}


public abstract class AbstractClass implements Interface {

    public static class AbstractBuilder<T> implements Interface.BuilderInterface<T> {

        private final T object;

        public AbstractBuilder(T object) {

            this.object = object;
        }

        @Override
        public T build() {

            return this.object;
        }
    }

    protected AbstractClass() {

        super();
    }
}

public class ConcreteClass extends AbstractClass {

    public ConcreteClass() {

    }

    @Override
    public AbstractBuilder<ConcreteClass> builder() {

        return new AbstractClass.AbstractBuilder<ConcreteClass>(this);
    }
}

I was expecting the generic type T of the Immutable interface to take the type of the implementing class, but instead it seems to be Object, which leads to the following warning:

Type safety: The return type AbstractClass.AbstractBuilder<ConcreteClass> for builder() from the type ConcreteClass needs unchecked conversion to conform to Immutable.Builder<Object> from the type Immutable

EDIT: The warning is given by the builder() method of ConcreteClass.


Solution

  • It's quite simple - the method signature of Immutable#builder expects the type parameter T set "on the fly" for the actual method call and not being bound to the class. To appropriately override this method, the signatur in ConcreteClass would be

    public <T> Builder<T> builder() {
    

    which obviously clashes with your builder definition

    return new AbstractClass.AbstractBuilder<ConcreteClass>(this);
    

    To make this all compilable, you have to infer T for Immutable#builder from the class and not from the method caller, i.e. that you finally have

    public interface Immutable<T> {
    
        public interface Builder<T> {
            public T build();
        }
    
        public Builder<T> builder(); 
    }
    

    and all inheriting classes changed accordingly to pass a T to its predecessors.

    public interface Interface<T> extends Immutable<T> {
    
        public interface BuilderInterface<T> extends Immutable.Builder<T> {
        }
    }
    
    
    public abstract class AbstractClass<T> implements Interface<T> {
    
        public static class AbstractBuilder<T> implements Interface.BuilderInterface<T> {
    
            private final T object;
    
            public AbstractBuilder(T object) {
    
                this.object = object;
            }
    
            @Override
            public T build() {
    
                return this.object;
            }
        }
    
        protected AbstractClass() {
    
            super();
        }
    }
    
    
    public class ConcreteClass extends AbstractClass<ConcreteClass> {
    
        public ConcreteClass() {
    
        }
    
        @Override
        public Builder<ConcreteClass> builder() {
            return new AbstractClass.AbstractBuilder<ConcreteClass>(this);
        }
    }