Let's say I need a class that is bounded to a generic Comparable
type:
class A<T extends Comparable<T>> {
// this is just an example of usage of T type
List<T> comparables;
int compareSomething(T smth) {
return comparables.get(0).compareTo(smth);
}
}
The class has a method with own generics in signature:
<V> Future<V> submit(Callable<V> task) {
return someExecutorService.submit(task);
}
Now, is there a possibility to restrict the input of the submit
method to accept only Callables
that also implement T
? I first tried this:
<V, W extends T & Callable<V>> Future<V> submit(W task) {
if(compareSomething(task) != 0)
throw new RuntimeException("");
return someExecutorService.submit(task);
}
but found out it's not possible (for reasons described here). Is there any elegant possibility to bypass it?
EDIT: The one ugly possibility I can think about is to split the encapsulation in two different types and pass an object pair in submit
:
class A<T extends Comparable<T>> {
// this is just an example of usage of T type
List<T> comparables;
int compareSomething(T smth) {
return comparables.get(0).compareTo(smth);
}
<V> Future<V> submit(Callable<V> task, T comparable) {
if(compareSomething(comparable) != 0)
throw new RuntimeException("");
return someExecutorService.submit(task);
}
}
The main disadvantage is that the method signature becomes more complicated, also I will need some one-to-one mapping of T
s to Callable
s for some latter code. Maybe one can suggest a pattern that solves it in an appropriate way?..
EDIT, take two: Let me explain briefly what I'm trying to achieve. I'm working on a custom thread pool implementation that is able to perform some kind of special task scheduling. To do so, this service accepts only one special sort of Callable
tasks. Those Callable
s have to implement a custom interface that is similar to the Comparable
one. By comparing pairs of tasks using methods in this interface, the service will:
Future
.The blocking/comparison logic should be provided by the tasks themselves. This way the thread pool class should only define what special kind of Comparable
s the pool object is accepting, and it doesn't care at all what kind of Callable
s they really are and what is their return type.
EDIT, take three: Based on Erick Robertson's answer, it's now possible to prevent submitting of smelly tasks:
public static void test(String[] args) {
A<Valid> scheduler = new A<>();
scheduler.betterSubmit(new Valid()); // applies to method signature
scheduler.betterSubmit(new Forbidden()); // rejected on compile time
scheduler.betterSubmit(new ConformWithValid()); // still appliable because all required interfaces implementations recognised
}
// just a bunch of test classes
private static class Valid implements Comparable<Valid>, Callable<Void> {
@Override
public int compareTo(Valid o) {
return 0;
}
@Override
public Void call() throws Exception {
return null;
}
}
private static class Forbidden implements Comparable<Forbidden>, Callable<Void> {
@Override
public int compareTo(Forbidden o) {
return -1;
}
@Override
public Void call() throws Exception {
return null;
}
}
private static class ConformWithValid implements Comparable<Valid>, Callable<Boolean> {
@Override
public int compareTo(Valid o) {
return 1;
}
@Override
public Boolean call() throws Exception {
return Boolean.FALSE;
}
}
Nice and easy! Hope some day this will help someone in the same situation as mine. :-)
Generic methods have a type parameter (the diamond operator enclosing the type) before the return type of the method declaration. Type parameters can be bounded (we explain bounds later in this article). Generic methods can have different type parameters separated by commas in the method signature.
Whenever you want to restrict the type parameter to subtypes of a particular class you can use the bounded type parameter. If you just specify a type (class) as bounded parameter, only sub types of that particular class are accepted by the current generic class.
Generics is a concept in Java where you can enable a class, interface and, method, accept all (reference) types as parameters. In other words it is the concept which enables the users to choose the reference type that a method, constructor of a class accepts, dynamically.
Which of these types cannot be used to initiate a generic type? Explanation: None.
Use Comparable<T>
to constrain the parameter instead of just T
.
If the only criteria is that the object is Callable
and Comparable
, then you can just put those two interfaces on the argument. That even reserves the option of adding a named class to the parameter if this is required to fill another requirement. You'll need to suppress one warning, but it's a safe suppression because you know that T
extends Comparable<T>
.
public class A<T extends Comparable<T>> {
// this is just an example of usage of T type
List<T> comparables;
ExecutorService someExecutorService = null;
int compareSomething(T smth) {
return this.comparables.get(0).compareTo(smth);
}
<V> Future<V> submit(Callable<V> task) {
return this.someExecutorService.submit(task);
}
@SuppressWarnings("unchecked")
<V, W extends Callable<V> & Comparable<T>> Future<V> betterSubmit(W task) {
if(this.compareSomething((T) task) != 0)
throw new RuntimeException("");
return this.someExecutorService.submit(task);
}
}
I've run into this same problem with another class that I have which references itself as its own generic parameter like this. I don't think it's a clean implementation yet, but I like your example. It provides a nice neat use case which I might be able to pattern some new code after. I generally don't like suppressing warnings, but I don't really mind it since it's only this method which is affected. I hope this will help!
I think Erick Robertson's answer comes closest to a direct solution. I'd like to propose a less direct one (which avoids any unchecked casts). You wrote:
this service accepts only one special sort of
Callable
tasks. ThoseCallable
s have to implement a custom interface that is similar to theComparable
one.
If we actually declare what you described as a type, we come up with this intersection interface:
interface CustomCallable<V> extends Callable<V>, Comparable<CustomCallable<?>> { }
Notice how it implements Comparable<CustomCallable<?>>
, not Comparable<CustomCallable<V>>
. This is because I'm assuming CustomCallable
s of different output types should still be comparable to each other by the pool. Otherwise there'd be no point to having V
as a method type parameter.
Assuming your pool can accept any type of that intersection interface, we can just remove T
entirely and the language limitation is moot:
class A {
List<CustomCallable<?>> customCallables;
int compareSomething(CustomCallable<?> smth) {
return customCallables.get(0).compareTo(smth);
}
<V> Future<V> submit(CustomCallable<V> task) {
if (compareSomething(task) != 0) {
throw new RuntimeException("");
}
return someExecutorService.submit(task);
}
}
If you insist on parameterizing A
on a specific type of custom callable, it gets more complicated. The only way to make it work is to add a "self type" parameter to the intersection interface:
interface CustomCallable<V, SELF> extends Callable<V>, Comparable<CustomCallable<?, ?>> { }
Here, SELF
is intended to represent the type of the implementor itself. Note that there's no way for that to be enforced however. More info about self types and their caveats on my answer here: Is there a way to refer to the current type with a type variable?
With the added self type, it's now possible for A
to declare a T
that submit
then insists be used for the self type of the custom callables it's provided:
class A<T extends CustomCallable<?, ?>> {
List<CustomCallable<?, T>> customCallables;
int compareSomething(CustomCallable<?, T> smth) {
return customCallables.get(0).compareTo(smth);
}
<V> Future<V> submit(CustomCallable<V, T> task) {
if (compareSomething(task) != 0) {
throw new RuntimeException("");
}
return someExecutorService.submit(task);
}
}
Here's an example of a CustomCallable
implementation, which resolves the self type:
class MyCustomCallable<V> implements CustomCallable<V, MyCustomCallable<?>> {
...
}
Similarly to before, notice how the self type is "relaxed" to MyCustomCallable<?>
rather than MyCustomCallable<V>
since different output types are interchangeable by design.
And here's a usage example:
A<MyCustomCallable<?>> pool = new A<>();
MyCustomCallable<String> strCallable = ...;
MyCustomCallable<Integer> intCallable = ...;
Future<String> strFuture = pool.submit(strCallable);
Future<Integer> intFuture = pool.submit(intCallable);
I don't see how you can combine both your generic class parameter T
and the V
in the Callable<V>
method argument, for the reasons listed in the answers to the linked post. However, maybe it would be an option - I cannot estimate how counterintuitive that would be, though -- to change the act of submitting a task to a method being invoked on the task itself? Something along the lines of...
class A<T extends Comparable<T> & Callable<?>> {
public static abstract class Submittable<T extends Submittable<T,V>,V>
implements Comparable<T>, Callable<V> {
// ugly, but unavoidable
@SuppressWarnings("unchecked")
public Future<V> submitTo(A<? super T> scheduler) {
return (Future<V>) scheduler.submit((T) this);
}
}
// this is just an example of usage of T type
List<T> comparables;
int compareSomething(T smth) {
return comparables.get(0).compareTo(smth);
}
// *can* be left public, but that way you enforce a single way
// of submitting tasks, namely via submitTo
private Future<?> submit(T task) {
if(compareSomething(task) != 0)
throw new RuntimeException("");
// the following cast is a save conversion - you could also write it cast-free
// as Callable<?> callable = task; ...submit(callable);
return someExecutorService.submit((Callable<?>) task);
}
}
Now, your tasks all have to inherit A.Submittable
, and then can be submitted to a scheduler instance via submitTo
, returning a Future<V>
for the correct type V
.
Of course, this crucially relies on T
in fact being a selftype of the subclasses of Submittable
. And what's going on in submitTo
is - well, ugly. But it does provide you with the convenience of returning a Future<V>
of the correct type.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With