I have the question
fun main(args : Array<String>){
val aa = "1"
val bb = aa.toInt() // <----- no problem
println(bb)
var cc = "1"
var dd = cc as Int // <----- exception
println(dd)
}
if I use as then what happens is...
Compiler : Exception in thread "main" java.lang.ClassCastException: class java.lang.String cannot be cast to class java.lang.Integer (java.lang.String and java.lang.Integer are in module java.base of loader 'bootstrap')
at MainKt.main(main.kt:7)
as Int casts something which is already an Int to the type Int.
val x:Any = 5
val xInt = x as Int
.toInt() parses a string that represents an Int.
Related
It seems this should be safe to do?
#Test
fun testCast() {
val storeId : Any = Int.MAX_VALUE
val numericStoreId = if(storeId is String) storeId.toLong() else storeId as Long
}
This yields:
java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.lang.Long
Why is Kotlin not allowing this? Our Kotlin version is 1.6.
The fix is writing val numericStoreId = if(storeId is String) storeId.toLong() else (storeId as Number).toLong() instead, but I don't get why it is required.
You can't do it in Java either. They simply aren't the same types.
What you're trying to do is equivalent to this:
Integer i = 0;
Long l = (Long) i;
I'm trying to parse a not very well designed api's json using Moshi + kotlin. For some reasons it parses numbers like 71 as Double.
The 3rd party api has a list of objects that could either look like:
{"foo":[[1234567000,12]]} // long, int
or
{"foo":[[1234567000,"string",0,2]]} // long, string, int, int
Because of the 3rd party api I have the following kotlin class:
#JsonClass(generateAdapter = true)
class D {
var foo: List<Any> // I use Any because it can be either String or Int or Long
}
and in my code I do something like:
val moshi = Moshi.Builder().build()
val adapter = moshi.adapter(D::class.java)
var D d = adapter.fromJson("{\"foo\":[[1234567000,\"string\",0,2]]}")
var index = d.foo[2]
var value : Long = 0
// here I get an error: ClassCastException: java.lang.Double cannot be cast to java.lang.Long
value = d.foo[index]
but for some reason Moshi converts the integers in the json string into Doubles instead of Int or Long. How could I fix it?
I'm not sure if this is the easiest way but it works:
class AnyAdapter {
#FromJson fun fromJson(str: String): Any {
var any: Any
try {
any = Integer.parseInt(str)
} catch (e: NumberFormatException) {
try {
any = java.lang.Long.parseLong(str)
} catch (e: NumberFormatException) {
try {
any = java.lang.Double.parseDouble(str)
} catch (e: NumberFormatException) {
any = str
}
}
}
return any
}
}
val moshi = Moshi.Builder()
.add(AnyAdapter())
.build()
val adapter = moshi.adapter(D::class.java)
var D d = adapter.fromJson("{\"foo\":[[1234567000,\"string\",0,2.0]]}")
var l : Long = d.foo[0] as Long
var s : String = d.foo[1] as String
var i : Int = d.foo[2] as Int
var dd : Double = d.foo[3] as Double
JSON number type makes no distinction between integer and floating-point
Fundamental idea behind any JSON parsing library is to parse JSON into certain type, if that type has properties of type integer then parsing library will try to convert JSON number type to integer, but you are parsing json to Any, which essentially tells moshi to take a guess as to the type of the Object.
Since JSON doesn't distinguish between integer and floating point fields moshi defaults to Float/Double for numeric fields when parsing to Any.
And the issue here is in the API, it should not return different type values for same query. at the very least there should be an indication as to the type of data. What happens if you receive a string value which actually looks like a number?
I got an error. Like this :
Error 1 : Platform declaration clash: The following declarations have the same JVM signature (getData()Ljava/lang/Object;):
fun (): I defined in typeErasure2
fun getData(): I defined in typeErasure2
Error 2 : Platform declaration clash: The following declarations have the same JVM signature (getData()Ljava/lang/Object;):
fun (): I defined in typeErasure2
fun getData(): I defined in typeErasure2
fun main(args : Array<String>){
var te = typeErasure("Jennie")
println(te.getData())
var te2 = typeErasure2("Sam")
println(te2.getData())
}
class typeErasure<I>(name : I){
private val data : I = name
fun getData() : I = data
}
class typeErasure2<I>(name : I){
val data : I = name // error 1
fun getData() : I = data // error 2
}
when I use the private keyword the program can run, otherwise it will get an error. anyone can explain to me? :)
This has nothing to do with generics. The problem with your code is that
public fun getData(): I
Is an accesor for "data". But when "data" is a public field, then the accesor is redundant. So when you do:
val someValue = myObject.data
Then the compiler cannot tell if it should use the accessor getData() or it should just point to the field directly.
When getData is private, then the compiler clearly knows that it can't use it so then it will point to the field directly.
class typeErasure2<I>(name : I){
val data : I = name
fun getData() : I = data
}
fun main() {
val obj = typeErasure2<Int>(123)
println(obj.data) // <--- Ask yourself, what does this line do exactly?
}
class typeErasure2<I>(name : I){
val data : I = name
private fun getData() : I = data
}
fun main() {
val obj = typeErasure2<Int>(123)
println(obj.data) // <--- Here it is clear
// it is not the "getData" because that one is private,
// so it must be the public field "data" you are pointing to
}
Kotlin's logic of properties differ slightly from Java's fields.
Whenever you declare any variable in class, its getter and setters are automatically generated, and they can be customized with get() or set {} after it.
Declaring getVariable() manually will result in platform clash, as getter is already defined for the field in the variable declaration and you are creating function with the same name as well.
You can use #JvmField annotation to instruct the compiler to not generate any getter or setter for the field.
#JvmField
val data: I = name
fun getData(): I = data
In Kotlin Documentation, there is mentioned about deferred assignment
val a: Int = 1 // immediate assignment
val b = 2 // `Int` type is inferred
val c: Int // Type required when no initializer is provided
c = 3 // **deferred assignment**
What is the meaning of deferred assignment?
This means simply that the variable is initialized not in its declaration but at a later point.
As stated in the Documentation the val c: Int is declared before c is initialized in the following line.
This means you can initialize a non nullable val inside a function some lines after you declared it.
For example like this:
class Hichhiker {
fun foo() {
val firstName: String
val lastName: String
val age: Int
firstName = "Arthur"
lastName = "Dent"
age = 40
}
}
Instead of this:
class Hichhiker {
fun foo() {
val firstName = "Arthur"
val lastName = "Dent"
val age = 40
}
}
So the compiler recognizes the initalization of the non nullable val even if the assignment was deferred by some lines of code.
For something similar on the class level and more often used see lateinit and delegates like lazy()
It simply means that the variable has been initialized and the assignment of a value to it takes place lines of code below the initialization.Meaning the assignment has been postponed for later.
Given the following code in Kotlin:
import com.fasterxml.jackson.module.kotlin.*
data class MyReply<R> (
val results : Array<R>? = null
)
class ErrorClient() {
val JSON = jacksonObjectMapper()
inline fun<reified R> request(): Array<R>? {
val json_in = """{"results": [2]}"""
val res: MyReply<R> = JSON.readValue(json_in)
return res.results
}
fun read(): Array<Int>? {
val res: Array<Int>? = request()
return res
}
}
and the following tests:
import org.junit.Test
class ErrorTest {
val client = ErrorClient()
#Test
fun `direct`() {
val res: Array<Int>? = client.request()
println(res)
}
#Test
fun `indirect`() {
val res : Array<Int>? = client.read()
println(res)
}
}
Short story: The first test passes and the second fails. Why?
Long story: I am experiencing a wrong type inference of the reified parameter R when calling the inline function via the read() class method, but the direct call to request() works. In the indirect case the type is erronously inferred to be java.lang.Object and thus the test fails with
java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;
at ErrorClient.read(Error.kt:17)
at ErrorTest.indirect(ErrorTest.kt:14)
This is not about reified. I test the code with
val res: MyReply<R> = MyReply()
It does not throw any errors. This problem is your JSON.readValue return an Object instead of Integer. Kotlin try to cast it to Integer but it fails.