Question

I'm developing a library and i have a faced an issue when overriding the return type as a List of sub elements.

Initially its looked like this,

    Class Student {
    }

    public interface A {
        public Student isCurrentStudent(String name);
        public List<Student> getStudentList();
    }

    public interface B extends A {
        public List<Student> getColleges();
        public Student getBestFriend();
    }

Then i wanted to introduce SeniorStudent class and APIs for access SeniourStudent, so i came up with follows,

Class SeniorStudent extends Student{
}

public interface C extends A {
    public SeniorStudent isCurrentStudent(String name);
    public List<SeniorStudent> getStudentList();
    public void addStudent(Attributes s);
}

public interface D extends B,C {
    public List<SeniorStudent> getColleges();
    public Student getBestFriend();
}

Problem starts with interface C and D. Since java supports for covariant return types, first method of C doesn't give an error. But when the return type is "List", it make's the trouble. So I have changed above interfaces to use generics.

public interface A <T extends Student> {
    public Student isCurrentStudent(String name);
    public List<T> getStudentList();
}

public interface B<T extends Student> extends A<T> {
    public List<T> getColleges();
    public Student getBestFriend();
}

public interface C extends A<SeniorStudent> {
    public SeniorStudent isCurrentStudent(String name);
    public List<SeniorStudent> getStudentList();
    public void addStudent(Attributes s);
}

public interface D extends B<SeniorStudent>,C {
    public List<SeniorStudent> getColleges();
    public SeniorStudent getBestFriend();
}

I believe this design can be improved, such that implementor of class B can be restricted to use Student class only.

What ultimately I wanted to have is, to provide clean API which can be used like this,

B bObject = getBObject();
List<Student> studentList = bObject.getColleges();

D dObject = getDObject();
List<SeniourStudent> seniorStudentList = dObject.getColleges();

The whole problem would be solved if there is a way to override the return type when it is a List of sub elements.

Since my actual code is much larger, I have used simple example here to describe the problem. Hope its clear enough.


EDIT

Introducing a BS super type for B would solve the problem. But is it the cleaner way?

class diagram when introducing SB

public interface A <T extends Student> {
    public Student isCurrentStudent(String name);
    public List<T> getStudentList();
}

public interface BS<T extends Student> extends A<T> {
    public List<T> getColleges();
    public Student getBestFriend();
}

public interface B extends BS<Student> {
    public List<Student> getColleges();
    public Student getBestFriend();
}

public interface C extends A<SeniorStudent> {
    public SeniorStudent isCurrentStudent(String name);
    public List<SeniorStudent> getStudentList();
    public void addStudent(Attributes s);
}

public interface D extends BS<SeniorStudent>,C {
    public List<SeniorStudent> getColleges();
    public SeniorStudent getBestFriend();
}

Thanks,

Was it helpful?

Solution

As Plínio Pantaleão said (or actually, wanted to say) in the comment, you can define the interface to return a List<? extends Student>

import java.util.List;

class Attributes {}
class Student { }

interface A {
    Student isCurrentStudent(String name);
    List<? extends Student> getStudentList();
}

interface B extends A {
    List<? extends Student> getColleges();
}


class SeniorStudent extends Student{ }

interface C extends A {

    @Override
    SeniorStudent isCurrentStudent(String name);

    @Override
    List<SeniorStudent> getStudentList();

    void addStudent(Attributes s);
}

interface D extends B,C {
    @Override
    List<SeniorStudent> getColleges();
}
public class CovariantList
{
    public static void main(String[] args)
    {
        B b = null;
        List<? extends Student> collegesB = b.getColleges();

        D d = null;
        List<SeniorStudent> collegesD = d.getColleges();
    }
}

If you only know the most "basic" interface (B, in this case) you can only obtain a List<? extends Student>. If you know the particular type (as in D), you can obtain the covariant type List<SeniorStudent>.


EDIT Update based on the comments and the edited question:

