TheLQ
TheLQ

Reputation: 15008

Specify valid generic types with a parameter

Consider the following classes

public interface SortBy<S> {
}

public class CommentSortBy<S> implements SortBy<S> {
    public static CommentSortBy<Date> CREATION = new CommentSortBy<Date>();
    public static CommentSortBy<Integer> VOTES = new CommentSortBy<Integer>();
}

public class SomeQueryUnsafe {
    public <M, S extends SortBy<M>> void setSort(S sortBy, M min) {
        //Set relevant values
    }
}

This is currently used as:

public SomeQueryUnsafe createCommentQueryUnsafe() {
    return new SomeQueryUnsafe();
}

public void test() {
    createCommentQueryUnsafe().setSort(CommentSortBy.CREATION, new Date());
}

While this works, the problem is that createCommentQueryUnsafe() does not specify limits on sortBy. Users are free to pass UserSortBy.NAME even though that would make no sense in this context

I can't figure out how to do write this though because just adding <B extends SortBy> to the class signature means I loose the ability to restrict the min parameter in the method. I can't use something like <M, S extends B & SortBy<M>> as its a compiler error. Other attempts with wildcard magic just result in significantly more complexity and compiler errors. Moving the sorting to the createCommentQuery() method would mean every single query needs 2 methods, which is a crazy amount of duplicated code

How can I possibly write the generics so createCommentQuery() limits the sortBy parameter to just CommentSortBy while still having min restricted to the S parameter in the SortBy class?

Upvotes: 4

Views: 526

Answers (1)

Paul Bellora
Paul Bellora

Reputation: 55223

This is indeed a tricky issue for the reasons you've pointed out. I tried various approaches but they were all defeated by the generics limitation you cited. Ultimately it seems like you'll need to make some design changes if you want the specified type safety.

Using the inheritance hierarchy of the SortBy implementations for your generic type restrictions seems to have led to this impasse in particular. I tried decoupling that restriction into a new type parameter on SortBy, which stands for the queried object itself, e.g. Comment, User, etc. This is the design I came up with:

static class Comment { }

static class User { }

interface SortBy<T, M> { }

static class CommentSortBy<M> implements SortBy<Comment, M> {

    static final CommentSortBy<Date> CREATION = new CommentSortBy<Date>();
    static final CommentSortBy<Integer> VOTES = new CommentSortBy<Integer>();
}

static class UserSortBy<M> implements SortBy<User, M> {

    static final UserSortBy<String> NAME = new UserSortBy<String>();
}

static class Query<T> {

    public <M> void setSort(SortBy<T, M> sortBy, M min) {
        //Set relevant values
    }
}

public static void main(String[] args) {

    new Query<Comment>().setSort(CommentSortBy.CREATION, new Date());
    new Query<Comment>().setSort(UserSortBy.NAME, "Joe"); //compiler error
}

(ideone)

Upvotes: 3

Related Questions