一、判断题(正确的描述打钩,错误的打叉,每题 1 分,共 10 分)
1. 一个 Java 源文件中只能定义一个公有类,并且类名必须与文件名一致。(对)
2. 通过在程序中引入“ import javax.swing.*; ”语句,就可以使用 javax.swing 包中的所有类(包括其嵌套的子包中的类)。 (错)
要使用嵌套子包中的类,必须再次添加 import 子句
3. J2SE 的功能是 J 2 ME 的一个子集。(错)
4. Java 字节码只有被装入到内存中之后,才能被执行。(对)
在当前计算机体系结构之下,CPU无法直接读取保存于磁盘上的程序文件,这些文件所包容的指令必须被装入到内存之后,CPU才能读取。
5. Swing是一套GUI组件,采用了新的思路设计Java应用程序的界面,它完全地替换掉了原有的AWT。(错)
Swing仍然使用AWT的事件模型,并非完全抛弃。
值得注意的NetBeans 6.9.1完成最新的更新组件后,创建Java桌面应用程序时,有了一个新的提示消息框:
看来Oracle也知道Java在桌面上没戏,干脆放弃了对Swing框架的进一步开发。已经存在这么多年的Swing框架被“新主人”抛弃,真不知道该说什么。
6. 在编程时编写过多的注释,会使编译之后生成的程序文件变大。(错)
放心, 编译器会删除所有的注释。所以,哪怕你在程序中写了一部长篇小说,也不会拖慢程序的运行速度。
7. Java Applet 可运行于浏览器中,这意味着浏览器本身就可以直接执行 Applet 程序。(错)
浏览器本身只认识HTML,它对其他类型信息的支持,比如Flash,Applet,Silverlight应用程序等都是通过启动本地计算机安装的附加插件来实现的。
8. JDK 5.0新增的枚举类型( enum )是一个原始数据类型。(错)
9. Java 中规定所有的方法都必须放到某个类中,不存在独立于类的方法。(对)
10. 面向对象软件系统设计师们经常用到的 UML 是一种编程语言。(错)
二、简答题(请将答案写到答题纸上,注明题号。每题 4 分,共 40 分)
1. 现代编程语言有两种主要的类型:一种是“编译型 ”的,比如 C ,程序源码必须经过编译才能运行;另一种是“解释型 ”的,这种类型的语言(比如早期的 Basic )通常都拥有一个交互环境,用户输入一句代码计算机就执行一句代码。
Java 属于上述哪种类型?如果你认为 Java 不属于上述任何一种类型,那么它是不是一种新的编程语言类型?
Java源程序需要编译,但运行时需要即时“解释”为本地CPU能执行的机器指令,所以它不属于“纯”的编译型或解释型,而是一种混合类型。
有的同学很“绝”,他说:“Java是一种面向对象类型的编程语言”。这句话还真是对的,我也给了分。
2. Java 中有两个关键字: void 和 null ,它们有什么区别?
这题100%的学生都能答对,纯粹是送分题。
3. 简述“类(Class)”、“类库(Class Library)”、“包(Package)”、“ Jar 文件”这四个概念间的联系。
类库其实是一个“统称”,它是类的集合。包则是类库的组织形式,它类似于C++中的namespace,可以解决类的同名问题。
Jar则是一个采用Zip格式压缩的文件包 ,主要是为了方便Java组件的复用,简化Java应用程序的部署和发布。Jar包中可以保存任意类型的多个文件和多级嵌套的
文件夹。比如JDK就包容了不少Jar压缩包,你可以使用解压缩工具(比如WinRAR)去查看它的内容。可以把Jar文件看成是Java类库的一个物理“载体”,之所以称
其为“物理”的,是因为我们可以在计算机中直接“看见”并“操作”它。
在学习和探索知识的过程中,有的时候就必须注意清晰地分清各个似是而非的概念,这道题有好几个同学就把这几个概念间的关系弄乱了。
4. 面向对象的程序在运行时会创建多个对象,这些对象之间通常可以相互 “ 发送消息 ” ,谈谈你对 “ 对象之间发送消息 ” 这句话的理解,并编写若干
句示例代码展示对象之间发送消息的具体编程实现方法。
对象间的消息发送最基本的手段就是:一个对象执有另一个对象的引用,然后,通这个对象引用调用对方的方法,存取对方的字段(或属性)。
对象互发消息,其本质目的就是为了交换信息。
除了上述这两种直接方式之外,我们还可以通过一些第三方的媒价来实现对象间的信息交换:比如利用类的静态字段,利用内存映射文件,利用数据库,利用
外部共享的“物理”文件等。
我在《.NET 4.0面向对象编程漫谈 》的第14章《对象间的协作与信息交换》中介绍了七八种单个、多个对象间消息发送和信息交换的方式,虽然是用C#实现
的,但其思想完全可用于Java中。
掌握这些编程技巧,对开发非常关键。
5. 加法运算符“+”可以施加于原始数值类型(比如 int )的变量,但我们发现一些对象类型(比如 Integer ) 的变量,也支持“+”运算:
Integer v1 = 100;
Integer v2 = 200;
System.out.println(v1+v2 ); //输出: 300
这看上去好象 Integer 类型重载了 “ + ” 法运算符,一些编程语言比如 C ++可以为特定的类重载运算符,但 Java 本身并不支持运算符重载这一特性。依你的
理解或猜测, Java 是采用什么方法让两个 Integer 对象可以直接“相加”的?
上面代码首先是调用Integer.valueOf方法将整数转换为Integer对象,当两个Integer对象相加时,其实是使用Integer.intValue方法取出其所“包装”的整数值,相加之
后,再将结果输出。
上述结论是通过分析javap反汇编示例代码得到的。
诸如Java和C#之类的编程语言,许多现象已不仅仅是语法问题,编译器在其中起了很重要的作用。我在课堂上已经介绍过如何使用javap去反汇编.class文件,通
过阅读Java编译器生成的字节码指令去弄明白java编译器在后台玩的把戏。
这道题主要是引导学生学编程语言不要仅学语法,要学会利用工具透过表面现象看本质。
有的学生猜测Java可能是设计了另外的对象负责完成“+”运算,虽然与Java实际情况不符,但这个思路是可行的,我同样给分。
6. 在一个很大的循环(比如循环10000000 次)中,如果需要进行字串的连接操作,通常我们者使用StringBuffer 取代String ,解释这么做的理由。
绝大部分同学都答对了。但有的同学说:在大循环中使用String,会“浪费”大量的内存,这句话是不准确的,因为Java有一个垃圾收集机制,在合适的时机会
出来回收不用的String对象,因此,这些内存不能说是被“浪费”的,我们唯一可以说的是:
由于String对象是只读的,使用它大量字串连接操作,将导致频繁的内存分配操作 ,这会严重地影响程序性能。
7. 现有两个长度不同的数组:
int[] arr1 = new int[10];
int[] arr2 = new int[100];
那么以下语句能通过编译吗?
arr1 = arr2;
如果你认为可以通过编译,得到这个结论的依据是什么?如果不能通过编译,你认为其原因是什么?
可以通过编译。此题考核学生知不知道 Java 中的数组属于对象类型,知不知道对象变量相互赋值的含义。
8. 请看以下代码 :
double d1=100.1;
double d2=1001/10;
System.out.println(d1==d2); // 输出 : false
明明 d1 与 d2 是相等的 , 为什么程序运行时会输出一个让人意外的结果 : false ?
很多同学都指出1001/10 其实是“整除”,得到100 ,再转为double ,所以d2=100.0 ,自然d1==d2 返回false 了。
由于我在课堂上曾展示过计算机难于精确表示双精度数,比较两个double 变量是否相等应该检测其差的绝对值是否在某个可以容忍的误差范围内,所以几个同
学就往这个思路上去想了。
对这种情况,我没扣分。
9. 当使用多个 catch 语句捕获多个异常时, Java 规定捕获 Exception 的 catch 语句必须排在最后,如下所示:
try { …… }
catch(ClassCastException ex){…… }
catch(NumberFormatException ex){…… }
catch(Exception ex){ …… } // 此句必须放在最后!
为什么会有这个限制?谈谈你是怎么理解的。
学生们几乎 100% 答对此题,由于 Exception 是顶层类,放到前面会导致后面的 catch 语句不可能有机会运行。
10. 请看以下示例代码:
String s1 = "Hello";
String s2 = "Hello";
System.out.println(s1 == s2); // 输出: true
String s3 = new String("Hello");
String s4 = new String("Hello");
System.out.println(s3 == s4); // 输出: false
请解释一下为什么上述代码中 “ System.out …” 两句代码输出完全不同的结果 ?
这里面的关键在于 Java 常量池。 JVM 在装载一个 .class 文件时,会在内存中创建一个常量池。
“ Hello ”是字符串常量, Java 编译器会将“ Hello ”放到常量池中,并给它一个索引:
const #28 = Asciz Hello;
以下两句代码:
String s1 = "Hello";
String s2 = "Hello";
将生成以下的字节码指令:
0: ldc #2; //String Hello
2: astore_1
3: ldc #2; //String Hello
5: astore_2
上面的 #2 代表常量池中的第 2 项:
const #2 = String #28; // Hello
注意它引用常量池的第 28 项,前面已经看到,第 28 项就是一个“ Hello ”字符串对象。
astore 指令将常量池中同一个“ Hello ”字符串对象的地址赋给了 s1 和 s2 ,所以, s1==s2 肯定返回 true 。
下面来看一下:
String s3 = new String("Hello");
所生成的字节码指令:
// 创建 String 对象,对象引用被 Push 到操作数堆栈( operand stack )
// 常量池的第 5 项指向一个 CONSTANT_Class_info 类型的表项,这个表项将引用 String 类型
// 在程序运行时,会导致 JVM 装载并连接 String 类型信息
22: new #5; //class java/lang/String
25: dup // 复制对象引用,再次压入操作数堆栈,此复制的对象引用将用于调用构造函数
26: ldc #2; //String Hello 装载常量“ Hello ”
28: invokespecial #6;
//Methodjava/lang/String."<init>":(Ljava/lang/String;)V
// 调用 String 类的构造函数,它从操作数堆栈提取信息初始化 String 对象
31: astore_3 // 将创建好的对象引用保存到 s3 中
从上述分析可以清晰地看到, s3 与 s4 都引用不同的对象。所以, s3==s4 返回 fasle 。
现在,同学们对 JVM 的认识是不是更深入了一步?
现在 Java 程序员“遍地”都是,但其中高水平的不多,为什么?
一个很重要的原因就是浅尝辄止,仅记住了一些语法结论,却从不主动地去探索一下代码背后的奥秘,怎会有提高?
我很早就在课堂上展示了 javap 的使用方法,并向同学们推荐了那本经典的《 Inside the Java Virtual Machine 》,但我估计由于课业负担重,没几个同学真的
去看了这本书,对此,我只能一声叹息。
多问几个为什么,对事物刨根问底,这其实不是在学习,而是在探索了,这个过程中,你的能力会得到不断地锻炼和提高。
另外我要指出,同样的学习方法可以直接用 .NET 领域,比如 .NET 有一个 ildasm 可以直接查看 .NET 程序集( .exe 和 .dll ,等价于 Java 中的 .class )中
的 IL 指令(对应于 Java 字节码指令),有一个 Reflector 工具可以很方便地反汇编查看任意一个程序集的源码,对应地, .NET 虚拟机—— CLR ,也与 JVM
有许多相似之处。
我在写作《 .NET 4.0 面向对象编程漫谈》时,就通过直接查看程序集的 IL 指令代码,弄清楚了不少东西,也加深了对语言编译器和虚拟机的认识。
象 Java 和 .NET 这种非常相近的平台,下功夫弄清楚一个,你会发现学习与掌握另一个并不困难。计算机有很多东西是相通的。关键不在于学什么东西,
而是是否掌握了学习的科学方法,并且有一种不断学习、勇于探索未知的精神。具备这种基本素质的学生,我相信他日后的发展应该不会差的。
posted on 2011-02-19 16:39
鹏凌 阅读(645)
评论(0) 编辑 收藏 所属分类:
java