The requirement that the API may be used as in the edited section of the main question could probably be achieved with something like this:

import java.util.List;

public class StudentsReturnTypesTest
{
    public static void main(String[] args)
    {
        B bObject = getBObject();
        List<Student> studentList = bObject.getColleges();

        D dObject = getDObject();
        List<SeniorStudent> seniorStudentList = dObject.getColleges();        
    }

    private static D getDObject() { return null; }
    private static B getBObject() { return null; }
}

interface A<T extends Student> {
    public Student isCurrentStudent(String name);
    public List<T> getStudentList();
}

interface AS extends A<Student> { }

interface B extends AS {
    public List<Student> getColleges();
}

interface C extends A<SeniorStudent> {
    public SeniorStudent isCurrentStudent(String name);
    public List<SeniorStudent> getStudentList();
    public void addStudent(Attributes s);
}

interface D extends C /* B Not possible, see below */  {
    public List<SeniorStudent> getColleges();
}

interface Attributes {}
interface Student {}
interface SeniorStudent extends Student {}

However in one of the comments, you mentioned

"For the interface D I wanted to inherit methods from B too"

This will be difficult. You basically have the problem that the method getColleagues() will then be inherited with different return types: Once with List<Student> and once with List<SeniorStudent>. It's simply not type-safe to do this.

At the moment, the only method that is defined in interface B is getColleagues(). And since this method (with a different return type) is already inherited from interface C, I assume that the intention to "...inherit methods from B too" referred to methods that are currently not contained in the example code that you posted.

A solution for this could possibly be to extract that the "methods from B" into a dedicated interface. The key point here is to avoid inheriting a method twice with different return types. This could possibly be achieved like in this example, where the respective methods are summarized in an interface with the beautiful name AdditionalMethodsThatHaveBeenInB ...

import java.util.List;

public class StudentsReturnTypesTest
{
    public static void main(String[] args)
    {
        B bObject = getBObject();
        List<Student> studentList = bObject.getColleges();
        bObject.methodThatWasInB();

        D dObject = getDObject();
        List<SeniorStudent> seniorStudentList = dObject.getColleges();
        dObject.methodThatWasInB();
    }

    private static D getDObject() { return null; }
    private static B getBObject() { return null; }
}

interface A<T extends Student> {
    public Student isCurrentStudent(String name);
    public List<T> getStudentList();
}

interface AS extends A<Student> { }

interface B extends AS, AdditionalMethodsThatHaveBeenInB {
    public List<Student> getColleges();
}

interface AdditionalMethodsThatHaveBeenInB {
    void methodThatWasInB();
}

interface C extends A<SeniorStudent> {
    public SeniorStudent isCurrentStudent(String name);
    public List<SeniorStudent> getStudentList();
    public void addStudent(Attributes s);
}

interface D extends C, AdditionalMethodsThatHaveBeenInB {
    public List<SeniorStudent> getColleges();
}

interface Attributes {}
interface Student {}
interface SeniorStudent extends Student {}

Of course, it's hard to tell whether this is also possible in your real code, but maybe a similar approach is applicable there.

OTHER TIPS

I'm a bit of confused what you're exactly trying to do. But I guess, your problem can be solved by not making interface C and D generic. You can have your interfaces like so:

interface A<T extends Student> {
    public T isCurrentStudent(String name);
    public List<T> getStudentList();
}

interface B<T extends Student> extends A<T> {
    public List<T> getColleges();
}

interface C extends B<Student> {
    public Student isCurrentStudent(String name);
    public List<Student> getStudentList();
    public List<Student> getColleges();
}

interface D extends B<SeniorStudent> {
    public SeniorStudent isCurrentStudent(String name);
    public List<SeniorStudent> getStudentList();
    public List<SeniorStudent> getColleges();
}

And then, make your class implement interface C and D, respectively for Student and SeniorStudent. This is the only way I can think of the top of my head. See if you can get along with this, else we can try to further improvise on this.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top