I'm using generics to not reuse code and I' running into a lack of understanding for type generics. I have a class Writer (java code from another library).
public class Writer<T>
A class FileWriter (java code from another library)
public class FileWriter<D>{
FileWriter(Writer<D> writer){
this.writer=writer
}
public void append(D datum){
//Does something
}
}
Now I'm initiating this in kotlin:
val writer = FileWriter(Writer(AGenratedJavaClassThatIMplementsSpecificRecord::class.java))
I can now call writer.append with AGenratedJavaClassThatIMplementsSpecificRecord(). It works just fine
I would like to pass this writer to a function.
funDoSomethingExtra(writer: FileWriter<in SpecificRecord>)
This gives me an error that I do not understand.
Type mismatch: inferred type is FileWriter<AGenratedJavaClassThatIMplementsSpecificRecord!>! but FileWriter<in SpecificRecord> was expected
Changing this to
funDoSomethingExtra(writer: FileWriter<out SpecificRecord>)
Makes writers.append give the error
Required Nothing, found AGenratedJavaClassThatIMplementsSpecificRecord.
Without the use of methods, all works fine. Which details Am I missing? It is probably something small,
Kind regards,
Jelmew
This line of your code:
val writer = FileWriter(Writer(AGenratedJavaClassThatIMplementsSpecificRecord::class.java))
does not specify the type of the FileWriter, so it is inferred from your arguments to the constructors, so the type of writer is FileWriter<AGenratedJavaClassThatIMplementsSpecificRecord>.
Your signature funDoSomethingExtra(writer: FileWriter<in SpecificRecord>) is correct for calling a method on the writer that does something to a SpecificRecord or subtype of SpecificRecord. However, your
FileWriter<AGenratedJavaClassThatIMplementsSpecificRecord>
cannot be cast to a FileWriter<in SpecificRecord> because AGenratedJavaClassThatIMplementsSpecificRecord is a subtype of SpecificRecord, not a supertype. The compiler knows your file writer can consume AGenratedJavaClassThatIMplementsSpecificRecord, but it doesn't know it can consume the less-specific type SpecificRecord. There's the possibility of you calling some function of the subtype that doesn't exist in the supertype.
So to be able to pass your writer to this function, it needs to be a FileWriter<SpecificRecord> or FileWriter<in SpecificRecord>. You can't safely cast it after its type is already assigned, but you can assign it the proper type right at the declaration site instead of letting the compiler try to infer it:
val writer: FileWriter<SpecificRecord> = FileWriter(Writer(AGenratedJavaClassThatIMplementsSpecificRecord::class.java))
Related
FYI: I'm currently using Mockk-1.12.4 and Kotlin-1.6
I've got an extension method that returns an object of type T:
fun <T> Entity.selectReferenceAsSingleObject(referenceName: String): T {
return this.selectReferencesByName(referenceName).single().asObjet() as T
}
This is defined as top level function in an Extentions.kt file (so not a class). I'm wanting to mock this with MockK by using mockkStatic
mockkStatic(Entity::selectReferenceAsOptionalSingleObject)
However I'm getting the error:
Not enough information to infer type variable T
because it cannot work out what type I should be mocking. I've tried adding my type parameter in a bunch of places
mockkStatic<KFunction<MyType>>(Entity::selectReferenceAsSingleObject)
mockkStatic<MyType>(Entity::selectReferenceAsSingleObject)
mockkStatic(Entity<KFunction<MyType>>::selectReferenceAsSingleObject)
mockkStatic(Entity<MyType>::selectReferenceAsSingleObject)
mockkStatic(Entity::<KFunction<MyType>>selectReferenceAsSingleObject)
mockkStatic(Entity::<MyType>selectReferenceAsSingleObject)
mockkStatic(Entity::selectReferenceAsSingleObject<KFunction<MyType>>)
mockkStatic(Entity::selectReferenceAsSingleObject<MyType>)
mockkStatic(Entity::selectReferenceAsSingleObject as KFunction<MyType>)
mockkStatic(Entity::selectReferenceAsSingleObject as MyType)
But nothing works and it either tells me the same error or tells me that type arguments are not allowed there. I'm not sure what I need to do to fix my syntax as IntelliJ isn't giving me any hints so I feel kind of stuck. Any help would be appreciated.
Try this:
mockkStatic("your.package.YourFileExtensionKt")
you can also give the file a nice name:
#file:JvmName("Extension")
would look like this:
mockkStatic("your.package.Extension")
anyway for the test to work you must use a mock in the extended class, in this case Entity, it would look like this:
#Test
fun test() {
val entity : Entity = mockk(relaxed = true)
every { entity.selectReferenceAsSingleObject<Any>(any()) } returns "whatever"
val result = entity.selectReferenceAsSingleObject<Any>("test")
assertEquals("whatever", result)
}
I have the following Kotlin code:
fun isObject(type: KClass<*>) = type.objectInstance != null
fun main() {
println(isObject(emptyMap<Int, Int>()::class))
}
which produces the following errror:
Exception in thread "main" java.lang.IllegalAccessException: class kotlin.reflect.jvm.internal.KClassImpl$Data$objectInstance$2 cannot access a member of class kotlin.collections.EmptyMap with modifiers "public static final"
at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:361)
at java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:591)
at java.base/java.lang.reflect.Field.checkAccess(Field.java:1075)
at java.base/java.lang.reflect.Field.get(Field.java:416)
at kotlin.reflect.jvm.internal.KClassImpl$Data$objectInstance$2.invoke(KClassImpl.kt:114)
at kotlin.reflect.jvm.internal.ReflectProperties$LazyVal.invoke(ReflectProperties.java:62)
at kotlin.reflect.jvm.internal.ReflectProperties$Val.getValue(ReflectProperties.java:31)
at kotlin.reflect.jvm.internal.KClassImpl$Data.getObjectInstance(KClassImpl.kt)
at kotlin.reflect.jvm.internal.KClassImpl.getObjectInstance(KClassImpl.kt:239)
I want my isObject function to work for any arbitrary KClass but I don't know how to do it without checking if the object instance is non null. Any suggestions?
If you don't mind some reflection overhead and that it only works with Kotlin/JVM then you can use my library fluid-meta for that:
fun isObject(type: KClass<*>) = Meta.of(type) is MObject
Use version 0.9.16 if you're still on Kotlin 1.3 and the library won't work otherwise.
It uses the hidden Kotlin metadata annotations added to each class generated by Kotlin. If these annotations get stripped in your project at some point (e.g. by an aggressive ProGuard) then you won't have that information at runtime anymore though.
I've a problem using Fuel's responseObject in a generic fashion. I'm trying to develop a centralized method with components getting their HTTP response object already deserialized, ready to go. It looks like this:
class Controller(private val url: String) {
fun <T> call(endpoint: String): T {
return "$url/$endpoint".httpGet().responseObject<T>()
}
}
class App(private val controller: Controller) {
fun getModel() {
val model = controller.call<AppModel>("model")
// use model
}
}
Of course, Controller.call would handle errors, and add common request parameters. The deserialization from JSON is supposed to be handled by Jackson (AppModel is a simple data class Jackson should pick up automatically), so I'm working with fuel-jackson:1.12.0 as an added dependency.
Now, using Kotlin-1.2.21, I get this compiler error:
Error:(35, 97) Kotlin: Cannot use 'T' as reified type parameter. Use a class instead.
How do I work around this, perhaps by switching to a different Fuel method?
I've considered making call inline (to reify T), but this defeats the purpose of having a private val url.
I don't think there's a simple workaround to this problem.
First, there's no way to call a Kotlin inline function with a reified type parameter without either using a concrete type or propagating the type argument through a chain of generic calls to inline functions, so you have to call .httpGet().responseObject<T>() from an inline function and use a reified type parameter as T.
Next, there's a reason for the restrictions on what an inline function can access. Basically, allowing inline functions to access non-public API would sometimes break binary compatibility. This is described in the docs here.
What you can do is, as suggested in the docs, make private val url: String a #PublishedApi internal val and, accordingly, go on with inline fun <reified T> call(...).
If you are worried about url becoming effectively public, you might want to take a look at this Q&A suggesting a workaround with #JvmSynthetic.
I am trying to deserialize a Json string into an object of type OperationResult<String> using Jackson with Kotlin.
I need to construct a type object like so:
val mapper : ObjectMapper = ObjectMapper();
val type : JavaType = mapper.getTypeFactory()
.constructParametricType(*/ class of OperationResult */,,
/* class of String */);
val result : OperationResult<String> = mapper.readValue(
responseString, type);
I've tried the following but they do not work.
val type : JavaType = mapper.getTypeFactory()
.constructParametricType(
javaClass<OperationResult>,
javaClass<String>); // Unresolved javaClass<T>
val type : JavaType = mapper.getTypeFactory()
.constructParametricType(
OperationResult::class,
String::class);
How do I get a java class from the type names?
You need to obtain instance of Class not KClass. To get it you simply use ::class.java instead of ::class.
val type : JavaType = mapper.typeFactory.constructParametricType(OperationResult::class.java, String::class.java)
Kotlin has a few things that become a concern when using Jackson, GSON or other libraries that instantiate Kotlin objects. One, is how do you get the Class, TypeToken, TypeReference or other specialized class that some libraries want to know about. The other is how can they construct classes that do not always have default constructors, or are immutable.
For Jackson, a module was built specifically to cover these cases. It is mentioned in #miensol's answer. He shows an example similar to:
import com.fasterxml.jackson.module.kotlin.* // added for clarity
val operationalResult: OperationalResult<Long> = mapper.readValue(""{"result":"5"}""")
This is actually calling an inline extension function added to ObjectMapper by the Kotlin module, and it uses the inferred type of the result grabbing the reified generics (available to inline functions) to do whatever is needed to tell Jackson about the data type. It creates a Jackson TypeReference behind the scenes for you and passes it along to Jackson. This is the source of the function:
inline fun <reified T: Any> ObjectMapper.readValue(content: String): T = readValue(content, object: TypeReference<T>() {})
You can easily code the same, but the module has a larger number of these helpers to do this work for you. In addition it handles being able to call non-default constructors and static factory methods for you as well. And in Jackson 2.8.+ it also can deal more intelligently with nullability and default method parameters (allowing the values to be missing in the JSON and therefore using the default value). Without the module, you will soon find new errors.
As for your use of mapper.typeFactory.constructParametricType you should use TypeReference instead, it is much easier and follows the same pattern as above.
val myTypeRef = object: TypeReference<SomeOtherClass>() {}
This code creates an anonymous instance of a class (via an object expression) that has a super type of TypeRefrence with your generic class specified. Java reflection can then query this information.
Be careful using Class directly because it erases generic type information, so using SomeOtherClass::class or SomeOtherClass::class.java all lose the generics and should be avoided for things that require knowledge of them.
So even if you can get away with some things without using the Jackson-Kotlin module, you'll soon run into a lot of pain later. Instead of having to mangle your Kotlin this module removes these types of errors and lets you do things more in the "Kotlin way."
The following works as expected:
val type = mapper.typeFactory.constructParametricType(OperationalResult::class.java, String::class.java)
val operationalResult = mapper.readValue<OperationalResult<String>>("""{"result":"stack"}""", type)
println(operationalResult.result) // -> stack
A simpler alternative to deserialize generic types using com.fasterxml.jackson.core.type.TypeReference:
val operationalResult = mapper.readValue<OperationalResult<Double>>("""{"result":"5.5"}""",
object : TypeReference<OperationalResult<Double>>() {})
println(operationalResult.result) // -> 5.5
And with the aid of jackson-kotlin-module you can even write:
val operationalResult = mapper.readValue<OperationalResult<Long>>("""{"result":"5"}""")
println(operationalResult.result)
I'm trying to translate a class from java to kotlin which uses a lot of raw types and accesses the Class of objects.
How can i get this to work:
val item: Any = items[position]
item::class.java // compiler complains about "Unresolved reference: item"
You'll want to call item.javaClass. The reason is that you're not calling on a class literal, you're calling on an actual object instance.