Justin Lee Justin Lee - 2 months ago 10
Java Question

Java Abstract, Generics, and Builders

this is my first time posting but I'm having a lot of trouble with a problem.
I currently have an AbstractDevice class with the header:

public abstract class AbstractDevice<T extends AbstractDevice.Builder<T>> implements Device


and this class has a nested builder class with the header:

public static abstract class Builder<T>


I also have a AbstractPeripheral class with the header:

public abstract class AbstractPeripheral<T extends AbstractPeripheral.Builder<T>> extends AbstractDevice<AbstractPeripheral.Builder>


and this class also has its own nested builder class with the header:

public static abstract class Builder<T> extends AbstractDevice.Builder<Builder>.


My goal is the have the AbstractPeripheral extend the AbstractDevice and the AbstractPeripheral's builder extend AbstractDevice's. However, I am getting this error when trying to compile:


type argument uxb.AbstractPeripheral.Builder is not within bounds of type-variable T.


Any help is appreciated. Thanks
Abstract Device:

package uxb;

import java.util.List;
import java.util.Optional;
import java.util.ArrayList;
import java.math.BigInteger;


public abstract class AbstractDevice<T extends AbstractDevice.Builder<T>>
implements Device{

public static abstract class Builder<T>{

private Integer version;

private Optional<Integer> productCode;

private Optional<BigInteger> serialNumber;

private List<Connector.Type> connectors;

public Builder(Integer version){
this.version = version;
} //end constructor method


public T productCode(Integer productCode){
if(productCode != null){
this.productCode = Optional.of(productCode);
} //end if statement
else{
this.productCode = Optional.empty();
} //end else statement
return getThis();
} //end method productCode()

public T serialNumber(BigInteger serialNumber){
if(serialNumber != null){
this.serialNumber = Optional.of(serialNumber);
} //end if statement
else{
/*Class has a static field for ZERO value*/
this.serialNumber = Optional.empty();
} //end else statement
return getThis();
} //end method serialNumber()


public T connectors(List<Connector.Type> connectors){
this.connectors = connectors;
return getThis();
} //end method connectors


protected abstract T getThis();


protected List<Connector.Type> getConnectors(){
return connectors;
} //end method getConnectors()


protected void validate(){
if(version == null){
throw new NullPointerException("Cannot be validated");
}
} //end method validate()

} //end nested abstract class Builder


private final Integer version;

private final Optional<Integer> productCode;


private final Optional<BigInteger> serialNumber;

private final List<Connector.Type> connectors;

private final List<Connector> connectorObjects;


protected AbstractDevice(Builder<T> builder){
this.version = builder.version;
this.productCode = builder.productCode;
this.serialNumber = builder.serialNumber;
this.connectors = builder.connectors;
ArrayList<Connector> temp = new ArrayList<Connector>();
for(int i = 0; i < connectors.size(); i++){
temp.add(new Connector(this, i, connectors.get(i)));
} //end for loop
connectorObjects = temp;
} //end constructor method



public Optional<Integer> getProductCode(){
return productCode;
} //end method getProductCode()


public Integer getConnectorCount(){
/*Not Implemented Yet*/
return 0;
} //end method getConnectorCount()



public Optional<BigInteger> getSerialNumber(){
return serialNumber;
} //end method getSerialNumber()


public Integer getVersion(){
return version;
} //end method getVersion()

public List<Connector> getConnectors(){
return new ArrayList<Connector>(connectorObjects);
} //end method getConnectors()


public Connector getConnector(int index){
if(! getConnectors().isEmpty()){
return getConnectors().get(index);
} //end if statement
else{
return null;
} //end else statement
} //end method getConnector()

} //end abstract class AbstractDevice


Abstract Peripheral:
package uxb;

import java.util.List;

public abstract class AbstractPeripheral<T extends
AbstractPeripheral.Builder<T>> extends
AbstractDevice<AbstractPeripheral.Builder>{

public static abstract class Builder<T> extends
AbstractDevice.Builder<Builder>{

protected void validate(){
super.validate();
if(getConnectors().equals(null)){
throw new IllegalStateException("Cannot be validated");
} //end if statement
if(checkTypes(getConnectors())){
throw new IllegalStateException("Cannot be validated");
} //end if statement
} //end method

private boolean checkTypes(List<Connector.Type> types){
for(Connector.Type type: types){
if(type != Connector.Type.PERIPHERAL){
return false;
} //end if statement
} //end for each loop
return true;
} //end method checkTypes

public Builder(Integer version){
super(version);
} //end constructor method
} //end nested class Builder

public AbstractPeripheral(Builder<T> builder){
super(builder);
}
} //end class AbstractPeripheral


Hub:
package uxb;

public class Hub extends AbstractDevice<Hub.Builder>{

public static class Builder extends AbstractDevice.Builder<Builder>{

public Builder(Integer version){
super(version);
} //end constructor method

public Hub build(){
validate();
return new Hub(getThis());
} //end method build()

protected Builder getThis(){
return this;
} //end method getThis()

protected void validate(){
super.validate();
if(!(super.getConnectors().contains(Connector.Type.COMPUTER))){
throw new IllegalStateException("Cannot be validated");
} //end if statement\
if(!(super.getConnectors().contains(Connector.Type.PERIPHERAL))){
throw new IllegalStateException("Cannot be validated");
} //end if statement
} //end validate()
} //end nested class Builder

private Hub(Builder builder){
super(builder);
} //end constructor method

public DeviceClass getDeviceClass(){
return DeviceClass.HUB;
} //end method getDeviceClass()


} //end class Hub

Answer

You really need to use both the class being built, and the builder, as parameters on the builder. The object being build does not need to know about the builder, however, so does not require it as a parameter.

I have used this pattern or slight variations in several open source projects, including Apache Brooklyn and jclouds.

So, from your example, change the parent class as follows:

public abstract class AbstractDevice implements Device {
    public static abstract class Builder<T, B> {
        public abstract B self();
        public abstract T build();
        public B example(String value) {
            // do something with value
            return self();
        }
    }
}

Note that I added an abstract build() method as well, which will return the built object. The self() method is required because when if builder methods were to return this it would have the wrong type. Instead, each builder method must end with return self(); as in the example(String value) method shown. Then the child becomes:

public abstract class AbstractPeripheral extends AbstractDevice {
    public static abstract class Builder<T, B> extends AbstractDevice.Builder<T, B> {
    }
}

You can see that the T and B generic parameters are being used to point to the type of the class and the type of the builder respectively. So, to make a concrete class that uses these, something like this should be created:

public class Hub extends AbstractPeripheral {
    public static class Builder extends AbstractPeripheral.Builder<Hub, Builder> {
        public static final Builder builder() {
            return new Builder();
        }
        public Builder self() {
            return this;
        }
        public Hub build() {
            return new Hub();
        }
    }
}

This also has a static builder() method, that returns an instance of the correct Builder class, you could also call the constructor directly if you want as well. The build() method simply creates and returns the concrete class, and the self() method is implemented here to return this which will have the correct type.

Putting everything together, we can use this to create a Hub object as follows:

Hub hub = Hub.Builder.builder()
        .example("something")
        .build();

Note that the AbstractDevice.Builder#example(String) method returns the correct type since it actually calls Hub#self(), and build() returns a concrete instance of Hub as expected.

To take a little of the pain out of this and remove the repeated boilerplate, you could also try using the Google AutoValue project, which we are switching to in jclouds now.