casting - 是否有任何编程语言可以禁止返回类型的类转换?
问题描述
背景
以这个简单的 java 类为例:
class SomeWrapper {
List<SomeType> internalDataStructure = new ArrayList<>();
boolean hasSomeSpecificSuperImportantElement = false;
// additional fields that do useful things
public void add(SomeType element) {
internalDataStructure.add(element);
if (SUPER_IMPORTANT.equals(element)) {
hasSomeSpecificSuperImportantElement = true;
}
}
public Iterable<SomeType> getContents() {
return internalDataStructure;
}
// additional methods that are definitely useful
}
虽然看起来还不错,但它有一个很大的缺陷。因为 internalDataStructure 是按原样返回的,所以 getContents() 的调用者可以将其转换为 List 并以它们不应该的方式修改内部数据结构。
class StupidUserCode {
void doSomethingStupid(SomeWrapper arg) {
Iterable<SomeType> contents = arg.getContents();
// this is perfectly fine
List<SomeType> asList = (List<SomeType>) contents;
asList.add(SUPER_IMPORTANT);
}
}
在 java 和其他类似的编程语言中,这个问题可以通过将内部数据结构包装在一些不可变的包装器中来解决。例如:
public Iterable<SomeType> getContents() {
return Collections.unmodifiableList(internalDataStructure);
}
这现在有效,但在我看来,有一些缺点:
- 微小的性能缺陷,除了最极端的情况外,没什么大不了的
- 不熟悉该语言的开发人员需要学习这个不可变的 API
- 标准库的开发者需要不断添加对各种数据结构的不可变支持
- 代码变得更加冗长
- 不可变包装器有许多公共方法,它们都会抛出异常。在我看来,这是一个相当肮脏的解决方案,因为它需要为 API 用户提供额外的文档。
问题
是否有任何编程语言可以指定方法的返回类型不可能是类强制转换?
我在想像合成类型这样附加感叹号的东西!到类型名的末尾使其无法进行类转换。像这样:
public Iterable!<SomeType> getContents() {
return internalDataStructure;
}
void doSomethingStupid(SomeWrapper arg) {
// The ! here is necessary because Iterable is a different type than Iterable!
Iterable!<SomeType> contents = arg.getContents();
// this now becomes a compile-time error because Iterable! can not be cast to anything
List<SomeType> asList = (List<SomeType>) contents;
asList.add(SUPER_IMPORTANT);
}
解决方案
由于这里的重点是关于可变性,Java 的List
vs.示例Iterable
并不是可变数据类型与不可变数据类型的一个很好的示例(该Iterator.remove
方法会改变底层集合,因此List
即使没有强制转换也可能会被外部调用者破坏)。
让我们想象两种类型,MutableList
和ReadonlyList
,子类型在哪里MutableList
, aReadonlyList
只防止用户改变它;列表本身不能保证避免突变。(我们不能明智地命名超类型ImmutableList
,因为没有值既是可变列表又是不可变列表。)
从超类型转换到子类型,例如 from ReadonlyList
to MutableList
,称为向下转换。向下转换是不安全的,因为不是超类型的每个值都是子类型的值;所以要么需要在运行时执行检查(就像 Java 那样,ClassCastException
如果被强制转换的实例没有正确的类型,则抛出),或者程序会做一些内存不安全的事情(就像 C 可能做的那样)。
理论上,一种语言可能会以不安全为由禁止向下转换;流行的编程语言不这样做,因为能够编写你知道是类型安全的代码很方便,但是语言的类型系统不足以让你编写合适的类型注释,从而允许类型检查器证明代码是类型安全的。并且没有任何类型检查器可以合理地证明您的代码的每个可证明的属性。尽管如此,理论上没有什么可以阻止语言禁止向下转换。我只是不认为很多人会选择使用这样的语言来开发大型软件。
也就是说,我认为您描述的问题的解决方案就是不MutableList
创建ReadonlyList
. 该类MutableList
仍然可以具有获取只读视图的方法,但是由于没有子类型/超类型关系,因此该视图不会是类型的值,MutableList
因此即使您向上转换,也无法将其转换为可变类型首先是一个共同的超类型。
为了避免运行时的性能成本,一种语言可以对此类包装器提供特定支持,以允许包装器在编译时而不是在运行时将其方法委托给原始列表。