Why extending an Interface does not make it's method to return more precise types?
For example if we have a class Person
and a class Student
extends Person.
The we may have:
public interface PersonQueryBuilder<T extends Person> {
PersonQueryBuilder<T> withName(String name);
PersonQueryBuil开发者_开发技巧der<T> withAgeBetween(int from, int to);
List<T> getResultList();
}
public interface StudentRepository<T extends Student> extends PersonQueryBuilder<T> {
StudentRepository studying(Course course);
}
So why when I have an StudentRepository, both the withName() and withAgeBetween() methods return a PersonQueryBuilder and not and StudentRepository? This is very annoying. Is there any "elegant" way of solving this problem?
interface StudentRepository<T extends Student> extends PersonQueryBuilder<T> {
StudentRepository<T> studying(Course course);
StudentRepository<T> withName(String name);
StudentRepository<T> withAgeBetween(int from, int to);
}
You can redeclare the methods in the sub-interface. Implementors of StudentRepository
will now be required to return a StudentRepository
for those methods, but the implementation can still be used polymorphically as a PersonQueryBuilder
.
This is kind of funny. I propose the following (ugly?) solution that doesn't require you to redefine the methods in the interface as Mark's answer does:
interface PersonQueryBuilder<T extends Person, U extends PersonQueryBuilder<T,U>> {
U withName(String name);
U withAgeBetween(int from, int to);
}
interface StudentRepository extends PersonQueryBuilder<Student, StudentRepository> {
StudentRepository studying(Course course);
}
Also you're using raw types as results in your current implementations of your with
methods.
For one, because you use the raw type PersonQueryBuilder as return type. T
does not appear in this type, so why should a particular T
matter? A first improvement therefore is:
PersonQueryBuilder<T> withName(String name);
PersonQueryBuilder<T> withAgeBetween(int from, int to);
The next problem stems from that a StudentRepository need not be the only PersonQueryBuilder for Students, as it would be entirely legal to declare:
interface UniversityRepository<T extends Student> extends PersonQueryBuilder<T> { }
So by your logic, should the return type be StudentRepository or UniversityRepository? Or somehow both? The compiler can't know your intention, so must specify what you want, for instance by using a covariant return type:
public interface StudentRepository<T extends Student> extends PersonQueryBuilder<T> {
@Override
StudentRepository<T> withName(String name);
@Override
StudentRepository<T> withAgeBetween(int from, int to);
StudentRepository<T> studying(Course course);
}
The obvious disadvantage of that approach is that you have to repeat all inherited methods. You can get around this with a type parameter for the actual repository:
public interface PersonQueryBuilder<T extends Person, R extends PersonQueryBuilder<T, R>> {
R withName(String name);
R withAgeBetween(int from, int to);
List<T> getResultList();
}
and
public interface StudentRepository<T extends Student, R extends StudentRepository<T, R> extends PersonQueryBuilder<T, R> {
R studying(Course course);
}
精彩评论