Is using an upper-bound generic any different from...
# announcements
s
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
Animal
will work with the function call. In which case the second option seems more readable and condensed if they function identically.
s
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
s
^you mean something like
fun <T: Comparable<T> foo() { … }
?
s
E.g
fun <T: Animal> print(animal: T, callback: (T) -> Unit) { … }
does make a difference
👍 1
s
Ah that makes sense as well
s
This will make sure that the
it
parameter of your lambda will have the same sub-type of animal as the argument value for
animal
s
But if there’s only one parameter that uses the type then there’s not really a point to using a generic right?
s
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.
s
So the function from the Kotlin docs on generics (https://kotlinlang.org/docs/reference/generics.html) 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>>) { ... }
c
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
listOf<Comparable<String>>()
is actually not compatible with the first example, since
Comparable<String>
itself does not implement
Comparable<Comparable<String>>
. And this is actually a good thing, since you should not sort a list by casting or wrapping it with a
Comparable
, but instead provide the sort function with a
Comparator
s
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?
c
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
Comparable
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
e
if the type parameter appears in signiture more than twice, probably you'll want to use unless - once or none - cases are useless
r
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.
s
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(R.id.name)
.
s
@streetsofboston that’s a great example usage I hadn’t thought of