我的问题几乎就是标题所说的:是否有可能使用不允许显式类型转换的编程语言?
为了澄清我的意思,假设我们正在使用父类Base
类和子Derived
类的类似C#的语言。显然,这样的代码是安全的:
Base a = new Derived();
由于上传继承层次结构是安全的,但
Dervied b = (Base)a;
不保证安全,因为下降不安全。
但是,无论安全性如何,这种向下转换在许多语言中都是有效的(如Java或C#) - 代码将编译,如果类型不正确,将在运行时失败。从技术上讲,代码仍然是安全的,但是通过运行时检查而不是编译时检查(顺便说一下,我不是运行时检查的粉丝)。
我个人认为完整的编译时类型安全非常重要,至少从理论角度来看,最多从可靠代码的角度来看。编译时类型安全的结果是不再需要强制转换(我认为这很好,因为它们仍然是丑陋的)。任何类似强制转换的行为都可以通过隐式转换运算符或构造函数来实现。
所以我想知道,目前是否有任何OO语言在编译时提供如此严格的类型安全性,以至于演员表已经过时了?即,他们不允许任何不安全的转换操作?或者有什么理由不起作用?
感谢您的任何意见。
如果我能举例说明,这就是我非常讨厌这种倾向的重要原因。
假设我有以下内容(松散地基于C#的集合):
public interface IEnumerable<T>
{
IEnumerator<T> GetEnumerator();
IEnumerable<T> Filter( Func<T, bool> );
}
public class List<T> : IEnumerable<T>
{
// All of list's implementation here
}
现在假设有人决定编写这样的代码:
List<int> list = new List<int>( new int[]{1, 2, 3, 4, 5, 6} );
// Let's filter out the odd numbers
List<int> result = (List<int>)list.Filter( x => x % 2 != 0 );
注意最后一行是如何进行强制转换的。但它有效吗?不一般。当然,List<T>.Filter
的实现将返回另一个List<T>
是有道理的,但这不是保证(它可能是IEnumerable<T>
的任何子类型)。即使它在某个时间点运行,更高版本也可能会改变这一点,从而暴露出代码的脆弱程度。
我认为需要向下转换的几乎所有情况都会归结为类似这样的例子 - 一个方法具有某种类或接口的返回类型,但由于我们知道一些实现细节,我们对转发有信心结果。但这是反OOP,因为OOP实际上鼓励从实现细节中抽象出来。那么为什么我们仍然这样做,即使是纯粹的OOP语言呢?
答案 0 :(得分:2)
通过提高类型系统的功能,可以逐步消除向下倾斜。
您提供的示例的一个建议解决方案是添加将方法的返回类型声明为“与此相同”的功能。这允许子类返回子类而不需要强制转换。因此你得到这样的东西:
public interface IEnumerable<T>
{
IEnumerator<T> GetEnumerator();
This<T> Filter( Func<T, bool> );
}
public class List<T> : IEnumerable<T>
{
// All of list's implementation here
}
现在演员阵容是不必要的:
List<int> list = new List<int>( new int[]{1, 2, 3, 4, 5, 6} );
// Compiler "knows" that Filter returns the same type as its receiver
List<int> result = list.Filter( x => x % 2 != 0 );
其他的向下转型案例也通过改进类型系统提出了解决方案,但尚未对C#,Java或C ++进行这些改进。
答案 1 :(得分:1)
嗯,编辑语言当然没有子类型,当然也不需要那里的向下转型。大多数非OO语言属于该类。
即使在基于类的OO语言(如Java)中,大多数向下转换也可以通过让基类具有方法来正式替换
Foo meAsFoo() {
return null;
}
然后子类将覆盖它以返回自身。然而,这仍然只是表达运行时测试的另一种方式,还有更复杂的使用方式。并且很难在不失去基于继承的子类型的所有其他优点的情况下禁止该模式。
当然,只有在您能够修改父类时才可以这样做。我怀疑你可能会认为这是一个优点,但鉴于一个可以修改父类的频率,因此使用解决方法,我不确定在鼓励“好”方面值多少钱设计(对于某些或多或少任意的“好”值)。
如果语言提供了一个案例匹配结构而不是一个向下倾斜的表达式,那么可以提出更多安全编程的案例:
Shape x = .... ;
switch( x ) {
case Rectangle r:
return 5*r.diagonal();
case Circle c:
return c.radius();
case Point:
return 0 ;
default:
throw new RuntimeException("This can't happen, and I, "+
"the programmer, take full responsibility");
}
然而,在实践中可能存在一个问题,即如果没有封闭世界的假设(现代编程语言似乎不愿意这样做),许多交换机都需要default:
个程序员的情况知道永远不会发生,这可能会使程序员对结果抛出敏感性。
答案 2 :(得分:0)
有许多语言有鸭子类型和/或隐式类型转换。 Perl肯定会浮现在脑海中;标量类型的子类型如何在内部转换的复杂性是批评的常见来源,但也得到称赞,因为当它们像你期望的那样工作时,它们有助于DWIM对语言的感觉。
传统的Lisp是另一个很好的例子 - 你所拥有的只是原子和列表,而nil
都是同时存在的。否则,twain永远不会见面......
(你好像来自一个编程语言必然面向对象,强类型和编译的宇宙。)