4

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?

Community
  • 1
  • 1
TheLQ
  • 14,830
  • 14
  • 69
  • 107

1 Answers1

3

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)

Community
  • 1
  • 1
Paul Bellora
  • 54,340
  • 18
  • 130
  • 181
  • First, there are many "create" methods: `createUserQuery()`, `createCommentQuery()`, etc. Each needs to create a query that restricts the sortBy values to its specific context. All you've done is made the create method return the generic version, which still lets `createCommentQuery()` accept `UserSortBy.NAME`, a completely invalid value for comments. – TheLQ Jul 15 '13 at 17:10
  • Ideally `createCommentQuery()` would return something like `SomeQuery`, which means sortBy would only accept `sortBy(CommentSortBy.CREATION, someDate)` and `sortBy(CommentSortBy.VOTES, 5L)`, but not `sortBy(UserSortBy.NAME, "Sam")` – TheLQ Jul 15 '13 at 17:14
  • @TheLQ That makes sense, but wouldn't calling `SomeQueryUnsafe., Object>create()` come close to imposing that restriction? (ignoring for the moment the inflexible `M` parameter). – Paul Bellora Jul 15 '13 at 17:24
  • Correct, if I didn't need M then it would be a simple ``. I just loose compile time checking (and IDE autocompletion) of the `min` and `max` parameters. I'm really trying to avoid that though – TheLQ Jul 15 '13 at 17:27
  • @TheLQ I see the issue clearly now, and it's definitely a tricky one. Thanks for your patience with me. – Paul Bellora Jul 15 '13 at 17:30
  • 1
    Wow, that's a pretty ingenious solution, so +1 (and the accept in a day or two if no more answers are posted) from me. This is defiantly something I'll be saving for later. The only reason I can't do it in my situation is the complexity of the sort API I'm working with (the StackExchange API), where some queries that return `Comment`s have different acceptable `sortBy` values. The object graph starts to get really complex with your solution. Thank you though so much for the idea though, I really appreciate it. – TheLQ Jul 15 '13 at 18:53
  • 1
    Most likely I'm going to use an idea I had after I posted the question: move setting `min` and `max` values to the `CommentSortBy` class. This allows some more flexibility (and less generics soup) at the expense of more awkward api. It was something I was trying to avoid but it looks like at this point there is no other option – TheLQ Jul 15 '13 at 18:56
  • @TheLQ Glad I could help. You should definitely post your own solution as an answer if you get a chance. – Paul Bellora Jul 15 '13 at 18:59
  • 1
    and of course the `S` is unnecessary. The `setSort` above can be declared as `public void setSort(SortBy sortBy, M min)` – newacct Jul 18 '13 at 09:03