Suppose I have a method "mix" that takes two Lists of possibly different types T and S and returns a single List containing the elements of both. For type-safety, I'd like to specify that the returned List is of a type R, where R is a supertype common to both T and S. For example:
List<Number> foo = mix(
Arrays.asList<Integer>(1, 2, 3),
Arrays.asList<Double>(1.0, 2.0, 3.0)
);
To specify this, I could declare the method as
static <R, T extends R, S extends R> List<R> mix(List<T> ts, List<S> ss)
But what if I want to make mix
an instance method instead of static, on the class List2<T>
?
<R, T extends R, S extends R> List<R> mix ...
shadows the <T>
on the instance of List2
, so that's no good.
<R, T extends S&T, S extends R> List<R> mix ...
solves the shadowing problem, but isn't accepted by the compiler
<R super T, S extends R> List<R> mix ...
is rejected by the compiler because lower-bounded wildcards can't be stored in a named variable (only used in ? super X
expressions)
I could move the arguments to the class itself, like List2<R, T extends R, S extends R>
, but the type information really has no business being on the instance level, because it's only used for one method call, and you would have to re-cast the object every time you wanted to invoke the method on different arguments.
As far as I can tell, there's no way to do this with generics. The best I can do would be to return a raw List2
and cast it at the callsite, like before generics were introduced. Does anybody have a better solution?
Example: Create a Generics Method Here, the type parameter <T> is inserted after the modifier public and before the return type void . We can call the generics method by placing the actual type <String> and <Integer> inside the bracket before the method name. demo. <String>genericMethod("Java Programming"); demo.
Implementing generics into your code can greatly improve its overall quality by preventing unprecedented runtime errors involving data types and typecasting.
Generics also provide type safety (ensuring that an operation is being performed on the right type of data before executing that operation). Hierarchical classifications are allowed by Inheritance. Superclass is a class that is inherited. The subclass is a class that does inherit.
As noted in the question and in the comments, the following signature would be ideal:
<R super T, S extends R> List<R> mix(List<S> otherList)
But of course, R super T
is not allowed by the language (note that polygenelubricants's answer on the linked post is wrong - there are use cases for this syntax, as your question demonstrates).
There's no way to win here - you only have one of several workarounds to choose from:
mix
a static method. This is actually a decent option, unless it needs to be part of your class's interface for polymorphism-related reasons, or you plan for mix
to be such a commonly used method that you think keeping it static is unnacceptable.mix
being overly restrictive, and document that certain unchecked casts will be necessary on the part of the caller. This is similar to what Guava's Optional.or
had to do. From that method's documentation:Note about generics: The signature
public T or(T defaultValue)
is overly restrictive. However, the ideal signature,public <S super T> S or(S)
, is not legal Java. As a result, some sensible operations involving subtypes are compile errors:Optional<Integer> optionalInt = getSomeOptionalInt(); Number value = optionalInt.or(0.5); // error
As a workaround, it is always safe to cast an
Optional<? extends T>
toOptional<T>
. Casting [the aboveOptional
instance] toOptional<Number>
(whereNumber
is the desired output type) solves the problem:Optional<Number> optionalInt = (Optional) getSomeOptionalInt(); Number value = optionalInt.or(0.5); // fine
Unfortunately for you, it's not always safe to cast List2<? extends T>
to List2<T>
. For example, casting a List2<Integer>
to a List2<Number>
could permit a Double
to be added to something that was only supposed to hold Integer
s and lead to unexpected runtime errors. The exception would be if List2
was immutable (like Optional
), but this seems unlikely.
Still, you could get away with such casts if you were careful and documented type-unsafe code with explanations. Assuming mix
had the following signature (and implementation, for fun):
List<T> mix(final List<? extends T> otherList) {
final int totalElements = (size() + otherList.size());
final List<T> result = new ArrayList<>(totalElements);
Iterator<? extends T> itr1 = iterator();
Iterator<? extends T> itr2 = otherList.iterator();
while (result.size() < totalElements) {
final T next = (itr1.hasNext() ? itr1 : itr2).next();
result.add(next);
final Iterator<? extends T> temp = itr1;
itr1 = itr2;
itr2 = temp;
}
return result;
}
Then you might have the following call site:
final List2<Integer> ints = new List2<>(Arrays.asList(1, 2, 3));
final List<Double> doubles = Arrays.asList(1.5, 2.5, 3.5);
final List<Number> mixed;
// type-unsafe code within this scope
{
@SuppressWarnings("unchecked") // okay because intsAsNumbers isn't written to
final List2<Number> intsAsNumbers = (List2<Number>)(List2<?>)ints;
mixed = intsAsNumbers.mix(doubles);
}
System.out.println(mixed); // [1, 1.5, 2, 2.5, 3, 3.5]
Again, a settling for a static mix
is going to be cleaner and have no risk to type-safety. I would make sure to have very good reasons not to keep it that way.
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