Java 语言的设计缺陷

Posted by envoy on August 6, 2018

没有任何一种程序设计语言是完美的。

在一门语言的产生过程中,设计阶段尤为重要。一旦一个语言特性被确定下来,世界上有至少一个人开始使用这个特性时,就难以在将来的版本中对其进行不兼容的变更了。换句话说,设计过程中出现的差错是难以弥补的。

程序语言是由人创造的,人非圣贤,孰能无过。况且大多数语言在初期的设计都是由一个人独立完成的,出现一些考虑不周和欠妥的设计在所难免。本文简要探讨Java语法在设计过程中存在的一些问题可行的解决方案。

基本类型(Primitive Types)

Java将数据类型分为基本类型(Primitive Types,如int, char)和引用类型(Reference Types,如Integer, Character)。这可能是从C++直接拿来的设计,但实际上它是一个重大的败笔。

想象一下:我们将一段代码中的int类型全部换为Integer,会发生什么?

int a = 1;
Integer a = 1;

这段代码的运行结果丝毫没有变化(除了用==比较两个整数之外)。

你也许会开始思考,既然基本类型可以替换为引用类型,那么它的存在意义是什么?没错,这是Java对性能做出的妥协:操作基本类型的数据(位于栈上)比引用类型的数据(位于堆上)更快,所以大多数情况下人们应该优先考虑使用基本类型。

但如果仅因此就将其作为语言特性,显然是不合理的。尽可能减少开发者的工作,提高其开发效率,是语言的使命之一。这样的性能考虑显然不需要开发者来操心:判断应该使用哪种类型是编译器或运行时的任务,而不是开发者的任务。

一个更好的设计是:从语言中删除基本类型。开发者在编码时一律使用Integer等引用类型,由编译器自动对代码进行优化,将其中可以用基本类型完成的功能替换成int,而这种优化对开发者是透明的。

类的构造函数

高级语言能够减少代码中的重复部分。

以下是一个简单的类:

class A {
    // constructor
    A() {
        // ...
    }

    // constructor with an argument
    A(int value) {
        this();
        this.value = value;
    }

    int value;
}

我们发现这段简单的代码中出现了多个相同的类名A,分别位于类声明和构造函数声明处,造成了轻微的冗余。当我们需要修改A的名称时,令人头疼的事情发生了:我们需要同时修改多处,这显然是不合理的设计。

一个合理的解决方案是对构造函数使用特定的名称,如initialize。按此方案修改的代码如下:

class A {
    // constructor
    initialize() {
        // ...
    }

    // constructor with an argument
    initialize(int value) {
        this();
        this.value = value;
    }

    int value;
}

Override 注解

Java规定所有的成员方法都可以被子类覆盖,除非声明为final。那么问题来了:我们如何知道一个方法究竟是独自存在的,还是覆盖了其父类的同名方法?

直到JDK 1.5,Java才通过引入@Override注解勉强解决了这个问题。但它是可选的。实际上,这更应该是一个关键字(如C#中的override),开发者在覆盖父类方法时必须写出,否则将无法通过编译,从而提高代码的自说明性,无需额外注释。此外还可以帮助IDE简化重构实现机制,增强代码提示。


限于笔者水平,文中有未尽和错漏之处,请(点此)不吝赐教