Is using an upper-bound generic any different from...
# announcements
Is using an upper-bound generic any different from just using that super type directly? E.g.,
Copy code
open class Animal { ... }

// Using upper bound generic
fun <T: Animal> print(animal: T) { ... }

// Using type directly
fun print(animal: Animal) { ... }
It seems in both cases any subclasses of
will work with the function call. In which case the second option seems more readable and condensed if they function identically.
For this particular example, no, there is no difference
But if you had more parameters and you’d need to tie the types together, using a type-parameter vs just the upper-bound (without the type-parameter), there are differences
^you mean something like
fun <T: Comparable<T> foo() { … }
fun <T: Animal> print(animal: T, callback: (T) -> Unit) { … }
does make a difference
👍 1
Ah that makes sense as well
This will make sure that the
parameter of your lambda will have the same sub-type of animal as the argument value for
But if there’s only one parameter that uses the type then there’s not really a point to using a generic right?
Yup, like this, a top function with just one input parameter that is bound to such a generic type, not really a point in using a generic type.
So the function from the Kotlin docs on generics ( has this function:
Copy code
fun <T: Comparable<T>> sort(list: List<T>) { ... }
Which would be identical to:
Copy code
fun <T> sort(list: List<Comparable<T>>) { ... }
The sort example is subtly different. With a type parameter upper-bound, the function can accept a list of any type of object, as long as that object implements the proper interface. But
is actually not compatible with the first example, since
itself does not implement
. And this is actually a good thing, since you should not sort a list by casting or wrapping it with a
, but instead provide the sort function with a
I see. So the second example would cast whatever type is passed to Comparable whereas the first ensures the type IS comparable at compile time?
No, they’d both be compile-time checks. But the lists they operate on are different. You can think of the first as a list of “sortable” objects, of any type. But the second is a list of
which “wrap” sortable objects. And you can’t really get the sorted objects back from the second without casting, which may not be a valid assumption if it is an actual wrapper object instead of being implemented as an interface
if the type parameter appears in signiture more than twice, probably you'll want to use unless - once or none - cases are useless
I tend to use generic to constrain the consistence of types of parameters and/or return value. that is to say, use generic when there're at least two occurrences of them. I read somewhere which regarded this as best practice, but I cant remember.
One occurrence can be very handy as well, if the generic type parameter is used by the return value. This can remove the need for manual downcasting when calling that function (eg
fun <T : View> findViewById(id: Int) : T
and then
val nameField: TextView = findViewById(
@streetsofboston that’s a great example usage I hadn’t thought of