探寻Gson解析遇到不存在键值时引发的Kotlin的空指针异常的原因
文章目录
- 一、问题背景
- 二、问题原因
- 三、问题探析
- Kotlin空指针校验
- Gson.fromJson(String json, Class<T> classOfT)
- TypeToken
- Gson.fromJson(JsonReader reader, TypeToken<T> typeOfT)
- TypeAdapter 和 TypeAdapterFactory
- ReflectiveTypeAdapterFactory
- RecordAdapter 和 FieldReflectionAdapter
- 四、解决方法
一、问题背景
在一次开发过程中,由于在 Kotlin
定义的实体类多了一个 json
不存在的 键
时,即使是对象类型是不可空的对象且指定了默认值,使用 Gson
库解析出来的实体对象中的那个变量是null
,导致后面使用的此变量的时候导致出现空指针异常。比如:
实体类对象定义如下
data class Entity(/*** 存在的元素*/val existParam: String,/*** 不存在的元素*/val nonExistParam: String = ""
)
nonExistParam
是 json
结构中不存在的 key
,json
如下
{"existParam" : "exist"
}
使用 Gson
进行解析 json
val jsonEntity = Gson().fromJson(json, Entity::class.java)
println("entity = $jsonEntity")
最后得到的输出为:
entity = Entity(existParam=exist, nonExistParam=null)
此时可以发现,nonExistParam
已经被指定为不可空的String
类型,且使用了默认值 ""
,但解析出来的实体类中nonExistParam=null
,如果此时不注意直接使用 nonExistParam
,可能引发空指针异常。
二、问题原因
此问题的原因是,Gson
在解析实体类的时候会使用反射构造方法创建对象,在通过反射的方式设置对象的值。因此,如果实体类的成员在json
中不存在,则不会有机会被赋值,其会保持一个默认值(对于对象来说即为空)。而在 Kotlin
中,只要在调用实际方法的时候,会触发Kotlin
的空校验,从而抛出空指针异常
,提早发现问题。但是Gson的反射的方式避开了这个空校验,所以成员的值为 null
,直到使用时可能会出现空指针异常
。
三、问题探析
我们需要探寻 Gson
在解析 json
的时候,究竟发生了什么,导致会出现解析出来的对象出现了 null
Kotlin空指针校验
但是,我们知道Kotlin
是对可空非常敏感的,已经指定了成员是不可空的,为什么会把 null
赋值给了不可空成员呢。
我们可以看 Kotlin
的字节码,并反编译成java
源码,可以看到最后由Kotlin
生成的java源码是怎样的。
我们可以得到如下的两个方法。
public Entity(@NotNull String existParam, @NotNull String nonExistParam) {Intrinsics.checkNotNullParameter(existParam, "existParam");Intrinsics.checkNotNullParameter(nonExistParam, "nonExistParam");super();this.existParam = existParam;this.nonExistParam = nonExistParam;
}// $FF: synthetic method
public Entity(String var1, String var2, int var3, DefaultConstructorMarker var4) {if ((var3 & 2) != 0) {var2 = "";}this(var1, var2);
}
第一个即为构造方法,传递了两个参数,且有 Intrinsics.checkNotNullParameter
可空检查。如果这里有空,则会抛出异常。而下一个则是因为对 nonExistParam
的变量设置了默认值生成的构造方法,默认值为 “”
因此,只有正常调用构造方法的时候,才会触发可空的检查。
Gson.fromJson(String json, Class classOfT)
首先,我们使用的方法是 Gson.fromJson(String json, Class<T> classOfT)
,这个方法是传进一个 json
的字符串和实体对象的 Class
类型,随后的返回值就是一个实体对象。方法如下:
public <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException {T object = fromJson(json, TypeToken.get(classOfT));return Primitives.wrap(classOfT).cast(object);
}
我们先看 Primitives.wrap(classOfT).cast(object);
这句的作用,点进去看 Primitives.wrap()
方法:
/*** Returns the corresponding wrapper type of {@code type} if it is a primitive type; otherwise* returns {@code type} itself. Idempotent.** <pre>* wrap(int.class) == Integer.class* wrap(Integer.class) == Integer.class* wrap(String.class) == String.class* </pre>*/
@SuppressWarnings({"unchecked", "MissingBraces"})
public static <T> Class<T> wrap(Class<T> type) {if (type == int.class) return (Class<T>) Integer.class;if (type == float.class) return (Class<T>) Float.class;if (type == byte.class) return (Class<T>) Byte.class;if (type == double.class) return (Class<T>) Double.class;if (type == long.class) return (Class<T>) Long.class;if (type == char.class) return (Class<T>) Character.class;if (type == boolean.class) return (Class<T>) Boolean.class;if (type == short.class) return (Class<T>) Short.class;if (type == void.class) return (Class<T>) Void.class;return type;
}
从代码中可以看出,这个方法的作用就是将基本数据类型转换成包装类,即将 int
转换成 Integer
,将 float
转换成 Float
等。如果非基本数据类,则直接返回类的本身。而随后接的 .cast(object)
则是强制数据类型转换的的Class类接口,即是 (T) object
。
因此最后一句的作用只是用来强制转换对象的,与解析 json
无关。我们回到第一句 T object = fromJson(json, TypeToken.get(classOfT));
,这句代码调用了 Gson.fromJson(String json, TypeToken<T> typeOfT)
,并使用 TypeToken
包装了 class
。
TypeToken
我们先看 TypeToken
的官方文档解释:
Represents a generic type T. Java doesn’t yet provide a way to represent generic types, so this class does. Forces clients to create a subclass of this class which enables retrieval the type information even at runtime.
这是一个代表泛型T
(generic type T
)的类,在 Java
运行时会进行泛型擦除
,因此在运行过程中是无法拿到泛型
的准确类型,因此 TypeToken
被创建出来,可以在运行时创建基于此类的子类并拿到泛型的信息。也即这个类通过包装泛型类,提供了在运行时获取泛型对象的类信息的能力。
Gson.fromJson(JsonReader reader, TypeToken typeOfT)
从 Gson.fromJson(String json, TypeToken<T> typeOfT)
方法开始,层次往下只是将 String 或 其他类型的来源封装成 JsonReader
类,代码如下:
public <T> T fromJson(String json, TypeToken<T> typeOfT) throws JsonSyntaxException {if (json == null) {return null;}StringReader reader = new StringReader(json);return fromJson(reader, typeOfT);
}public <T> T fromJson(Reader json, TypeToken<T> typeOfT)throws JsonIOException, JsonSyntaxException {JsonReader jsonReader = newJsonReader(json);T object = fromJson(jsonReader, typeOfT);assertFullConsumption(object, jsonReader);return object;
}
首先使用 StringReader
包装 json
字符串,随后使用 JsonReader
包装 StringReader
,随后再调用 Gson.fromJson(JsonReader reader, TypeToken<T> typeOfT)
进行解析 json
,得到 <T>
对象。因此我们来看 Gson.fromJson(String json, TypeToken<T> typeOfT)
方法。
public <T> T fromJson(JsonReader reader, TypeToken<T> typeOfT)throws JsonIOException, JsonSyntaxException {boolean isEmpty = true;Strictness oldStrictness = reader.getStrictness();if (this.strictness != null) {reader.setStrictness(this.strictness);} else if (reader.getStrictness() == Strictness.LEGACY_STRICT) {// For backward compatibility change to LENIENT if reader has default strictness LEGACY_STRICTreader.setStrictness(Strictness.LENIENT);}try {JsonToken unused = reader.peek();isEmpty = false;TypeAdapter<T> typeAdapter = getAdapter(typeOfT);return typeAdapter.read(reader);} catch (EOFException e) {/** For compatibility with JSON 1.5 and earlier, we return null for empty* documents instead of throwing.*/if (isEmpty) {return null;}throw new JsonSyntaxException(e);} catch (IllegalStateException e) {throw new JsonSyntaxException(e);} catch (IOException e) {// TODO(inder): Figure out whether it is indeed right to rethrow this as JsonSyntaxExceptionthrow new JsonSyntaxException(e);} catch (AssertionError e) {throw new AssertionError("AssertionError (GSON " + GsonBuildConfig.VERSION + "): " + e.getMessage(), e);} finally {reader.setStrictness(oldStrictness);}
}
这个方法的一开始是将Gson的 Strictness
设置给 JsonReader
。随后再获取 类型的 TypeAdapter
,使用TypeAdapterread.read(JsonReader in)
,进行解析 json
得到实体对象。
TypeAdapter 和 TypeAdapterFactory
TypeAdapter
是一个抽象类,其有两个抽象方法
/*** Writes one JSON value (an array, object, string, number, boolean or null) for {@code value}.** @param value the Java object to write. May be null.*/
public abstract void write(JsonWriter out, T value) throws IOException;/*** Reads one JSON value (an array, object, string, number, boolean or null) and converts it to a* Java object. Returns the converted object.** @return the converted Java object. May be {@code null}.*/
public abstract T read(JsonReader in) throws IOException;
也就是 write()
方法定义如何把 实体对象
转换成 json字符串
的实现,和 read()
方法定义如何把 json字符串
转换成 实体对象
的实现。默认已经有部分实现了 Java
常用类的转换方式,如基础数据类 int,float,boolean等 和 map 、set、list
提供转换方式。
TypeAdapterFactory
是一个接口,只有一个 creat()
的方法
/*** Returns a type adapter for {@code type}, or null if this factory doesn't support {@code type}.*/
<T> TypeAdapter<T> create(Gson gson, TypeToken<T> type);
此接口将支持的类型 type
返回一个 TypeAdapter
,支持的 type
可以是多种类型。如果不支持的话就返回null。因此 TypeAdapterFactory
和 TypeAdapter
互相配合,可以生成解析和生成json的具体实现方法。
通过一个类型获取 TypeAdapter
的 Gson.getAdapter()
方法如下
public <T> TypeAdapter<T> getAdapter(TypeToken<T> type) {Objects.requireNonNull(type, "type must not be null");TypeAdapter<?> cached = typeTokenCache.get(type);if (cached != null) {@SuppressWarnings("unchecked")TypeAdapter<T> adapter = (TypeAdapter<T>) cached;return adapter;}Map<TypeToken<?>, TypeAdapter<?>> threadCalls = threadLocalAdapterResults.get();boolean isInitialAdapterRequest = false;if (threadCalls == null) {threadCalls = new HashMap<>();threadLocalAdapterResults.set(threadCalls);isInitialAdapterRequest = true;} else {// the key and value type parameters always agree@SuppressWarnings("unchecked")TypeAdapter<T> ongoingCall = (TypeAdapter<T>) threadCalls.get(type);if (ongoingCall != null) {return ongoingCall;}}TypeAdapter<T> candidate = null;try {FutureTypeAdapter<T> call = new FutureTypeAdapter<>();threadCalls.put(type, call);for (TypeAdapterFactory factory : factories) {candidate = factory.create(this, type);if (candidate != null) {call.setDelegate(candidate);// Replace future adapter with actual adapterthreadCalls.put(type, candidate);break;}}} finally {if (isInitialAdapterRequest) {threadLocalAdapterResults.remove();}}if (candidate == null) {throw new IllegalArgumentException("GSON (" + GsonBuildConfig.VERSION + ") cannot handle " + type);}if (isInitialAdapterRequest) {/** Publish resolved adapters to all threads* Can only do this for the initial request because cyclic dependency TypeA -> TypeB -> TypeA* would otherwise publish adapter for TypeB which uses not yet resolved adapter for TypeA* See https://github.com/google/gson/issues/625*/typeTokenCache.putAll(threadCalls);}return candidate;
}
首先,从缓存Map 的 typeTokenCache
中取出 TypeAdapter
,如果有的话,则直接返回此 TypeAdapter
进行使用。
Objects.requireNonNull(type, "type must not be null");
TypeAdapter<?> cached = typeTokenCache.get(type);
if (cached != null) {@SuppressWarnings("unchecked")TypeAdapter<T> adapter = (TypeAdapter<T>) cached;return adapter;
}
随后从 ThreadLocal
中去取出 TypeAdapter
,如果有的话,则直接返回此 TypeAdapter
进行使用。如果没有当前线程的 threadCalls
Map
,则直接创建新的threadCalls
。
Map<TypeToken<?>, TypeAdapter<?>> threadCalls = threadLocalAdapterResults.get();
boolean isInitialAdapterRequest = false;
if (threadCalls == null) {threadCalls = new HashMap<>();threadLocalAdapterResults.set(threadCalls);isInitialAdapterRequest = true;
} else {// the key and value type parameters always agree@SuppressWarnings("unchecked")TypeAdapter<T> ongoingCall = (TypeAdapter<T>) threadCalls.get(type);if (ongoingCall != null) {return ongoingCall;}
}
随后遍历 Gson
对象的 TypeAdapterFactory
List
,如果是适合的对象,即通过 TypeAdapterFactory.create()
方法可以创建 TypeAdapter
,则直接返回此对象。如果找不到,则会抛出异常。
TypeAdapter<T> candidate = null;
try {FutureTypeAdapter<T> call = new FutureTypeAdapter<>();threadCalls.put(type, call);for (TypeAdapterFactory factory : factories) {candidate = factory.create(this, type);if (candidate != null) {call.setDelegate(candidate);// Replace future adapter with actual adapterthreadCalls.put(type, candidate);break;}}
} finally {if (isInitialAdapterRequest) {threadLocalAdapterResults.remove();}
}if (candidate == null) {throw new IllegalArgumentException("GSON (" + GsonBuildConfig.VERSION + ") cannot handle " + type);
}
因此需要去研究不同类型的 TypeAdapter
的做了什么。
ReflectiveTypeAdapterFactory
在 Gson
的构造方法中,会将支持的 TypeAdapterFactory
添加进 Gson 类的 fatories
中,有以下语句:
List<TypeAdapterFactory> factories = new ArrayList<>();// built-in type adapters that cannot be overridden
factories.add(TypeAdapters.JSON_ELEMENT_FACTORY);
factories.add(ObjectTypeAdapter.getFactory(objectToNumberStrategy));// the excluder must precede all adapters that handle user-defined types
factories.add(excluder);// users' type adapters
factories.addAll(factoriesToBeAdded);// type adapters for basic platform types
factories.add(TypeAdapters.STRING_FACTORY);
factories.add(TypeAdapters.INTEGER_FACTORY);
factories.add(TypeAdapters.BOOLEAN_FACTORY);
factories.add(TypeAdapters.BYTE_FACTORY);
factories.add(TypeAdapters.SHORT_FACTORY);
TypeAdapter<Number> longAdapter = longAdapter(longSerializationPolicy);
factories.add(TypeAdapters.newFactory(long.class, Long.class, longAdapter));
factories.add(TypeAdapters.newFactory(double.class, Double.class, doubleAdapter(serializeSpecialFloatingPointValues)));
factories.add(TypeAdapters.newFactory(float.class, Float.class, floatAdapter(serializeSpecialFloatingPointValues)));
factories.add(NumberTypeAdapter.getFactory(numberToNumberStrategy));
factories.add(TypeAdapters.ATOMIC_INTEGER_FACTORY);
factories.add(TypeAdapters.ATOMIC_BOOLEAN_FACTORY);
factories.add(TypeAdapters.newFactory(AtomicLong.class, atomicLongAdapter(longAdapter)));
factories.add(TypeAdapters.newFactory(AtomicLongArray.class, atomicLongArrayAdapter(longAdapter)));
factories.add(TypeAdapters.ATOMIC_INTEGER_ARRAY_FACTORY);
factories.add(TypeAdapters.CHARACTER_FACTORY);
factories.add(TypeAdapters.STRING_BUILDER_FACTORY);
factories.add(TypeAdapters.STRING_BUFFER_FACTORY);
factories.add(TypeAdapters.newFactory(BigDecimal.class, TypeAdapters.BIG_DECIMAL));
factories.add(TypeAdapters.newFactory(BigInteger.class, TypeAdapters.BIG_INTEGER));
// Add adapter for LazilyParsedNumber because user can obtain it from Gson and then try to
// serialize it again
factories.add(TypeAdapters.newFactory(LazilyParsedNumber.class, TypeAdapters.LAZILY_PARSED_NUMBER));
factories.add(TypeAdapters.URL_FACTORY);
factories.add(TypeAdapters.URI_FACTORY);
factories.add(TypeAdapters.UUID_FACTORY);
factories.add(TypeAdapters.CURRENCY_FACTORY);
factories.add(TypeAdapters.LOCALE_FACTORY);
factories.add(TypeAdapters.INET_ADDRESS_FACTORY);
factories.add(TypeAdapters.BIT_SET_FACTORY);
factories.add(DefaultDateTypeAdapter.DEFAULT_STYLE_FACTORY);
factories.add(TypeAdapters.CALENDAR_FACTORY);if (SqlTypesSupport.SUPPORTS_SQL_TYPES) {
factories.add(SqlTypesSupport.TIME_FACTORY);
factories.add(SqlTypesSupport.DATE_FACTORY);
factories.add(SqlTypesSupport.TIMESTAMP_FACTORY);
}factories.add(ArrayTypeAdapter.FACTORY);
factories.add(TypeAdapters.CLASS_FACTORY);// type adapters for composite and user-defined types
factories.add(new CollectionTypeAdapterFactory(constructorConstructor));
factories.add(new MapTypeAdapterFactory(constructorConstructor, complexMapKeySerialization));
this.jsonAdapterFactory = new JsonAdapterAnnotationTypeAdapterFactory(constructorConstructor);
factories.add(jsonAdapterFactory);
factories.add(TypeAdapters.ENUM_FACTORY);
factories.add(new ReflectiveTypeAdapterFactory(constructorConstructor,fieldNamingStrategy,excluder,jsonAdapterFactory,reflectionFilters));this.factories = Collections.unmodifiableList(factories);
首先我们根据这个列表顺序,结合 for (TypeAdapterFactory factory : factories)
分析得到,对于自己定义的实体类,使用的 TypeAdapterFactory
为 ReflectiveTypeAdapterFactory
,即是反射型的 TypeAdapterFactory
。
我们先来看 ReflectiveTypeAdapterFactory.create()
方法创建 TypeAdapter
,这段代码的作用是根据 class的类型生成不同的TypeAdapter
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {Class<? super T> raw = type.getRawType();if (!Object.class.isAssignableFrom(raw)) {return null; // it's a primitive!}// Don't allow using reflection on anonymous and local classes because synthetic fields for// captured enclosing values make this unreliableif (ReflectionHelper.isAnonymousOrNonStaticLocal(raw)) {// This adapter just serializes and deserializes null, ignoring the actual values// This is done for backward compatibility; troubleshooting-wise it might be better to throw// exceptionsreturn new TypeAdapter<T>() {@Overridepublic T read(JsonReader in) throws IOException {in.skipValue();return null;}@Overridepublic void write(JsonWriter out, T value) throws IOException {out.nullValue();}@Overridepublic String toString() {return "AnonymousOrNonStaticLocalClassAdapter";}};}FilterResult filterResult =ReflectionAccessFilterHelper.getFilterResult(reflectionFilters, raw);if (filterResult == FilterResult.BLOCK_ALL) {throw new JsonIOException("ReflectionAccessFilter does not permit using reflection for "+ raw+ ". Register a TypeAdapter for this type or adjust the access filter.");}boolean blockInaccessible = filterResult == FilterResult.BLOCK_INACCESSIBLE;// If the type is actually a Java Record, we need to use the RecordAdapter instead. This will// always be false on JVMs that do not support records.if (ReflectionHelper.isRecord(raw)) {@SuppressWarnings("unchecked")TypeAdapter<T> adapter =(TypeAdapter<T>)new RecordAdapter<>(raw, getBoundFields(gson, type, raw, blockInaccessible, true), blockInaccessible);return adapter;}ObjectConstructor<T> constructor = constructorConstructor.get(type);return new FieldReflectionAdapter<>(constructor, getBoundFields(gson, type, raw, blockInaccessible, false));
}
首先,对于 私有类 、 匿名内部类 、非静态内部类是不支持生成json的,此时会返回 null
的 TypeAdapter
或者 不生成 json
的 TypeAdapter
Class<? super T> raw = type.getRawType();if (!Object.class.isAssignableFrom(raw)) {return null; // it's a primitive!
}// Don't allow using reflection on anonymous and local classes because synthetic fields for
// captured enclosing values make this unreliable
if (ReflectionHelper.isAnonymousOrNonStaticLocal(raw)) {// This adapter just serializes and deserializes null, ignoring the actual values// This is done for backward compatibility; troubleshooting-wise it might be better to throw// exceptionsreturn new TypeAdapter<T>() {@Overridepublic T read(JsonReader in) throws IOException {in.skipValue();return null;}@Overridepublic void write(JsonWriter out, T value) throws IOException {out.nullValue();}@Overridepublic String toString() {return "AnonymousOrNonStaticLocalClassAdapter";}};
}
如果是 Java 14
之后 Record
类,则使用 RecordAdapter
的 TypeAdapter
// If the type is actually a Java Record, we need to use the RecordAdapter instead. This will
// always be false on JVMs that do not support records.
if (ReflectionHelper.isRecord(raw)) {@SuppressWarnings("unchecked")TypeAdapter<T> adapter =(TypeAdapter<T>)new RecordAdapter<>(raw, getBoundFields(gson, type, raw, blockInaccessible, true), blockInaccessible);return adapter;
}
而如果是普通的类型,则使用 FieldReflectionAdapter
的 TypeAdapter
ObjectConstructor<T> constructor = constructorConstructor.get(type);
return new FieldReflectionAdapter<>(constructor, getBoundFields(gson, type, raw, blockInaccessible, false));
RecordAdapter 和 FieldReflectionAdapter
RecordAdapter
和 FieldReflectionAdapter
都是 Adapter
的子类,其都没有覆写 write
和 read
的方法,因此我们直接看 Adapter
的的 read
方法。
@Override
public T read(JsonReader in) throws IOException {if (in.peek() == JsonToken.NULL) {in.nextNull();return null;}A accumulator = createAccumulator();Map<String, BoundField> deserializedFields = fieldsData.deserializedFields;try {in.beginObject();while (in.hasNext()) {String name = in.nextName();BoundField field = deserializedFields.get(name);if (field == null) {in.skipValue();} else {readField(accumulator, in, field);}}} catch (IllegalStateException e) {throw new JsonSyntaxException(e);} catch (IllegalAccessException e) {throw ReflectionHelper.createExceptionForUnexpectedIllegalAccess(e);}in.endObject();return finalize(accumulator);
}
首先,会通过 A accumulator = createAccumulator();
方法获取到一个指定类型的对象,从方法中可以看到,其实是调用 constructor.construct();
反射调用构造方法生成指定类型的对象。
// FieldReflectionAdapter.java
@Override
T createAccumulator() {return constructor.construct();
}// RecordAdapter.java
@Override
Object[] createAccumulator() {return constructorArgsDefaults.clone();
}
在初始化的时候,会先调用 getBoundFields()
方法,通过反射的方式,获取指定类型已经声明了的成员。因此通过get
方法,去判断 json
的 key
是否存在,
BoundField field = deserializedFields.get(name);
if (field == null) {in.skipValue();
} else {readField(accumulator, in, field);
}
可以看 FieldReflectionAdapter
的 readField
方法 (Kotlin
对象未使用Recond
)
@Override
void readField(T accumulator, JsonReader in, BoundField field)throws IllegalAccessException, IOException {field.readIntoField(in, accumulator);
}
继续往下看 BoundField.readIntoField()
@Override
void readIntoField(JsonReader reader, Object target)throws IOException, IllegalAccessException {Object fieldValue = typeAdapter.read(reader);if (fieldValue != null || !isPrimitive) {if (blockInaccessible) {checkAccessible(target, field);} else if (isStaticFinalField) {// Reflection does not permit setting value of `static final` field, even after calling// `setAccessible`// Handle this here to avoid causing IllegalAccessException when calling `Field.set`String fieldDescription = ReflectionHelper.getAccessibleObjectDescription(field, false);throw new JsonIOException("Cannot set value of 'static final' " + fieldDescription);}field.set(target, fieldValue);}
}
最后是通过反射的方式,field.set(target, fieldValue);
将 json
中的 value
设置到指定对象中具体的成员中。
因此,如果实体类的成员在json
中不存在,则不会有机会被赋值,其会保持一个默认值(对于对象来说即为空)
四、解决方法
从 Gson
解析 json
的源码中可以得出,由于使用了反射的方式,所以最后生成对象中可能会出现null
,尤其是实体类中存在 json
没有的 key
,或者虽然 key
存在时但 value
就是null。因此,在设计json
的实体类的时候,需要考虑成员是可空的情况,尽量使用可空类型,避免出现空指针异常。或者使用kotlinx.serialization
进行Kotlin JSON序列化
,保证数据的可空安全性。
相关文章:
探寻Gson解析遇到不存在键值时引发的Kotlin的空指针异常的原因
文章目录 一、问题背景二、问题原因三、问题探析Kotlin空指针校验Gson.fromJson(String json, Class<T> classOfT)TypeTokenGson.fromJson(JsonReader reader, TypeToken<T> typeOfT)TypeAdapter 和 TypeAdapterFactoryReflectiveTypeAdapterFactoryRecordAdapter …...
面试算法高频08-动态规划-01
动态规划 递归知识要点 递归代码模板:提供递归代码的标准形式public void recur(int level, int param) ,包含终止条件(if (level> MAX_LEVEL))、当前层逻辑处理(process(level, param))、向下一层递归…...
【AI】以Llama模型为例学习如何进行LLM模型微调
以Llama模型为例学习如何进行LLM模型微调 推荐超级课程: 本地离线DeepSeek AI方案部署实战教程【完全版】Docker快速入门到精通Kubernetes入门到大师通关课AWS云服务快速入门实战目录 以Llama模型为例学习如何进行LLM模型微调背景预训练微调全部微调参数高效微调低秩适配 (LoR…...
细说STM32单片机FreeRTOS任务管理API函数vTaskList()的使用方法
目录 一、函数vTaskList() 1、 函数说明 2、返回的字符串表格说明 3、函数的使用方法 二、 vTaskList()的应用示例 1、示例功能、项目设置 2、软件设计 (1)main.c (2)freertos.c (3)FreeRTOSConf…...
ffmpeg 添加 nvenc支持
运行以下命令检查当前 FFmpeg 是否支持 hevc_nvenc: ffmpeg -hide_banner -encoders | grep nvenc 若输出包含 hevc_nvenc,说明编码器已集成,问题出在驱动或参数配置若无输出,则需要手动编译 ffmpeg 安装显卡驱动、cuda和cudnn…...
锚定效应的应用-独立站优化价格打折显示-《认知偏差手册》
锚定效应的应用-独立站优化价格打折显示-《认知偏差手册》 先看结果:价格展示 https://atemplate.com/pricing 旧的打折价格展示 新的打折价格展示 锚定效应是什么? 人类在进行决策时,会过度偏重先前取得的资讯(这称为锚点&…...
红宝书第四十九讲:XSS/CSRF攻击防御策略解析
红宝书第四十九讲:XSS/CSRF攻击防御策略解析 资料取自《JavaScript高级程序设计(第5版)》。 查看总目录:红宝书学习大纲 XSS(跨站脚本):黑客把恶意代码塞进网页,当你打开页面时&am…...
Unity基于屏幕空间的鼠标拖动,拖动物体旋转
代码的核心在于,鼠标的屏幕偏移映射到物体的旋转角度,代码中是使用射线去检测的,检测帧间隔鼠标的位置对应物体上的旋转 未解决的问题:旋转都是相对的,怎么去处理,鼠标拖动物体,物体不动&#…...
Unity3D 测试驱动开发(TDD)框架设计
前言 针对Unity3D测试驱动开发(TDD)框架的设计,需要结合Unity引擎特性与TDD核心原则,构建可维护、高效且与开发流程深度集成的测试体系。以下是分层次的框架设计方案: 对惹,这里有一个游戏开发交流小组&a…...
Google Mock(GMock):C++单元测试的高效模拟框架详解
标题: Google Mock(GMock):C单元测试的高效模拟框架详解 摘要: Google Mock(GMock)是C单元测试中的核心工具,能够高效隔离外部依赖并验证复杂交互逻辑。本文详细介绍了GMock的核心…...
智慧城市气象中台架构:多源天气API网关聚合方案
在开发与天气相关的应用时,获取准确的天气信息是一个关键需求。万维易源提供的“天气预报查询”API为开发者提供了一个高效、便捷的工具,可以通过简单的接口调用查询全国范围内的天气信息。本文将详细介绍如何使用该API,以及其核心功能和调用…...
vue3项目启动bug
项目场景: vue3 项目启动运行 问题描述 终端无法正常启动运行 C:/user/adminC:/user/admin> npm run dev > student_status_vue30.0.0 dev > vite原因分析: 暂无 解决方案: 在当前项目目录下运行: npx vite --host…...
逻辑回归 (Logistic Regression)
文章目录 逻辑回归 (Logistic Regression)问题的引出Sigmoid function逻辑回归的解释决策边界 (Decision boundary)逻辑回归的代价函数机器学习中代价函数的设计1. 代价函数的来源(1)从概率模型推导而来(统计学习视角)(…...
SLAM | 激光SLAM中的退化问题
在激光SLAM中,判断退化环境的核心是通过数学建模分析环境特征对位姿估计的约束能力。除了LOAM中提出的退化因子D外,还存在多种基于表达式和阈值设定的方法。以下是几种典型方法及其实现原理: 1. 协方差矩阵特征值分析 原理:通过分析点云协方差矩阵的特征值分布,判断环境中…...
【已更新】2025华中杯B题数学建模网络挑战赛思路代码文章教学:校园共享单车的调度与维护问题
完整内容请看文末最后的推广群 先展示问题一代码和结果、再给出四个问题详细的模型 import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns from matplotlib.font_manager import FontPropertiesfrom matplotlib import rcParams# 设…...
[特殊字符] 基于大模型的地理领域文档中英互译自动化方案
一、📌 项目背景与挑战 在全球化商业环境中,跨国企业经常面临专业文档翻译的痛点: 传统方式效率低下:专业文档翻译需要专人耗时数小时甚至数天 专业术语准确性难保证:地理领域术语的特殊性 格式保持困难:…...
破局遗留系统!AI自动化重构:从静态方法到Spring Bean注入实战
在当今快速发展的软件行业中,许多企业都面临着 Java 遗留系统的维护和升级难题。这些老旧系统往往采用了大量静态方法,随着业务的不断发展,其局限性日益凸显。而飞算 JavaAI 作为一款强大的 AI 工具,为 Java 遗留系统的重构提供了全新的解决方案,能够实现从静态方法到 Spring B…...
高度图(Heightmap)
高度图的数学组成与建模方法 高度图(Heightmap)是一种基于规则网格的地形表示方法,其数学本质是将三维地形简化为二维离散函数,通过高度值的存储和插值实现地形重建。以下从数学建模角度系统阐述其组成原理及关键技术。 一、基础…...
2025第十七届“华中杯”大学生数学建模挑战赛题目B 题 校园共享单车的调度与维护问题完整思路 模型 代码 结果分享
共享单车目前已成为不少大学校园内学生的重要通勤工具,给学生的出行带来了极大便利,但同时也产生了一些问题,如共享单车投放点位设计不合理,高峰期运力不足等。 某高校委托一公司在校园内投放了一批共享单车,经过一段时…...
ESP32-idf学习(一)搭建环境和点灯
一、前言 先说一下查到的数据(不保证准确): 1、连续四年Wi-Fi MCU全球市场份额第一,产品应用于智能家居、工业自动化、医疗健康等泛IoT领域,2024 年营收突破 20 亿元(同比 40%),…...
超详细VMware虚拟机扩容磁盘容量-无坑版
1.环境: 虚拟机:VMware Workstation 17 Pro-17.5.2 Linux系统:Ubuntu 22.04 LTS 2.硬盘容量 虚拟机当前硬盘容量180G -> 扩展至 300G 3.操作步骤 (1)在虚拟机关机的状态下,虚拟机硬盘扩容之前必…...
多线程(进阶续~)(内涵面试题)
目录 一、JUC 的常见类 1. Callable 接口 2. ReentrantLock ReentrantLock 的用法: ReentrantLock 和 synchronized 的区别: 何时使用何锁: 3. 原子类 4. 线程池 ExecutorService 和 Executors ThreadPoolExecutor 5. 信号量 Semaphore 6. C…...
OpenGL shader开发实战学习笔记:第十一章 立方体贴图和天空盒
1. 立方体贴图和天空盒 1.1. 什么是立方体贴图 立方体贴图(Cube Map)是一种纹理,它由六个纹理图像组成,每个纹理图像对应一个方向。这些方向通常是立方体的六个面,分别是“前面”,“后面”,“…...
双指针算法(二)
目录 一、力扣611——有效三角形的个数 二、牛客网3734——和为S的两个数字 三、力扣15——三数之和 四、力扣18——四数之和 一、力扣611——有效三角形的个数 题目如下: 这里我们先认识如何判断是个三角形,ab>c,ac>b,bc>a即为三角形 这里…...
docker Windows 存放位置
docker Windows 存放位置 镜像文件层可能是这 docker的overlay2中存的都是什么and如何清理/var/lib/docker/overlay2_docker overlay 是什么目录-CSDN博客 存的是我们的镜像文件和容器内的文件 \\wsl.localhost\docker-desktop\mnt\docker-desktop-disk\data\docker\overla…...
每日一题(小白)暴力娱乐篇31
首先分析一下题意,需要求出2024的因子,因为我们要求与2024互质的数字,为什么呢?因为我们要求互质说直白点就是我和你两个人没有中间人,我们是自然而然认识的,那我们怎么认识呢,就是直接见面对吧…...
FastAPI与SQLAlchemy数据库集成
title: FastAPI与SQLAlchemy数据库集成 date: 2025/04/17 15:33:34 updated: 2025/04/17 15:33:34 author: cmdragon excerpt: FastAPI与SQLAlchemy的集成通过创建虚拟环境、安装依赖、配置数据库连接、定义数据模型和实现路由来完成。核心模块包括数据库引擎、会话工厂和声…...
SQL刷题记录贴
1.题目:现在运营想要对用户的年龄分布开展分析,在分析时想要剔除没有获取到年龄的用户,请你取出所有年龄值不为空的用户的设备ID,性别,年龄,学校的信息。 错误:select device_id,gender,age,un…...
消息队列实际结点数与计数器不一致问题分析
问题描述 协议栈 PDCP线程任根据外部消息,维护一个链表式的PDCP PDU消息队列,以及一个变量count来记录消息队列中结点数。 当收到 从NG接口业务数据时,PDCP线程会向PDCP PDU消息队列中添加大量节点,消息队列的count值相应的增加…...
AI预测3D新模型百十个定位预测+胆码预测+去和尾2025年4月17日第55弹
从今天开始,咱们还是暂时基于旧的模型进行预测,好了,废话不多说,按照老办法,重点8-9码定位,配合三胆下1或下2,杀1-2个和尾,再杀6-8个和值,可以做到100-300注左右。 (1)定…...
C++23 新特性:std::size_t 字面量后缀 Z/z
在 C23 中,引入了一个非常实用的新特性:为 std::size_t 类型的字面量提供了新的后缀 Z 和 z。这一改进使得在代码中声明和使用 std::size_t 类型的字面量变得更加直观和便捷。 1. 背景与动机 在之前的 C 标准中,std::size_t 是一种非常常用…...
【裁员感想】
裁员感想 今天忽然感觉很emo 因为知道公司要裁员 年中百分之10 年末百分十10 我知道这个百分20会打到自己 所以还挺不开心的 我就想起 我的一个亲戚当了大学老师 我觉得真的挺好的 又有寒暑假 又不是很累 薪资也不低 又是编制 同时也觉得自己很失败 因为对自己互联网的工作又…...
CSS例子 > 图片瀑布流布局(vue2)
<template><div class"container"><!-- 临时容器用于计算高度 --><div v-if"!isLayoutReady" class"temp-container"><divv-for"(item, index) in list":key"temp- index":ref"(el) > …...
Python 获取淘宝券后价接口的详细指南
在电商领域,淘宝作为国内领先的电商平台,提供了丰富的商品和优惠活动。对于开发者来说,获取淘宝商品的券后价是一个极具价值的功能,可以帮助用户更好地进行购物决策,同时也为相关应用和服务提供了数据支持。本文将详细…...
零服务器免备案!用Gitee代理+GitHub Pages搭建个人博客:绕过443端口封锁实战记录
#GitHub Pages #Gitee代理 #SSH密钥管理 #Jekyll博客 #网络穿透 场景:自己的电脑没有添加github的ssh代理,只有gitee的代理 实现效果,在公网可以运行个人博客。在本地更改内容后公网同步更新。 最开始的模板 最终实现的博客模板࿱…...
如何新建一个空分支(不继承 master 或任何提交)
一、需求分析: 在 Git 中,我们通常通过 git branch 来新建分支,这些分支默认都会继承当前所在分支的提交记录。但有时候我们希望新建一个“完全干净”的分支 —— 没有任何提交,不继承 master 或任何已有内容,这该怎么…...
[终极版]Javascript面试全解
this指向 执行上下文 是代码执行时的运行环境作用域 是变量和函数的可访问性规则(静态);全局、函数和块状;内层可访问外层,外层不能访问内层词法环境 是实现作用域的引擎内部机制(静态) 执行上…...
day30图像处理OpenCV
文章目录 一、图像预处理9. 图像掩膜9.1 制作掩膜9.2 与运算1.原理2.语法 9.3 颜色替换9.4案例 一、图像预处理 9. 图像掩膜 创建的掩膜方便我们对目标区域进行操作。 9.1 制作掩膜 掩膜通常是一个二值化图像,并且与原图像的大小相同。其中目标区域被设置为1&am…...
蓝桥杯 10.拉马车
拉马车 原题目链接 题目描述 小时候你玩过纸牌游戏吗? 有一种叫做 “拉马车” 的游戏,规则简单但非常吸引小朋友。 游戏规则简述如下: 假设参加游戏的小朋友是 A 和 B,游戏开始时,他们得到的随机纸牌序列如下&am…...
Java学习总结-Junit单元测试
单元测试: 就是针对最小功能的单元:方法,编写测试代码对其进行正确性测试。 之前我们怎么测试的:在main方法中调用其他方法,一个方法测试失败可能导致其他方法得不到测试,无法得到测试报告。 Junit单元测…...
代理IP:城市文化IP打造的隐形加速器
目录 一、解码代理IP:数字时代的"变身术" 1.1 工作原理探秘 1.2 主要类型对比 二、城市文化IP的打造密码 2.1 核心要素拆解 2.2 成功案例启示 三、代理IP的五大赋能场景 3.1 文化数据采集 3.2 目标市场定位 3.3 品牌传播突破 3.4 版权保护监控 …...
链式数据存储系统
目录 系统说明 服务端的模块设计 存储数据说明 服务端设计-程序入口(main) 数据库的连接-mysql包的编写 数据的加密-hash文件的编写 数据传递格式-proto文件的编写 具体实现方法-controller包的编写 日志的打印-logs包的编写 扩展服务端 系统说…...
《理解 Java 泛型中的通配符:extends 与 super 的使用场景》
大家好呀!👋 今天我们要聊一个让很多Java初学者头疼的话题——泛型通配符。别担心,我会用最通俗易懂的方式,带你彻底搞懂这个看似复杂的概念。准备好了吗?Let’s go! 🚀 一、为什么我们需要泛型通配符&…...
Scala 入门指南
Scala 入门指南 目录 简介环境搭建基础语法面向对象编程函数式编程集合模式匹配特质隐式转换并发编程与 Java 互操作最佳实践常见问题 简介 Scala 是一种多范式编程语言,结合了面向对象编程和函数式编程的特性。它运行在 JVM 上,与 Java 完全兼容&am…...
GESP2025年3月认证C++八级( 第一部分选择题(11-15))
杨辉三角形: #include <iostream> using namespace std;#define N 35 // 最多支持输出 35 行 int a[N]; // 一维数组,用于存储当前行的杨辉三角数int main() {int n;cin >> n; // 输入要输出的行数for (int i 0; i < n; i) {a[i] …...
Dynamics 365 Business Central Master Data Managerment Setup 主数据管理
#Dynamics 365 BC ERP# #Navision# 引言 在BC中除了之前有一个章节提到的用Code 同步资料, 也可以用内置主数据管理功能来同步资料。 Master Data Management Setup 设置Source Company 为 主要管理主数据的公司 Synchronization Tables 设置需要同步的Table 这…...
深入理解 requestIdleCallback 与大数据加载优化
使用 requestIdleCallback 优化大批量 DOM 操作 —— 以加载 100 万条数据为例 在前端开发中,如果你尝试在短时间内往 DOM 中添加大量元素,比如一次性插入 100 万条数据,页面极有可能卡顿甚至直接崩溃。为了解决这一性能问题,我们…...
【MySQL】索引运算与NULL值问题详解:索引字段应尽量 NOT NULL ,NULL值不能参与部分索引运算
索引运算与NULL值问题详解 不能参与的"部分索引运算"指什么? 这里的"部分索引运算"指的是索引列在某些特定操作或条件下无法被MySQL优化器有效利用的情况,特别是当字段包含NULL值时。主要包括以下几种情况: 1. 比较运…...
STM32 F103 标准库CH452A 4线 数码管驱动芯片显示数码管
公司生产测试需要统一去检查这版CH452A的好坏,网上找了一下没有现成可以用的4线CH452A的驱动程序,所以直接就肝了移植官方的51程序到stm32上面去,亲测可以使用!! 文末有代码 测试图片: 如你所见我测了一堆…...
Vue 和 Spring boot 和 Bean 不同生命周期
一、Vue 组件生命周期 父子组件生命周期顺序: 创建时: 父 beforeCreate → 父 created → 父 beforeMount → 子组件生命周期 → 父 mounted 更新时: 父 beforeUpdate → 子组件更新 → 父 updated。 销毁时: 父 beforeDestroy…...