文章目錄
- 一、問題背景
- 二、問題原因
- 三、問題探析
- 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序列化
,保證數據的可空安全性。