Reputation: 8574
I have a generic type (T: Any?
) that I need constrained in one situation never to be null
:
class Repository<T> { // T may be a non-null or nullable type.
fun getFromFoo(): T {}
fun getFromBar(): T {} // This is never null. Can I mark it as so?
}
val repository = Repository<String>()
val fromFoo: String = repository.getFromFoo() // Returns a non-null String.
val fromBar: String = repository.getFromBar() // How do I make this work?
val repository = Repository<String?>()
val fromFoo: String? = repository.getFromFoo() // Returns a nullable String.
val fromBar: String = repository.getFromBar() // How do I make this return a non-null String?
While I would ideally refactor these into separate types (like FooRepository
and BarRepository
), is there any way to get this type constraint functionality?
Upvotes: 8
Views: 1889
Reputation: 181
You can now do this easily in Kotlin 1.7 with definitely non-nullable types:
class Repository<T> { // T may be a non-null or nullable type.
fun getFromFoo(): T { TODO() }
fun getFromBar(): T & Any { TODO() } // This is never null.
}
Upvotes: 4
Reputation: 23164
What you need is the type T & Any
— an intersection of generic T
and Any
. It represents such subtype of T
that can never contain nulls.
Unfortunately the intersection types and this intersection in particular currently are non-denotable in Kotlin, meaning that you can't declare a return type of a function to be non-nullable subtype of T
.
Upvotes: 7
Reputation: 2294
class Repository<T : Any> { // T may only be a non-null type.
fun getFromFoo(): T? {} // This may return null.
fun getFromBar(): T {} // This is never null.
}
val repository = Repository<String>()
val fromFoo: String? = repository.getFromFoo()
val fromBar: String = repository.getFromBar()
Require a non-null type. You can selectively indicate nullability via T?
vs T
for your methods/properties.
EDIT: Revised solution based on question clarification found in the comments
My impression now is that you want one method to always return a non-null value, even if T is of a nullable type. The short answer is no, there is no way to do this.
You could however, define two generics:
class Repository<T, N : T> {
fun getFromFoo(): T {}
fun getFromBar(): N {}
}
val repository = Repository<String?, String>()
val fromFoo: String? = repository.getFromFoo()
val fromBar: String = repository.getFromBar()
<T, N : T>
is one option, but it doesn't enforce that N
be of a non-nullable type. <T, N : Any>
is another option, this forces N
to be a non-nullable type, but it's not longer forced to extend T
. Pick your poison.
This makes constructing Repository uglier but gives you the convience of it being non-null at all of the call sites.
Upvotes: 4
Reputation: 82087
You should not parametrize with nullable types. You should rather use non-null types and then make the nullability explicit where really needed:
class Repository<out T : Any> {
fun getFromFoo(): T? {
TODO()
}
fun getFromBar(): T {
TODO()
}
}
You say that T
is supposed to be a non-null type (T: Any
), and say that getFromFoo()
might return T?
, which works totally fine.
Upvotes: 1
Reputation: 29884
Refactoring into two separate classes would be a better idea, but since you don't want that you could use two generic types:
class Repository<T, S: Any> {
fun getFromFoo(): T? { /* ... */ }
fun getFromBar(): S { /* ... */ }
}
Upvotes: 0
Reputation: 36302
Do the opposite. Assume your generic type is not nullable:
class Repository<T> {
fun getFromFoo(): T? { ... }
fun getFromBar(): T { ... }
}
val repository = Repository<String>()
val fromFoo: String? = repository.getFromFoo()
val fromBar: String = repository.getFromBar()
Upvotes: 2