Aconsejar al funcionario que lo solucione antes: un ERROR mágico en Kotlin (nota para el desarrollo móvil)

Nadie es perfecto, ningún oro es perfecto, incluso si es oficial, hay un ERROR inevitable. La última vez que compartí con ustedes un artículo sobre un pozo profundo que puede traer Kotlin (último recordatorio)

Lo que quiero compartir con ustedes hoy es la
dirección original de un error mágico en Kotlin : https://blog.csdn.net/m0_46962786/article/details/114027622

Prefacio

Este artículo presentará un error en Kotlin a través de escenarios comerciales específicos, desde los más superficiales hasta los más profundos, y le contará la magia del error, y luego lo llevará a encontrar la causa del error y, finalmente, a evitarlo.

recurrencia de errores

En el desarrollo real, a menudo tenemos el problema de deserializar la cadena Json en un objeto. Aquí, usamos Gson para escribir un fragmento de código de deserialización, de la siguiente manera:

fun <T> fromJson(json: String, clazz: Class<T>): T? {
    return try {                                            
        Gson().fromJson(json, clazz)                  
    } catch (ignore: Exception) {                           
        null                                                
    }                                                       
} 

El código anterior solo es aplicable a clases sin genéricos. Para clases con genéricos, como List, tenemos que transformarlo nuevamente, de la siguiente manera:

fun <T> fromJson(json: String, type: Type): T? {
    return try {                                
        return Gson().fromJson(json, type)      
    } catch (e: Exception) {                    
        null                                    
    }                                           
} 

En este punto, podemos usar la clase TypeToken en Gson para lograr cualquier tipo de deserialización, de la siguiente manera:

//1、反序列化User对象
val user: User? = fromJson("{...}}", User::class.java)

//2、反序列化List<User>对象,其它带有泛型的类,皆可用此方法序列化
val type = object : TypeToken<List<User>>() {}.type
val users: List<User>? = fromJson("[{..},{...}]", type)

La escritura anterior está traducida de la gramática de Java, tiene un inconveniente, es decir, la transmisión de genéricos debe implementarse a través de otra clase.

Usamos la clase TypeToken anterior. Creo que esto no es aceptable para muchas personas. Entonces, en Kotlin, apareció una nueva palabra clave reificada (no se presentó aquí, y si no la entiende, puede verificar la información relevante usted mismo) .

Combinando las características de la función en línea de Kotlin, puede obtener directamente el tipo genérico específico dentro del método. Transformemos el método anterior nuevamente, de la siguiente manera:

inline fun <reified T> fromJson(json: String): T? {
    return try {
        return Gson().fromJson(json, T::class.java)
    } catch (e: Exception) {
        null
    }
}

Como puede ver, agregamos la palabra clave en línea antes del método, lo que indica que se trata de una función en línea; luego agregamos la palabra clave reificada delante de la T genérica y eliminamos el parámetro Type innecesario en el método; finalmente pasamos T :: class.java pasa tipos genéricos específicos, que se utilizan de la siguiente manera:

val user = fromJson<User>("{...}}")
val users = fromJson<List<User>>("[{..},{...}]")

Cuando probamos el código anterior con confianza, apareció el problema y la deserialización de la lista falló, de la siguiente manera:

