membersound
membersound

Reputation: 86935

How to change collection type in inheritance class?

I have a base class that defines a collection List<Person>. Now, one specific class extending the base class should change the collection type.

Is that possible?

public class Request {
    private List<Person> persons;

    //getter, setter
}


public class SubRequest1 extends Request {
}

public class SubRequest2 extends Request {
}

//only this class should define a different list type
public class SubRequest3 {
    private List<SubRequest3.Person> persons;

    //compiler error. this is invalid code!
    @Override
    public List<SubRequest3.Person> getPersons() {
        return persons;
    }

    @Override
    public void setPersons(List<SubRequest3.person> persons) {
        this.persons = persons;
    }

    static class SubRequest3.Person extends Person {

    }
}

Upvotes: 0

Views: 287

Answers (3)

Lino
Lino

Reputation: 19910

You can just declare your class to accept a generic type T which extends Person:

public class Request<T extends Person> {
    private final List<T> people = new ArrayList<>(); // or any other implementation

    public void setPeople(List<? extends T> people){
        this.people.clear();
        this.people.addAll(people);
    }

    public List<T> getPeople(){
        return people;
    }
}

And then let your subclasses define what type should be used. E.g:

public class SubRequest3 extends Request<SubRequest3.Person>{ /* ... */ }

You may have seen that the people list is final, which means it will never be overriden. This attempt is a lot more safer than just accepting any List implementation, because you can ensure that the implementation does what you actually want. E.g. no harmful List can be passed via setPeople.

Like in the answer from @Michael you might aswell want to declare a Default implementation which declares T to just be Person and then let SubRequest1 and SubRequest2 extend that default class.

Upvotes: 1

ernest_k
ernest_k

Reputation: 45339

The subclass cannot override a method and change the return type. There are two possible ways to allow the subclass send a different type of element in the person list.

Both of these suppose that SpecialPerson is a subclass of Person:

1 - Make the Request class generic:

public class Request<P extends Person> {
    private List<P> persons;

    public List<P> getPersons() {
       //...
    }
}

With that, SubRequest3 will be declared as:

public class SubRequest3 extends Request<SubRequest3.Person> {
    private List<SubRequest3.Person> persons;
}

All other SubRequest classes will declare extends Request<Person>

2 - Keep everything at API level as is, but add instances of SubRequest3.Person to a list, rather than trying to return some List<SubRequest3.Person>. This takes advantage of the Person/SubRequest3.Person inheritance, while avoiding the issue of invariance associated with List<Person>/List<SubRequest3.Person>

private List<SubRequest3.Person> persons;

public void setPersons(List<Person> people) { 
    //cast element by element
    this.persons.clear(); //or whatever means of resetting
    people.stream().forEach(p -> persons.add((SubRequest3.Person) p));
}
public List<SubRequest3.Person> getPersons()  { 
    //Can also use a loop and add manually to a `List<Person>` object
    return this.persons.stream().map(p -> (Person) p).collect(Collectors.toList());
}

Upvotes: 1

Michael
Michael

Reputation: 44250

No, it's not possible. You can't override fields, you can only hide them.

The correct way to handle this situation is to declare Person as a common interface. The subclasses of Request can use different implementations of Person, but the calling code probably shouldn't care about the concrete type.

interface Person
{
    //...
}

interface Request<T extends Person>
{
    void setPeople(List<T> person);
    List<T> getPeople();
}

abstract class DefaultRequest implements Request<Person>
{
    private List<Person> persons;

    public void setPeople(List<Person> people) { /* ... */ }
    public List<Person> getPeople()  { /* ... */ }
}

class SubRequest1 extends DefaultRequest 
{
    //...
}

class SubRequest2 extends DefaultRequest 
{
    //...
}

class SubRequest3 implements Request<SpecialPerson>
{
    private List<SpecialPerson> persons;

    public void setPeople(List<SpecialPerson> people) { /* ... */ }
    public List<SpecialPerson> getPeople()  { /* ... */ }
}

Upvotes: 1

Related Questions