如何使用 Class<TChild> 将 TBase 列表转换为 Child 列表,其中 Child : TAble?
How to cast a list of TBase to a list of TChild where TChild : TBase using KClass<TChild>?
我有一个 TBase
的列表,我知道它是 TChild
的列表,其中 TChild : TBase
。如果我只有KClass<TChild>
,是否可以将List<TBase>
转换为List<TChild>
?
fun <TChild : BaseType> castList(list: List<BaseType>, clazz: KClass<TChild>): List<TChild> {
// is this possible?
}
我知道如果不涉及 List
,我可以将 TBase
的实例转换为 TChild
:
clazz.cast(item)
我想做的事情可行吗?希望避免具体化的解决方案。
如果你真的确定它只包含 TChild
个对象并且你不会在转换后向它添加任何其他对象(List
是只读的,不是不可变的),那么你可以像这样进行未经检查的转换:
fun <TChild : BaseType> castList(list: List<BaseType>, clazz: KClass<TChild>): List<TChild> {
@Suppress("UNCHECKED_CAST")
return list as List<TChild>
}
如果你更喜欢在投射时实际检查物品的类型,那么做:
fun <TChild : BaseType> castList(list: List<BaseType>, clazz: KClass<TChild>): List<TChild> {
return list.map { clazz.cast(it) }
}
请注意,第二个解决方案创建了列表的副本,因此这不完全是强制转换。如果您打算在转换后修改原始列表,这一点很重要,因为正如我所说,List
不是不可变的,而是只读的。
是的。类型断言。
请记住,泛型是编译器凭空想象出来的。 JVM 不知道泛型是什么,也不关心。大多数泛型被删除(被剥离并且在变成 class 文件的过程中无法存活)。剩下的少数部分(签名中的泛型)被 JVM 视为注释。 java.exe
完全忽略它们。
它们 仅 使 kotlinc/javac 产生错误、警告和语法糖。就是这样。
您可以进行类型断言。请注意,这些 未选中 。您告诉编译器您将保证所讨论的列表 [A] 当前仅包含 TChild
的实例,并且 [B] 将来在这段代码运行的整个持续时间内,没有什么会改变这一点(没有代码最终会向这个列表添加非 TChild 引用。
如果列表是不可变的,[B] 部分显然根本不相关。
编译器会相信您的话。如果事实证明你错了,你会在包含零转换的行上得到 ClassCastException
。因为这有点奇怪,javac 会警告你,但你可以抑制这个警告。
在 java 中看起来像这样:
List<Object> list1 = new ArrayList<Object>();
List<String> list2 = (List<String>) list1; // warning here
list1.add(5.0); // works.
list2 = (List<String>) list1; // still works. (and warns).
String a = list2.get(0); // look ma, ClassCastException without a cast!
要抑制它,@SuppressWarnings("unchecked")
在本地 var decl 或方法上。
在 kotlin 中它看起来很相似:list as List<TChild>
转换它,@Suppress("UNCHECKED_CAST")
抑制警告。
另一种方法是实际迭代列表并确保列表实际上包含正确的类型,当场抛出 ClassCastException,而不是向堆中注入一个定时炸弹,只要某些代码触及稍后列出:
List<Object> obj = new ArrayList<Object>();
obj.add(5.0);
List<String> strings = new ArrayList<String>();
for (Object o : obj) strings.add((String) o);
当然,这需要 O(N)
时间;任何检查列表的尝试都将是 O(N)
,因为泛型已被删除,因此除了遍历批次之外,无法在运行时确定。它在 kotlin 中非常相似。
我有一个 TBase
的列表,我知道它是 TChild
的列表,其中 TChild : TBase
。如果我只有KClass<TChild>
,是否可以将List<TBase>
转换为List<TChild>
?
fun <TChild : BaseType> castList(list: List<BaseType>, clazz: KClass<TChild>): List<TChild> {
// is this possible?
}
我知道如果不涉及 List
,我可以将 TBase
的实例转换为 TChild
:
clazz.cast(item)
我想做的事情可行吗?希望避免具体化的解决方案。
如果你真的确定它只包含 TChild
个对象并且你不会在转换后向它添加任何其他对象(List
是只读的,不是不可变的),那么你可以像这样进行未经检查的转换:
fun <TChild : BaseType> castList(list: List<BaseType>, clazz: KClass<TChild>): List<TChild> {
@Suppress("UNCHECKED_CAST")
return list as List<TChild>
}
如果你更喜欢在投射时实际检查物品的类型,那么做:
fun <TChild : BaseType> castList(list: List<BaseType>, clazz: KClass<TChild>): List<TChild> {
return list.map { clazz.cast(it) }
}
请注意,第二个解决方案创建了列表的副本,因此这不完全是强制转换。如果您打算在转换后修改原始列表,这一点很重要,因为正如我所说,List
不是不可变的,而是只读的。
是的。类型断言。
请记住,泛型是编译器凭空想象出来的。 JVM 不知道泛型是什么,也不关心。大多数泛型被删除(被剥离并且在变成 class 文件的过程中无法存活)。剩下的少数部分(签名中的泛型)被 JVM 视为注释。 java.exe
完全忽略它们。
它们 仅 使 kotlinc/javac 产生错误、警告和语法糖。就是这样。
您可以进行类型断言。请注意,这些 未选中 。您告诉编译器您将保证所讨论的列表 [A] 当前仅包含 TChild
的实例,并且 [B] 将来在这段代码运行的整个持续时间内,没有什么会改变这一点(没有代码最终会向这个列表添加非 TChild 引用。
如果列表是不可变的,[B] 部分显然根本不相关。
编译器会相信您的话。如果事实证明你错了,你会在包含零转换的行上得到 ClassCastException
。因为这有点奇怪,javac 会警告你,但你可以抑制这个警告。
在 java 中看起来像这样:
List<Object> list1 = new ArrayList<Object>();
List<String> list2 = (List<String>) list1; // warning here
list1.add(5.0); // works.
list2 = (List<String>) list1; // still works. (and warns).
String a = list2.get(0); // look ma, ClassCastException without a cast!
要抑制它,@SuppressWarnings("unchecked")
在本地 var decl 或方法上。
在 kotlin 中它看起来很相似:list as List<TChild>
转换它,@Suppress("UNCHECKED_CAST")
抑制警告。
另一种方法是实际迭代列表并确保列表实际上包含正确的类型,当场抛出 ClassCastException,而不是向堆中注入一个定时炸弹,只要某些代码触及稍后列出:
List<Object> obj = new ArrayList<Object>();
obj.add(5.0);
List<String> strings = new ArrayList<String>();
for (Object o : obj) strings.add((String) o);
当然,这需要 O(N)
时间;任何检查列表的尝试都将是 O(N)
,因为泛型已被删除,因此除了遍历批次之外,无法在运行时确定。它在 kotlin 中非常相似。