Tom Wellbrock
Tom Wellbrock

Reputation: 3042

specify the generic type of method arguments

to extend my departments private Libary I tried to implement a isBetween method. This method is generic and expects a Comparable type T for its minimum value, its maximum value, and for an varargs values. Its purpose is to examine if all values are within the specified range.

 public static void main(String[] args) {
    System.out.print(isBetween(1, 100, 200, 100, 1, 2));
  }


  @SafeVarargs
  public static <T extends Comparable> boolean isBetween(T minimum, T maximum, T... values) {
    for (T value : values) {
      if (!(value.compareTo(minimum) >= 0 && value.compareTo(maximum) <= 0))
        return false;
    }
    return true;
  }

This works just fine. But the type T can contain any Comparable objects. A method call:

System.out.print(isBetween(1, 100, 200, 100, "How awkward", 2));

would also be accepted at compile-time. This is not typesafe at all and can't be accepted.

Two solutions came into my mind:

1. call the method like the following

System.out.print(Class.<Integer>isBetween(1, 100, 200, 100, 1, 2));

2. make one of my method parameters of type U and extend type T to type U

Both "solutions" do not seem to be very elegant. The first one requires to write additional code before you call the method and the second one seems like a hack.

Are there any more elegant ways to solve my problem?

Upvotes: 1

Views: 145

Answers (4)

Alex - GlassEditor.com
Alex - GlassEditor.com

Reputation: 15557

Another way you could do this is to pass in a Comparator instead of requiring T to be Comparable:

public static <T> boolean isBetween(Comparator<? super T> comparator, 
        T minimum, T maximum, T... values) {
    for (T value : values) {
        if (!(comparator.compare(value, minimum) >= 0 && 
                comparator.compare(value, maximum) <= 0)){
            return false;
        }
    }
    return true;
}

This allows you compare the objects however you want, and they don't need to be Comparable.

You could also provide a similar method to the other answers to skip writing the Comparator when T is Comparable:

public static <T extends Comparable<? super T>> boolean isBetween(T minimum, 
        T maximum, T... values) {
    return isBetween(T::compareTo, minimum, maximum, values);
}

Upvotes: 1

Tom
Tom

Reputation: 17597

Well, this can be easily fixed by avoiding raw types.

Let's look at your code:

public static <T extends Comparable> boolean isBetween(T minimum, T maximum, T... values) {
    // ...
}

T is a nice and generic, but Comparable is a raw type. You want Comparable<T>, so your method should look like this:

public static <T extends Comparable<T>> boolean isBetween(T minimum, T maximum, T... values) {
    // ...
}

and then isBetween(1, 100, 200, 100, "How awkward", 2) won't be possible anymore.


The isBetween method allows "equal" argument types if they have a equal super class which implements Comparable.

For example using java.util.Date and java.sql.Date (user fge had this good idea):

isBetween(new java.util.Date(1L), new java.util.Date(4L), new java.sql.Date(5L))

This will result in "false", because the third argument is "greater", than the upper bound. And java.sql.Date is accepted, because it extends java.util.Date and will infer T with it as the "common parent class which implements Comparable".

Upvotes: 3

TouDick
TouDick

Reputation: 1322

Use Comparable < T > instead of raw Comparable. In this way, you will ensure that all instances of type T are of the same type and it will fail on compile.

Upvotes: 0

Jan Chimiak
Jan Chimiak

Reputation: 746

Make it <T extends Comparable<T>>:

@SafeVarargs
public static <T extends Comparable<T>> boolean isBetween(T minimum, T maximum, T... values) {
    for (T value : values) {
        if (!(value.compareTo(minimum) >= 0 && value.compareTo(maximum) <= 0))
            return false;
    }
    return true;
}

this will fail at compile.

Upvotes: 1

Related Questions