算法(4th ed)(111):基础——数据抽象 4.6

阅读数:10 2019 年 11 月 2 日 12:27

算法(4th ed)(111):基础——数据抽象 4.6

(答疑)

 为什么要使用数据抽象?

 它能够帮助我们编写可靠而正确的代码。例如,在 2000 年的美国总统竞选中,Al Gore 在弗罗里达州的 Volusia 县的一个电子计票机上得到了 -16022 张选票——显然电子计票机软件中的选票计数器的封装不正确!

 为什么要区别原始数据类型和引用类型?为什么不只用引用类型?

 因为性能。Java 提供了 IntegerDouble 等和原始数据类型对应的引用类型,以供希望忽略这些类型的区别的程序员使用。原始数据类型更接近计算机硬件所支持的数据类型,因此使用它们的程序比使用引用类型的程序运行得更快。

 数据类型必须是抽象的吗?

 不。Java 也支持publicprotected 来帮助用例直接访问实例变量。如正文所述,允许用例代码直接访问数据所带来的好处比不上对数据的特定表示方式的依赖所带来的坏处,因此我们代码中所有的实例变量都是私有的(private),有时也会使用私有实例方法在公有方法之间共享代码。

 如果我在创建一个对象时忘记使用 new 关键字会发生什么?

 对于 Java,这种代码看起来就好像你希望调用一个静态方法,却得到一个对象类型的返回值。因为并没有定义这样一个方法,你得到的错误信息和引用一个未定义的符号是一样的。如果编译这段代码:

复制代码
Counter c = Counter("test");

会得到这条错误信息:

复制代码
cannot find symbol
symbol : method Counter(String)

如果你提供给构造函数的参数数量不对,也会得到相同的出错信息。

 如果我在创建一个对象数组时忘记使用 new 关键字会发生什么?

 创建每个对象都需要使用new,所以要创建一个含有 N 个对象的数组,需要使用 N+1 次 new 关键字:创建数组需要一次,创建每个对象各需要一次。如果忘了创建数组:

复制代码
Counter[] a;
a[0] = new Counter("test");

你得到的错误信息和尝试为一个未初始化的变量赋值是一样的:

复制代码
variable a might not have been initialized
a[0] = new Counter("test");
^

但如果在创建数组中的一个对象时忘了使用 new,然后又尝试调用它的方法,会得到一个 NullPointerException

复制代码
Counter[] a = new Counter[2];
a[0].increment();

 为什么不用 StdOut.println(x.toString()) 来打印对象?

 这条语句也可以,但 Java 能够自动调用任意对象的 toString() 方法来帮我们省去这些麻烦,因为 println() 接受的参数是一个 Object 对象。

 指针是什么?

 问得好。或许上面那个异常应该叫做 NullReferenceException。和 Java 的引用一样,可以把指针看做机器地址。在许多编程语言中,指针是一种原始数据类型,程序员可以用各种方法操作它。但众所周知,指针的编程非常容易出错,因此需要精心设计指针类的操作以帮助程序员避免错误。Java 将这种观点发挥到了极致(许多主流编程语言的设计者也赞同这种做法)。在 Java 中,创建引用的方法只有一种new),且改变引用的方法也只有一种(赋值语句)。也就是说,程序员能对引用进行的操作只有创建和复制。在编程语言的行话里,Java 的引用被称为安全指针,因为 Java 能够保证每个引用都会指向某种类型的对象(而且它能找出无用的对象并将其回收)。习惯于编写直接操作指针的程序员认为 Java 完全没有指针,但人们仍在为是否真的需要不安全的指针而争论。

 我在哪里能够找到 Java 如何实现引用和进行垃圾收集的细节?

 Java 系统的实现各有不同。例如,实现引用的一种自然方式是使用指针(机器地址);而另一种使用的则可能是句柄(指针的指针)。前者访问数据的速度更快,而后者则能够更好地实现垃圾回收。

 导入(import)一个对象名意味着什么?

 没什么,只是可以少打一些字。如果不想使用 import 语句,你也可以在代码中用 java.util.Arrays 代替所有的 Arrays

 实现继承有什么问题?

 子类继承阻碍模块化编程的原因有两点。第一,父类的任何改动都会影响它的所有子类。子类的开发不可能和父类无关。事实上,子类是完全依赖于父类的。这种问题被称为脆弱的基类问题。第二,子类代码可以访问所有实例变量,因此它们可能会扭曲父类代码的意图。例如,用于选票统计系统的Counter 类的设计者可能会尽最大努力保证Counter 每次只能将计数器加一(还记得 Al Gore 的问题吗)。但它的子类可以完全访问这个实例变量,因此可以将它改变为任意值。

 怎样才能使一个类不可变?

 要保证含有一个可变类型的实例变量的数据类型的不可变性,需要得到一个本地副本,这被称为保护性复制,但这也不一定能够达到目的。得到副本是一个方面,保证没有任何实例方法能够改变数据的值是另一方面。

 什么是空(null)?

 它是一个不指向任何对象的字面量。引用 null 调用一个方法是没有意义的,并且会产生 NullPointerException。如果你得到了这条错误信息,请检查并确认构造函数是否正确地初始化了类的所有实例变量。

 实现某种数据类型的类中能否存在静态方法?

 当然可以。例如,我们实现的所有类中都含有一个 main() 方法。另外,对于涉及多个对象的操作,如果它们都不是触发该方法的合适对象,那么就应该考虑添加一个静态方法。例如,我们可以在 Point 类中定义如下静态方法:

复制代码
public static double distance(Point a, Point b)
{
return a.distTo(b);
}

这种方法常常能够简化用例代码。

 除了参数变量、局部变量和实例变量外还有其他种类的变量吗?

 如果你在类的声明中包含了关键字 static(在其他类型之前),就创建了一种称为静态变量的完全不同的变量。和实例变量一样,类中的所有方法都可以访问静态变量,但静态变量却并不和任何具体的对象相关联。在较老的编程语言中,这种变量被称为全局变量,因为它们的作用域是全局的。在现代编程中,我们希望限制变量的作用域,因此很少使用这种变量。在使用它们时会非常小心。

 什么是弃用(deprecated)的方法?

 不再被支持但为了保持兼容性而留在 API 中的方法叫做弃用的方法。例如,Java 曾经包含了一个 Character.isSpace() 的方法,程序员也使用这个方法编写了一些程序。当 Java 的设计者们后来希望支持 Unicode 空白字符时,他们无法既改变 isSpace() 的行为又不损害用例程序。因此他们添加了一个新方法 Character.isWhiteSpace() 并放弃了老的方法。随着时间的推移,这种方式显然会使 API 更复杂。有时候甚至整个类都会被弃用。例如,Java 为了更好地支持国际化就将它的 java.util.Date 标记为弃用。

评论

发布