I have tried a number of different variations of the code below and cannot find any solution which doesn't rely on an unsafe cast or cause another other compiler warning. I am confident the goal is possible, but maybe not?
To put it simply, the goal is that I have derived types which are related to each other and have invariant relationship, which can be enforced by a generic method.
AlphaTask always returns AlphaTaskResult.
AlphaTask is a concrete implementation of ITask<T>, where T is String.
AlphaTaskResult extends the base class of TaskResult<T>, where again T is String.
Everything checks out until it comes to writing a generic method which take any Task and get back the corresponding TaskResult type.
The error is:
Required type: List<U>
Provided: List<TaskResult<T>>
no instance(s) of type variable(s) exist so that TaskResult<T> conforms to U inference variable T has incompatible bounds: equality constraints: U lower bounds: TaskResult<T>
package com.adobe.panpipe;
import java.util.List;
import java.util.Arrays;
import java.util.stream.Collectors;
interface ITask<T>{
TaskResult<T> make();
}
class TaskResult<T>{
T value;
}
class AlphaTaskResult extends TaskResult<String> {
AlphaTaskResult(String value){
this.value = value;
}
}
class BetaTaskResult extends TaskResult<Integer> {
BetaTaskResult(Integer value){
this.value = value;
}
}
class AlphaTask implements ITask<String> {
public AlphaTaskResult make(){
return new AlphaTaskResult("alphaTask");
}
}
class BetaTask implements ITask<Integer> {
public BetaTaskResult make(){
return new BetaTaskResult(9001);
}
}
public class Main <T>{
public static <T, U extends TaskResult<T>, V extends ITask<T>> List<U> run(List<V> tasks){
List<U> results = tasks
.stream()
.map(ITask::make)
.collect(Collectors.toList());
return results;
}
public static void main(String[] args) {
List<AlphaTaskResult> alphaResults = run(Arrays.asList(new AlphaTask(), new AlphaTask()));
List<BetaTaskResult> betaResults = run(Arrays.asList(new BetaTask(), new BetaTask()));
}
}
There is no connection between ITask and TaskResult. Sure, the task implementations override make with their respective return types, but this information can't be used in the declaration of the run method.
You can make the connection by adding an additional type parameter to ITask:
interface ITask<T, TResult extends TaskResult<T>>{
TResult make();
}
class TaskResult<T>{
T value;
}
Change the task implementations and run accordingly:
class AlphaTask implements ITask<String, AlphaTaskResult> {
public AlphaTaskResult make(){
return new AlphaTaskResult("alphaTask");
}
}
class BetaTask implements ITask<Integer, BetaTaskResult> {
public BetaTaskResult make(){
return new BetaTaskResult(9001);
}
}
public class Main { // Main doesn't need a type parameter
public static <T, U extends TaskResult<T>, V extends ITask<T, U>> List<U> run(List<V> tasks){
List<U> results = tasks
.stream()
.map(ITask::make)
.collect(Collectors.toList());
return results;
}
public static void main(String[] args) {
List<AlphaTaskResult> alphaResults = run(Arrays.asList(new AlphaTask(), new AlphaTask()));
List<BetaTaskResult> betaResults = run(Arrays.asList(new BetaTask(), new BetaTask()));
}
}
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