With reified, I could replace the below function
private fun <T>createFragmentX(fragmentClass: Class<T>, fragmentArgs: Bundle?): Fragment {
try {
val fragment = fragmentClass.newInstance() as Fragment
fragment.arguments = fragmentArgs
return fragment
} catch (exception: Exception) {
throw RuntimeException(exception.message)
}
}
with this
private inline fun <reified T>createFragmentZ(fragmentArgs: Bundle?): Fragment {
try {
val fragment = T::class.java.newInstance() as Fragment
fragment.arguments = fragmentArgs
return fragment
} catch (exception: Exception) {
throw RuntimeException(exception.message)
}
}
It looks to me, with reified there's no longer need of using Class or KClass. Am I right?
Is there other use of Class or KClass that can't be fulfilled by reified, other than wanted to do pure reflection work?
Short answer:
In your example, it does not make a difference, except for aesthetics.
Long answer:
If you pass in T as reified, you can retrieve the KClass from the reified type parameter T like this
val kClass = T::class
at any time. You then have all the options you would have using a KClass as parameter, so no difference here.
There are situations where you need to use KClass as parameter though. Here a two:
1) No inline
When you don't want your function to be inline, because using a reified type works only with inlined functions.
2) Default values
Another situation would be default values:
fun f(kClass: KClass<*> = Int::class) { /*...*/ }
You couldn't achieve exactly the same with a reified type parameter.
Conclusion:
In all other cases go for the generic type parameter, because it makes the call-site more concise as shown here (which may be personal preference):
inline fun <reified T> printType() {
println(T::class.simpleName)
}
fun printType(kClass: KClass<*>) {
println(kClass.simpleName)
}
Call-site
printType<Int>() // better
printType(Int::class)
You don't need to pass it as a parameter here, but your code still uses Class: that's what T::class.java is. So yes, there are many cases where you need Class and yours is one of them.
Related
If I have the following code:
fun main() {
println(Example("test", 1))
}
class Example(private val text: String, private val num: Int) {
override fun toString(): String {
return "String: $text $num"
}
}
and it works.
Is there any way to do this for types different from string?
Example:
class Example {
fun toList(): List {
//Convert to list...
}
}
thisFunctionRequireList(Example2())
I will first explain how
println(Example1())
works, and from there, create something similar.
First, note that println has many overloads. This println call does not call the println(String) overload. It calls the println(Any?) overload.
The println(Any?) overload is implemented to transform the Any parameter into a String using toString, and then calls the println(String) overload using that string. Here is the Kotlin/Native implementation:
public actual fun println(message: Any?) {
println(message.toString())
}
Note that it is able to do this, only because Any?, and by extension Any, declares the toString method. This allows it to convert any value you pass, to a String. At runtime, this gets dispatched to the toString override that you declared in Example1.
Therefore, it is not because you overrode toString, that you were able to call println with Example. It is because
println has an overload that takes Any, allowing you to pass anything into it
Any also declares a toString that you can override, which println(Any) also uses in its implementation
Any does not declare a toList method, so we can't use Any if we want to do the same for List.
We can declare a new interface for types that can be converted to lists:
interface ConvertibleToList<T> {
fun toList(): List<T>
}
This is like an Any, but only for types that implement it. Example2 can then implement the interface:
class Example2: ConvertibleToList<String> {
override fun toList(): List<String> {
//Convert to list...
}
}
Now suppose there is an existing function that takes a List<String>:
fun doThingsToStrings(strings: List<String>) { ... }
You can add a new overload of this, with the parameter type being ConvertibleToList<T>:
fun doThingsToStrings(strings: ConvertibleToList<String>) = doThingsToStrings(strings.toList())
doThingsToStrings becomes just like println, with one overload taking the "exact" type (List<String>), and another overload taking types that can be converted to the "exact" type (ConvertibleToList<String>).
Now you can call doThingsToStrings with Example2()!
I'm trying to create an AnimalFactory that returns generic factories for making different types of Animals, depending on the arguments passed to the AnimalFactory.
Here's the code:
interface Animal {
fun talk(): String
}
class Cow: Animal {
override fun talk(): String {
return "mooo"
}
}
class Cat: Animal {
override fun talk(): String {
return "miow"
}
}
class Dog: Animal {
override fun talk(): String {
return "bark"
}
}
object AnimalFactory {
fun <T: Animal> AnimalMakerFactory(type: String): AnimalMaker<T> {
val maker = when (type) {
"cat" -> CatMaker()
"dog" -> DogMaker()
else -> CowMaker()
}
return maker
}
}
interface AnimalMaker<out T: Animal> {
fun make(): T
}
class CatMaker: AnimalMaker<Cat> {
override fun make(): Cat {
return Cat()
}
}
class DogMaker: AnimalMaker<Dog> {
override fun make(): Dog {
return Dog()
}
}
class CowMaker: AnimalMaker<Cow> {
override fun make(): Cow {
return Cow()
}
}
I get a type exception:
Type mismatch.
Required: AnimalMaker<T>
Found: AnimalMaker<Animal>
I thought that AnimalMaker would solve this, but apparently not. Why is AnimalMaker<T> not of type AnimalMaker<Animal> here?
The return value of the function is AnimalMaker<T> and not AnimalMaker<Animal> because that’s what you declared as the return type. The variable maker is indeed an AnimalMaker<Animal> but that isn’t a match for what the function is supposed to return because T could be a subtype of Animal.
You declared your function as having a generic type of T: Animal. Generic types are always an input to the function. In this case, it doesn’t make sense to use a generic input to the function because there’s no way to enforce that the type given is a match for the input String it corresponds with. To make your function work, you can remove <T : Animal and declare that it returns AnimalMaker<Animal>.
A little more explanation. There are two reasons why you might want to use generics in a function signature.
Enforce input parameter types.
Determine the output type.
You might use generics for one or both reasons (but the second can only be done by itself in a useful way by using reified generics, except in very specific cases where the returned class won’t be producing anything).
In your case, your input generic is not used to enforce the input parameter since that is just a String. To use it for the second reason, you would have to cast your return value’s type to the unknown (to the compiler) type T which would be unsafe because there’s no way to know if the input type given at the call site is a valid match for the given input String. And if you expected the call site to pass the right type, it would be redundant and error prone to also require a matching String to be passed.
Edit:
If you know the input type at compile time, then you can do this with reified generics. Get rid of the String input. It would look like this:
object AnimalFactory {
inline fun <reified T: Animal> AnimalMakerFactory(): AnimalMaker<T> {
#Suppress("UNCHECKED_CAST")
return when (T::class) {
Cat::class -> CatMaker()
Dog::class -> DogMaker()
Cow::class -> CowMaker()
else -> error("No factory found for type ${T::class}.")
} as AnimalMaker<T>
}
}
// Example usage
val someCatFactory = AnimalFactory.AnimalFactoryMaker<Cat>()
val cat: Cat = someCatFactory.make()
Inside this function, it is up to you to match the types up correctly, or there will be a ClassCastException at runtime. It seems logically it should be able to automatically cast them, but the compiler isn't sophisticated enough (yet?).
If I have a following interface:
interface BaseDataRemote<T, in Params> {
fun getData(params: Params? = null): Single<T>
}
Would it be possible have implementation of this interface that does not take Params?
To have effectively something like:
interface BaseDataRemote<T> {
fun getData(): Single<T>
}
Implementation is as follows:
class RemoteSellerDataSource #Inject constructor(
private val sellerApi: SellerApi,
#Named("LANG") private val lang: String
) : BaseDataRemote<SellerEntity, Nothing> {
override fun getData(params: Nothing?): Single<SellerEntity> {
return sellerApi.getSeller(lang).map { it.fromApiEntity() }
}
}
I use Dagger 2 to module to bind this implementation:
#Module
internal interface RemoteModule {
#Binds
#CoreScope
fun bindsSellerRemote(remoteSellerDataSource: RemoteSellerDataSource): BaseDataRemote<SellerEntity, Nothing>
}
I tried using Nothing as second type parameter, but it does not seem to work
(I'm getting required: class or interface without bounds error
Full error message:
RemoteSellerDataSource.java:6: error: unexpected type
public final class RemoteSellerDataSource implements com.bigchangedev.stamps.business.sdk.data.base.data.BaseDataRemote<SellerEntity, ?> {
^
required: class or interface without bounds
found:?
Thanks.
EDIT: the original answer was a pure Kotlin answer because the OP didn't mention Dagger.
Using Nothing is correct and works in pure Kotlin. However, Dagger seems to convert your code to Java, and in doing so it uses wildcards for the generics (which it doesn't like because it wants exact type matches). To avoid this issue, you can try using #JvmSuppressWildcards on your generic type parameters:
class RemoteSellerDataSource #Inject constructor(
private val sellerApi: SellerApi,
#Named("LANG") private val lang: String
) : BaseDataRemote<SellerEntity, #JvmSuppressWildcards Nothing> {
override fun getData(params: Nothing?): Single<SellerEntity> {
return sellerApi.getSeller(lang).map { it.fromApiEntity() }
}
}
Although I'm not sure what will happen in Java with Nothing in that case. I guess this should have the same effect on the Java code as removing the in variance for the second type param in the interface declaration, but without weakening your Kotlin types.
Another workaround would be to use Unit instead of Nothing, which Dagger will most likely convert to Void in this case. This is not great for your types, though.
Original answer:
You can technically already call getData() without arguments thanks to the default value. An implementation that doesn't care about the params argument can simply expect null all the time.
The Kotlin type that only contains null and no other value is technically Nothing?, and since getData is defined with Params? (note the ?) as input, it should be correct to specify Nothing (even without ?) as second type argument. So you should be able to define an implementation like this:
interface BaseDataRemote<T, in Params> {
fun getData(params: Params? = null): Single<T>
}
class ImplementationWithoutParams<T> : BaseDataRemote<T, Nothing> {
override fun getData(params: Nothing?): Single<T> {
// params will always be null here
}
}
To avoid confusion for the users, this implementation may additionally provide a getData() method without arguments at all:
class ImplementationWithoutParams<T> : BaseDataRemote<T, Nothing> {
override fun getData(params: Nothing?): Single<T> = getData()
fun getData(): Single<T> {
TODO("implementation")
}
}
Simply, I want a function like:
fun <T> convert(val foo: String, fooT: KType) : T {
...?
}
For Int, it would return foo.toInt(), for Double, foo.toDouble(), and to some unknown type, just throw an exception. I think it's not so hard to create my own switch statement for the types I expect, but out of curiosity - is there a way already?
Recommended way
Unfortunately, there's no easy generic way because we're not dealing with casts, but method calls. This would be my approach:
fun <T> convert(str: String, type: KType) : T {
val result: Any = when (type.jvmErasure)
{
Long::class -> str.toLong()
Int::class -> str.toInt()
Short::class -> str.toShort()
Byte::class -> str.toByte()
...
else -> throw IllegalArgumentException("'$str' cannot be converted to $type")
}
return result as T // unchecked cast, but we know better than compiler
}
Usage:
#UseExperimental(ExperimentalStdlibApi::class)
fun main() {
val int = convert<Int>("32", typeOf<Int>())
println("converted: $int")
}
Instead of a KType parameter, you could also use a Class<T> and make the function reified, so it can be called as convert<Int>("32") or even "32".toGeneric<Int>().
Hardcore way
While there is no easy way, it is possible to access the type using heavy reflection and relying on implementation details. For this, we can extract the type name from the KType object, find an extension method (in a different class) that matches, and call it using reflection.
We have to use to*OrNull() instead of to*(), because the latter is inline and won't be found by reflection. Also, we need to resort to Java reflection -- at this time, Kotlin reflection throws UnsupportedOperationException for the types involved.
I do not recommend this in productive code, as it's inefficient and can break with future standard library versions, but it's a nice experiment:
fun convert(str: String, type: KType): Any {
val conversionClass = Class.forName("kotlin.text.StringsKt")
// here, the to*OrNull() methods are stored
// we effectively look for static method StringsKt.to*OrNull(String)
val typeName = type.jvmErasure.simpleName
val funcName = "to${typeName}OrNull" // those are not inline
val func = try {
conversionClass.getMethod(funcName, String::class.java) // Java lookup
} catch (e: NoSuchMethodException) {
throw IllegalArgumentException("Type $type is not a valid string conversion target")
}
func.isAccessible = true // make sure we can call it
return func.invoke(null, str) // call it (null -> static method)
?: throw IllegalArgumentException("'$str' cannot be parsed to type $type")
}
I'm trying to write a function that produces map of properties and values for any type
inline fun <reified T : Any> T.propertiesMap() {
for (property in this::class.memberProperties) {
property.get(this)
}
}
i get a compilation error in property.get(this) about
out-projected type [...] prohibits the use of 'public abstract fun get(receiver...
The issue is that this::class produces a KClass<out T> instead of KClass<T> which is what would be needed to use anything of type T in the property.get(...) call. So you can do an unchecked cast to do what you want:
fun <T : Any> T.propertiesMap() {
#Suppress("UNCHECKED_CAST")
for (property in (this::class as KClass<T>).memberProperties) {
property.get(this)
}
}
Which does not require the function to be inline nor reified type parameter. Otherwise you can change your function to use T::class instead of this::class to create a matching KClass<T>.
inline fun <reified T : Any> T.propertiesMap() {
for (property in T::class.memberProperties) {
property.get(this)
}
}
If you use the type you are reifying rather than an instance of that type the variance issue will go away. When you call T::class.memberProperties you get back a Collection<KProperty<T, *>> which is what I believe you want. On the other hand, if you call that on an instance (this) rather than a type, you get back a Collection<KProperty<out T, Any?>>, which is where your out-variance issue comes from.
inline fun <reified T : Any> T.propertiesMap() {
for (property in T::class.memberProperties) {
property.get(this)
}
}
Essentially, you need to do T::class rather than this::class in order to get the right kind of collection back. I've left your code as-is otherwise because I'm not clear on what you want this function to do, but I suspect you could drop the for loop in favor of a map call.