[Error en la transferencia de la imagen del enlace externo. El sitio de origen puede tener un mecanismo de enlace anti-sanguijuelas. Se recomienda guardar la imagen y subirla directamente (img-CbQt9QuJ-1614156224032) (https://upload-images.jianshu.io/ upload_images / 14735202-a9662c9073ad78ea? imageMogr2 / auto-orient / strip% 7CimageView2 / 2 / w / 1240)]

El objeto de la Lista no es Usuario, sino LinkedTreeMap. ¿Qué pasa? ¿Es este el error de Kotlin mencionado en el título? ¡por supuesto no!

Regresamos al método fromJson y vemos que la transferencia interna es el objeto T :: class.java, es decir, el objeto de clase. Si el objeto de clase tiene un tipo genérico, el tipo genérico se borrará durante el tiempo de ejecución, por lo que si es un objeto List, se ejecuta Durante el período, se convierte en un objeto List.class, y Gson deserializará automáticamente el objeto json en un objeto LinkedTreeMap cuando el tipo genérico que recibe no esté claro.

¿Como lidiar con? Fácil de manejar, podemos usar la clase TypeToken para pasar genéricos, y esta vez, solo necesitamos escribir una vez dentro del método, de la siguiente manera:

inline fun <reified T> fromJson(json: String): T? {
    return try {
        //借助TypeToken类获取具体的泛型类型
        val type = object : TypeToken<T>() {}.type
        return Gson().fromJson(json, type)
    } catch (e: Exception) {
        null
    }
}

En este punto, probemos nuevamente el código anterior, de la siguiente manera:

[Error en la transferencia de la imagen del enlace externo. El sitio de origen puede tener un mecanismo de enlace anti-sanguijuela. Se recomienda guardar la imagen y subirla directamente (img-Eg7vmm2h-1614156224038) (https://upload-images.jianshu.io/ upload_images / 14735202-b436d72383738f70? imageMogr2 / auto-orient / strip% 7CimageView2 / 2 / w / 1240)]

Como puede ver, tanto los objetos Usuario como Lista se deserializan con éxito esta vez.

En este punto, algunas personas tendrán preguntas. Después de tanto hablar, ¿qué pasa con los errores de Kotlin? No te preocupes, sigue mirando hacia abajo, el error está a punto de aparecer.

De repente, un día, su líder se acercó para decirle si este método fromJson se puede optimizar. Ahora, cada vez que deserialice la colección List, debe escribir <List <>> después de fromJson. Hay muchos escenarios de este tipo. Es un poco engorroso escribir.

En este momento, tienes 10,000 cosas saltando, pero cálmate y piensa en ello. Lo que dijo el líder no es irrazonable. Si te encuentras con una situación genérica de varios niveles, será más engorroso escribir, como: fromJson < BaseResponse <List>>,

Así que abrió el camino hacia la optimización, desacoplando las clases genéricas de uso común y, finalmente, escribió el siguiente código:

inline fun <reified T> fromJson2List(json: String) = fromJson<List<T>>(json)

Bajo la prueba, ¿eh? Aturdido, las preguntas familiares son las siguientes:

[Error en la transferencia de la imagen del enlace externo. El sitio de origen puede tener un mecanismo de enlace anti-sanguijuela. Se recomienda guardar la imagen y subirla directamente (img-zFwUSlVq-1614156224040) (https://upload-images.jianshu.io/ upload_images / 14735202-dbf3876add7dcb33? imageMogr2 / auto-orient / strip% 7CimageView2 / 2 / w / 1240)]

¿Por qué es esto de nuevo? FromJson2List solo llama al método fromJson, por qué fromJson puede, pero falla fromJson2List, lo cual es desconcertante.

¿Es este el error de Kotlin mencionado en el título? Te lo digo muy responsablemente, sí.

¿Dónde está la magia de los insectos? Continuar mirando hacia abajo

La magia de los bichos

Reorganicemos todo el evento. Primero definimos dos métodos y los colocamos en el archivo Json.kt. El código completo es el siguiente:

@file:JvmName("Json")

package com.example.test

import com.google.gson.Gson
import com.google.gson.reflect.TypeToken

inline fun <reified T> fromJson2List(json: String) = fromJson<List<T>>(json)

inline fun <reified T> fromJson(json: String): T? {
    return try {
        val type = object : TypeToken<T>() {}.type
        return Gson().fromJson(json, type)
    } catch (e: Exception) {
        null
    }
}

Luego cree una nueva clase de usuario, el código completo es el siguiente:

package com.example.bean

class User {
    val name: String? = null
}

Luego cree un nuevo archivo JsonTest.kt, el código de finalización es el siguiente:

@file:JvmName("JsonTest")

package com.example.test

fun main() {
    val user = fromJson<User>("""{"name": "张三"}""")
    val users = fromJson<List<User>>("""[{"name": "张三"},{"name": "李四"}]""")
    val userList = fromJson2List<User>("""[{"name": "张三"},{"name": "李四"}]""")
    print("")
}

Nota: estas 3 clases están bajo el mismo nombre de paquete y en el mismo módulo.

Finalmente, ejecute el método principal y se encontrará el error.

Tenga en cuenta, mucha energía por delante: copiamos el archivo Json.kt al módulo base, de la siguiente manera:

@file:JvmName("Json")

package com.example.base

import com.google.gson.Gson
import com.google.gson.reflect.TypeToken

inline fun <reified T> fromJson2List(json: String) = fromJson<List<T>>(json)

inline fun <reified T> fromJson(json: String): T? {
    return try {
        val type = object : TypeToken<T>() {}.type
        return Gson().fromJson(json, type)
    } catch (e: Exception) {
        null
    }
}

Luego agregamos un método de prueba al archivo Json.kt en el módulo de la aplicación, de la siguiente manera:

fun test() {
    val users = fromJson2List<User>("""[{"name": "张三"},{"name": "李四"}]""")
    val userList = com.example.base.fromJson2List<User>("""[{"name": "张三"},{"name": "李四"}]""")
    print("")
}

Nota: No existe tal método en el archivo Json.kt del módulo base.

En el código anterior, el método fromJson2List en el módulo de la aplicación y el módulo base se ejecutan respectivamente. Adivinemos el resultado esperado de la ejecución del código anterior.

La primera declaración, con el caso anterior, obviamente devolverá el objeto List; ¿qué pasa con la segunda declaración? Lógicamente, el objeto List también debería devolverse. Sin embargo, las cosas son contraproducentes. Echemos un vistazo a la ejecución, como sigue:

[Error en la transferencia de la imagen del enlace externo. El sitio de origen puede tener un mecanismo de enlace anti-sanguijuelas. Se recomienda guardar la imagen y subirla directamente (img-QZ0MeQqQ-1614156224044) (https://upload-images.jianshu.io/ upload_images / 14735202-651f1b934a63a1be? imageMogr2 / auto-orient / strip% 7CimageView2 / 2 / w / 1240)]

Como puede ver, el método fromJson2List en el módulo de la aplicación no pudo deserializar la Lista, pero el método fromJson2List en el módulo base tuvo éxito.

El mismo código, pero el módulo en el que está ubicado es diferente, y el resultado de la ejecución también es diferente ¿Dices que Dios no es mágico?

Echale un vistazo

Conociendo el error y conociendo la magia del error, explorémoslo a continuación. ¿Por qué está sucediendo esto? ¿Donde empezar?

Obviamente, para ver el archivo de código de bytes de la clase Json.kt, primero echemos un vistazo al archivo Json.class en el módulo base, de la siguiente manera:

Nota: Para los siguientes archivos de código de bytes, se eliminará cierta información de anotación para facilitar la visualización.

package com.example.base;

import com.google.gson.reflect.TypeToken;
import java.util.List;

public final class Json {

  public static final class Json$fromJson$type$1 extends TypeToken<T> {}

  public static final class Json$fromJson2List$$inlined$fromJson$1 extends TypeToken<List<? extends T>> {}
}

Como puede ver, los dos métodos en línea en Json.kt, después de ser compilados en archivos de código de bytes, se convierten en dos clases internas estáticas, y ambas heredan la clase TypeToken, que parece estar bien.

Continúe mirando el archivo de código de bytes correspondiente al archivo Json.kt del módulo de la aplicación, de la siguiente manera:

package com.example.test;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.List;

public final class Json {
  public static final void test() {
    List list;
    Object object = null;
    try {
      Type type = (new Json$fromJson2List$$inlined$fromJson$2()).getType();
      list = (List)(new Gson()).fromJson("[{\"name\": \"\"},{\"name\": \"\"}]", type);
    } catch (Exception exception) {
      list = null;
    } 
    (List)list;
    try {
      Type type = (new Json$test$$inlined$fromJson2List$1()).getType();
      object = (new Gson()).fromJson("[{\"name\": \"\"},{\"name\": \"\"}]", type);
    } catch (Exception exception) {}
    (List)object;
    System.out.print("");
  }

  public static final class Json$fromJson$type$1 extends TypeToken<T> {}

  public static final class Json$fromJson2List$$inlined$fromJson$1 extends TypeToken<List<? extends T>> {}

  public static final class Json$fromJson2List$$inlined$fromJson$2 extends TypeToken<List<? extends T>> {}

  public static final class Json$test$$inlined$fromJson2List$1 extends TypeToken<List<? extends User>> {}
}

En el archivo de código de bytes, hay 1 método de prueba + 4 clases internas estáticas; las dos primeras clases internas estáticas son los resultados compilados de los dos métodos en línea en el archivo Json.kt. Esto se puede ignorar.

A continuación, echemos un vistazo al método de prueba. Este método tiene dos procesos de deserialización, la primera vez que llama a la clase interna estática JsonfromJson2List Error de análisis de KaTeX: No se puede usar la función '$' en modo matemático en la posición 16: inlinefromJson $ ̲2 , La clase interna estática Js ... inlinesfromJson2List $ 1 se llama por segunda vez , es decir, la tercera y cuarta clases internas estáticas se llaman respectivamente para obtener tipos genéricos específicos.

Los tipos genéricos declarados por estas dos clases internas estáticas no son los mismos. Son <Lista <? Extiende T >> y <Lista <? Extiende Usuario >>. En este punto, se estima que todos lo entienden. Obviamente el primero time El tipo genérico se borró durante la deserialización, lo que provocó que fallara la deserialización.

En cuanto a por qué el método de confiar en este módulo, cuando la T genérica se combina con la clase específica, la T genérica se borrará. Esto debe responderse en el sitio web oficial de Kotlin. Si conoce el motivo, puede dejar un mensaje en el área de comentarios.

Expandir

Si su proyecto no se basa en Gson, puede personalizar una clase para obtener tipos genéricos específicos, de la siguiente manera:

open class TypeLiteral<T> {
    val type: Type
        get() = (javaClass.genericSuperclass as ParameterizedType).actualTypeArguments[0]
}

//用以下代码替换TypeToken类相关代码即可
val type = object : TypeLiteral<T>() {}.type

Para la combinación de genéricos, también puede usar la clase ParameterizedTypeImpl en la biblioteca RxHttp, el uso es el siguiente:

https://github.com/liujingxing/okhttp-RxHttp

https://github.com/liujingxing/okhttp-RxHttp/blob/master/rxhttp/src/main/java/rxhttp/wrapper/entity/ParameterizedTypeImpl.kt

//得到 List<User> 类型
val type: Type = ParameterizedTypeImpl[List::class.java, User::class.java]

Para obtener información detallada sobre el uso, consulte Alfabetización genérica de Android y Java.

https://juejin.cn/post/6844903828219756552

resumen

Para evitar este problema en la actualidad, simplemente mueva el código relevante al submódulo. Llamar al código del submódulo no tendrá el problema del borrado genérico.

De hecho, descubrí este problema en Kotlin 1.3.x. La última versión siempre ha existido. Durante el período, consulté a Bennyhuo, pero eludí este problema más tarde, por lo que no tenía la seguridad de que este problema se abordará en el en un futuro próximo. Envíela al funcionario de Kotlin, espero solucionarlo lo antes posible.

Pensamientos del autor

Desde que Google anunció el importante concepto de Kotlin-First, los funcionarios han defendido constantemente que Kotlin es el idioma elegido por los desarrolladores de Android.

Sin embargo, todavía hay una pregunta en la mente de muchas personas hasta ahora: Entonces estoy involucrado en el desarrollo de Android, ¿puedo aprender Kotlin en el futuro?

**Mi respuesta es no. ** Java sigue siendo el lenguaje principal para el desarrollo de Android. Además, el lenguaje C también es muy importante (la dirección de desarrollo de audio y video es muy popular).

Mucha gente piensa que las ruedas se pueden usar para el desarrollo de Android, pero las ruedas no son omnipotentes. El mercado actual de talentos de Android está lleno. Si quieres destacar, todavía hay muchas cosas que debes dominar. Si quieres un desarrollo a largo plazo , Espero que puedas. Echa un vistazo a los puntos que se enumeran a continuación.

¡Tomó 298 días, 8 módulos, 3382 páginas, 660,000 palabras y notas sobre el conocimiento básico del desarrollo de Android! (Todo esto está resumido por los grandes basados ​​en sus muchos años de experiencia laboral, y también es el conocimiento que debe dominarse para el desarrollo de Android, espero que sea útil para todos)

Supongo que te gusta

Origin blog.csdn.net/BUGgogogo/article/details/114135899
Recomendado
Clasificación