Stefan Lechner
Stefan Lechner

Reputation: 141

How to return List<Impl> for List<interface>

The following code is pretty useless and not tested. It should only explain the problem. I want to hide the implementing classes from the application, but define the hierarchy.

Given following interfaces

public interface RowType {
    Integer getSumFromHere();
}

public interface TableType {
    List<RowType> getRows();
}

Implemented by

public class RowImpl implements RowType {

    private Integer value = 0;
    private RowImpl nextRow;

    public RowImpl someFunctionNotInInterface() {
        return nextRow;
    }

    @Override
    public Integer getSumFromHere() {       
        return nextRow == null ? value : value + nextRow.getSumFromHere();
    }
}

public class TableImpl implements TableType {

    List<RowImpl> implList = new ArrayList<>();

    public void doSomethingOnImpl (){
      for(RowImpl row : implList) {
        row.someFunctionNotInInterface();
      }
    }

    @Override
    public List<RowType> getRows() {
        return implList;
    }  
}

The implementation of getRows() leads to an error "cannot convert from List<RowImpl> to List<RowType>" In fact, it`s guaranteed that every entry in the implList can be accessed through the interface RowType, so it could work.

I tried <? extends RowType> but that's not compatible to the TableType interface. Of course I can simple solve the problem, by copying the list return new ArrayList<>(implList); but that's not the same as having a reference to the list held by the class.

Is there a solution for this, or is the design completely wrong ?

edit: Added function in TableImpl that clarifies why the list is build on RowImpl and not on RowType.

Upvotes: 1

Views: 218

Answers (3)

ernest_k
ernest_k

Reputation: 45309

The easiest solution from where you are could be just type-casting your list elements:

public class TableImpl implements TableType {

    List<RowImpl> implList = new ArrayList<>();

    @Override
    public List<RowType> getRows() {
        return implList.stream().map(e -> (RowType) e).collect(Collectors.toList());
    }  
}

A better solution, though, can involve redesigning your API, to make TableType aware of the type of RowType it holds. This is only good if you allow code that calls getRows() to know about the implementation class RowImpl (otherwise the previous solution is suitable).

public interface TableType<T extends RowType> {
    List<T> getRows();
}

With this, your implementation class can use RowImpl by implementing this version of the interface in this way:

public class TableImpl implements TableType<RowImpl> {

    List<RowImpl> implList = new ArrayList<>();

    @Override
    public List<RowImpl> getRows() {
        return implList;
    }  
}

Upvotes: 2

Matt Timmermans
Matt Timmermans

Reputation: 59154

implList is a List<RowImpl> and should contain only RowImpl instances.

The List<RowType> you return has an add(RowType) method, for example, that could be used to add RowType instances that are not RowImpl.

For this reason, List<RowType> is not a supertype of List<RowImpl> and you will have to cast implList if you want to return it.

At the same time, you should make sure that it is not modified by the caller, so that it really can only contain RowImpl instances.

The Collections.unmodifiableList() method does both of those jobs:

@Override
public List<RowType> getRows() {
    return Collections.unmodifiableList(implList);
}  

Upvotes: 3

Glains
Glains

Reputation: 2863

You should change the generic type to

List<RowType> implList = new ArrayList<>();

rather than:

List<RowImpl> implList = new ArrayList<>();

When inserting an element into the List, you can guarantee that it has the required type. For example:

implList.add(new RowImpl());

Upvotes: 1

Related Questions