第1章 声明和访问控制
目标一 创建数组
数组
Java中的数组跟C/C++这些语言中的数组的语法结构很相似。但是,Java去掉了C/C++中的可以通过[]或者使用指针来访问元素的功能。这种在C/C++中被普遍接受的功能虽然强大,但是也让Bug横行的软件更容易出现。因为Java不支持这种直接通过指针来操纵数据,这类的Bug也被消除了。
数组是一类包含被称为元素的值的对象。这就为你在程序中移动或保存一组数据以很方便的支持,并且允许你根据需要访问和改变这些值。用一个小例子来说:你可以创建一个String类型的数组,每一个都包含一个运动队队员名字。数组可以传送给一个需要访问每个队员名字的方法。如果一个新队员加入,其中一个老队员的名字可以被修改成新队员的名字。这就显得比player1、player2、player3等等很随意的不相关的变量方便很多。跟变量通过变量名来访问不同的是,元素通过从0开始的数字来访问。因此,你可以一个个的访问数组的每个元素。
数组跟对象很相似,它们都是用new关键字来创建,并且有属于主要父对象类的方法。数组可能存储简单类型或者对象的引用。
数组的每个元素必须是同一类型的。元素的类型在数组被声明时确定。如果你需要存储不同类型元素的方式,你可以选择collection类,collection类是Java2考试中的新增的考点,我们将会在第十部分讨论它。你可以用数组来存储对象的句柄,你能像使用其它任意对象引用一样访问,摘录或者使用它。
声明但不分配空间
声明一个数组不需分配任何存储空间,它仅仅是代表你试图创建一个数组。跟C/C++声明一个数组的明显区别就是空间的大小没有被特别标识。因此,下面的声明将会引起一个编译期错误。
int num[5];
一个数组的大小将在数组使用new关键字真正创建时被给定,例如:
int num[];
num = new int[5];
你可以认为命令new的使用跟初始化一个类的实例的使用是类似的。例子中数组名num说明数组大小可以是任意大小的整形数据。
同时声明和创建数组
这个例子也可以使用一行语句完成:
int num[] = new int[5];
方括号也可以放在数据类型后面或者数组名后面。下面的两种都是合法的:
int[] num;
int num[];
你可以读作:
一个名字为num的整型数组
一个数据类型为整型名字为num的数组
Java和C/C++数组的比较
Java数组知道它的大小,并且Java语言支持对意外的移动到数组末端的保护。
如果你从Visual Basic背景下转到Java开发,并且还不习惯于一直从0开始计数,这点是很方便的。这也可以帮你避免一些在C/C++程序中很难发现的错误,例如移动到了数组末端并且指向了任意内存地址。
例如,下面的程序会引起一个ArrayIndexOutOfBoundsException异常。
int[] num= new int[5];
for(int i =0; i<6; i++){
num=i*2;
}
访问一个Java数组的标准习惯用法是使用数组的length成员
例如:
int[] num= new int[5];
for(int i =0; i<num.length; i++){
num=i*2;
}
数组知道它的大小
假如你跳过了C/C++的对照,Java中的数组总是知道它们的大小,这表现在length字段。因此,你可以通过下面的语句动态移动数组:
int myarray[]=new int[10];
for(int j=0; j<myarray.length;j++){
myarray[j]=j;
}
注意,数组有length字段,而不是length()方法。当你开始用一组字符串的时候,你会像s.length()这样使用字符串的length方法。
数组中的length是域(或者说特性)而不是方法。
Java数组和Visual Basic数组的对照
Java中的数组总是从0开始。如果使用了Option base声明,Visual Basic可能从1开始。Java中没有跟Visual Basic中可以使你不删除内容就改变数组大小的redim preserve命令等价的语句。但你可以建立一个同样大小的新数组,并且复制现有元素到里面。
一个数组声明可以有多个方括号。Java形式上不支持多维数组,但是它可以支持数组的数组,就是我们常说的嵌套数组。
C/C++中那样的多维数组和嵌套数组的最主要区别就是,每个数组不需要有同样的长度。如果你将一个数字当作一个矩阵,矩阵不一定是矩形。按照Java语言规范:(http://java.sun.com/docs/books/jls/html/10.doc.html#27805)
“括号里的数指明了数组嵌套的深度”
在其他语言中,就要跟数组的维度相符。因此,你可以建立一个类似于下面的形式的二维数组:
int i[][];
第一个维度可以匹配X,第二个维度可以匹配Y。
声明和初始化相结合
一个数组可以通过一个语句来创建并初始化,这就代替了通过数组循环来初始化的方式。这种方法很适合小数组。下面的语句创建了一个整型数组并且赋值为0到4:
int k[]=new int[] {0,1,2,3,4};
注意,你没有必要确定数组元素的数量。你可能在测验中被问到下面的语句是不是正确的问题:
int k=new int[5] {0,1,2,3,4} //Wrong, will not compile!
你可以创建数组的同时确定任何数据类型,因此,你可以创建一个类似于下面形式的字符串数组:
String s[]=new String[] {"Zero","One","Two","Three","Four"};
System.out.println(s[0]);
这句将会输出String[0]。
数组的默认值
不同于其他语言中的变量在类级别创建和本地方法级别创建有不同的动作,Java数组总是被设定为默认值。
无论数组是否被创建了,数组中的元素总是设为默认值。因此,整型的数组总是被置0,布尔值总是被置false。下面的代码编译时不会出错,并且输出0。
public class ArrayInit{
public static void main(String argv[]){
int[] ai = new int[10];
System.out.println(ai[0]);
}
}
问题
问题1)怎样通过一个语句改变数组大小同时保持原值不变?
1) Use the setSize method of the Array class
2) Use Util.setSize(int iNewSize)
3) use the size() operator
4) None of the above
问题2) 你想用下面的代码查找数组最后一个元素的值,当你编译并运行它的时候,会发生什么?
public class MyAr{
public static void main(String argv[]){
int[] i = new int[5];
System.out.println(i[5]);
}
}
1) Compilation and output of 0
2) Compilation and output of null
3) Compilation and runtime Exception
4) Compile time error
问题3)作为一个好的Java程序员,你已忘记了曾经在C/C++中知道的关于数组大小信息的知识。如果你想遍历一个数组并停止在最后一个元素处。你会使用下面的哪一个?
1)myarray.length();
2)myarray.length;
3)myarray.size
4)myarray.size();
问题4)你的老板为了你写出了HelloWorld而很高兴地为你升职了,现在她给你分配了一个新任务,去做一个踢踏舞游戏(或者我小时候玩的曲棍球游戏)。你认为你需要一个多维数组,下面哪一个能做这个工作?
1) int i =new int[3][3];
2) int[] i =new int[3][3];
3) int[][] i =new int[3][3];
4) int i[3][3]=new int[][];
问题5)
你希望找到一个更优雅的方式给你的数组赋值而不使用for循环语句,下面的哪一个能做到?
1)
myArray{
[1]="One";
[2]="Two";
[3]="Three";
}
2)String s[5]=new String[] {"Zero","One","Two","Three","Four"};
3)String s[]=new String[] {"Zero","One","Two","Three","Four"};
4)String s[]=new String[]={"Zero","One","Two","Three","Four"};
问题6)当你试着编译运行下面的代码的时候,可能会发生什么?
public class Ardec{
public static void main(String argv[]){
Ardec ad = new Ardec();
ad.amethod();
}
public void amethod(){
int ia1[]= {1,2,3};
int[] ia2 = {1,2,3};
int ia3[] = new int[] {1,2,3};
System.out.print(ia3.length);
}
}
1) Compile time error, ia3 is not created correctly
2) Compile time error, arrays do not have a length field
3) Compilation but no output
4) Compilation and output of 3
luren04 回复于:2008-04-14 16:57:25
答案
答案1)
4) None of the above
你不能改变一个数组的大小。你需要创建一个不同大小的临时数组,然后将原数组中的内容放进去。Java支持能够改变大小的类的容器,例如Vector或者collection类的一个成员。
答案2)
3) Compilation and runtime Exception
当你试着移动到数组的末端的时候,你会得到一个运行时错误。因为数组从0开始索引,并且最后一个元素是i[4]而不是i[5]。
答案3)
2) myarray.length;
答案4)
3) int[][] i=new int[3][3];
答案5)
3)String s[]=new String[] {"Zero","One","Two","Three","Four"};
答案6)
4) Compilation and output of 3
所有的数组的声明都是正确的。如果你觉得不太可能,可以自己编译这段代码。
目标二 定义类和变量
定义类,内部类,方法,实例变量,静态变量和自动(本地方法)变量,需要合适的选用允许的修饰词。(例如public,final,static,abstract诸如此类)。这些修饰词或者单独使用或者联合使用,定义了包的关系。
本目标需要注意的
我发现目标中用了“诸如此类”,这让我有些烦恼,我想你需要弄明白下面词的意思:
native
transient
synchronized
volatile
什么是类?
一个类的的定义把它很生硬描述为“方法和数据的集合”。它把面向对象编程出来之前的编程思想结合起来,这对理解该概念很有帮助。在类和面向对象程序设计前的主要概念是结构化程序设计。结构化程序设计的理念是程序员将复杂问题划分为小块的代码,一般称为函数或子程序。这符合“做一件很大很复杂的事情的好办法是把它分成一系列比较小但更容易管理的问题”的理念。
尽管结构化程序设计在管理复杂性方面很有用,但它不能容易的解决代码重用问题。程序员发现他们总是“重复发明”轮子。在试着对现实物理对象的思考中,程序设计方面的思想家找到了面向对象的理念(有时被称为OO)。
举例来说,一个计算机厂商准备生产一种新型个人电脑,如果计算机厂商使用类似于程序设计的方式的话,就要求他建立新团队来设计新CPU芯片,新声卡,没准还需要另一个团队设计规划制造新的主板。事实上,这根本不可能出现。由于电脑组件接口的标准化,计算机厂商只需要联系配件供应商,并商议好他们要生产的新型号的说明书就行了。注意组件接口标准化的重要性。
比较C++/VB和Java的类
因为Java被设计成容易让C++程序员学习的语言,因此两种语言在处理类上有很多相似的地方。C++和Java都有继承,多态和数据隐藏特性,并使用显式的修饰词。有一些不同也是因为使Java更容易学习和使用。
C++语言实现了多态继承,这样,一个类就可以比一个的父类(或基类)更强大。Java只允许单继承,这样就只有一个父类。为了克服这个限制,Java有一个被称作接口的特性。Java语言的设计者确定接口能够提供多态继承的好处而没有坏处。所有Java类都是Object类的后代。
对象在Visual Basic中是语言设计之后才加入的想法。Visual Basic有时被称作基于对象的语言而不是面向对象的语言。这就好像是语言的设计者认为类很酷,然后随着VB4的发布,他们决定加入一个新类型的模块,称它为类并且加上冒号,让它看起来更像C++。VB的类概念中失去了至关重要的元素:继承。微软在VB5中加入了跟Java的接口很相似的接口的概念。VB类和Java类的最主要相似之处是引用的使用和new关键字。
Java中类的角色
类是Java的心脏,所有的Java代码都在一个类里。Java里没有自由独立代码的概念,甚至最简单的HelloWorld应用都是包含在类里被创建的。为了指出一个类是另一个类的派生类,我们使用extend关键字。如果extend关键字没有被使用,这个类将是基类Object派生的。这可以使它有一些基本的功能,比如打印自己的名字和其他一些在线程中可能需要用到的功能。
类的最简单特性
定义一个类至少需要class关键字,类名和一对花括号。如:
class classname {}
如果不是有特别作用的类,它在语法上是正确的(我很惊讶的发现,当我举例说明继承时,我定义了一个跟着类似的类)。
通常,一个类还会包括一个访问修饰符,放在关键字class前面,还会有程序体放在花括号之间。下面的是一个更好的类模版:
public class classname{
//Class body goes here
}
创建一个简单的HelloWorld类
这里有一个简单的HelloWorld程序,它将会向控制台输出“hello world”。
public class HelloWorld{
public static void main(String argv[]){
System.out.println("Hello world");
}
}//End class definition
关键字public是一个可见的修饰符,指明了这个类对于其他类来说都是可见的。一个文件只有一个外部类可以声明为public。内部类将会隐藏在任意位置。如果你在一个文件中定义了多于一个的public类,将会发生一个编译期错误。注意,Java对每一部分都是很敏感的,包含这个类的文件名字必须是HelloWorld.java。当然,这跟微软平台虽然保护但是却忽略文件的大小写有些差别。
关键字class指明了一个将被定义的类,并且类名是HelloWorld。左花括号表明类的开始。注意,类结束的右花括号后面没有分号。注释语句
//End class definition
使用了C/C++中同样允许的单行类型。Java也能够识别/**/的注释模式。
创建一个类的实例
上面描述的HelloWorld应用例子很浅显的告诉了你所能创建的最简单的应用,但是它漏掉了使用类时至关重要的元素,那就是关键字new的使用,new指出了一个类的新实例的创建。在HelloWorld应用中,因为只有System.out.println这个唯一的static方法,并且不需要类使用new关键字创建,因此创建新实例不是必要的。static方法只能访问static变量。可以稍微改进一下HelloWorld应用,下面举例说明一个类的新实例的创建。
public class HelloWorld2{
public static void main(String argv[]){
HelloWorld2 hw = new HelloWorld2();
hw.amethod();
}
public void amethod(){
System.out.println("Hello world");
}
}
上面的代码通过这行代码创建了自己的一个新实例。
HelloWorld2 hw = new HelloWorld2();
这是使用类创建新实例的一个基本语法。注意类的名字怎样出现了两次。第一个指明了类的引用的数据类型。这需要它不能和new关键字所修饰真正的类的名字相同。这个类实例的名字是hw。这仅仅是给变量选择的名字。这里有一个命名习惯,一个类的实例名以小写字母开头,而类的名字以大写字母开头。
创建方法
在上一个例子HelloWorld2中,一个Java中的方法跟C/C++中的函数和Visual Basic中的子程序很相似。上例中名字为amethod的方法和本例中的amethod方法被声明为public,这说明它可以在任何地方被访问。它有一个返回值void,表明没有值返回。并且括号中也是空的,表明它没有参数。
同样的方法可以从下面几种方式之中选择:
private void amethod(String s)
private void amethod(int i, String s)
protected void amethod(int i)
这些例子说明了一些典型的方法签名。使用关键字private和protected说明它们将会在别处隐藏。
Java方法和其他像C这样的非面向对象语言的方法的区别是Java方法属于类。这表明它们通过点号指明代码属于哪个类的实例来调用。(static方法是一个例外,但我们现在无需担心)
因此在HelloWorld中amethod通过下面的语句调用
HelloWorld hw = new HelloWorld();
hw.amethod();
在HelloWorld类中创建的其他实例中,方法被类的每个实例所调用。每个类的实例将能够访问它自己的变量。因此下面的代码将调用不同实例的amethod方法
HelloWorld hw = new HelloWorld();
HelloWorld hw2 = new HelloWorld();
hw.amethod();
hw2.amethod();
类的两个实例hw和hw2可能访问不同的变量。
自动局部变量
自动变量是方法变量。它们在方法代码开始运行时生效,并在方法结束时失效。因为它们只能在方法内可见,因此临时操作数据时比较有用。如果你希望一个值在方法被调用时保持,你需要将变量创建在类级别。
一个自动变量将“屏蔽”类级别的变量。
因此,下面的代码将打印99而不是10
public class Shad{
public int iShad=10;
public static void main(String argv[]){
Shad s = new Shad();
s.amethod();
}//End of main
public void amethod(){
int iShad=99;
System.out.println(iShad);
}//End of amethod
}
修饰语和封装
修饰符的可见性是Java封装机制的一部分。封装允许分离方法执行的接口。修饰符的可见性是Java封装机制至关重要的部分。封装允许分离方法执行的接口。带来的好处就是类内部的代码的细节可以被改变,同时不影响其他对象的使用。这是面向对象设计(最后不得不在某处使用这个词)的一个关键概念。
封装一般用找回或更新private类的变量值的方法的形式。这些方法一般是accessor或mutator方法。访问方法找回值而设置方法改变值。命名惯例是这些方法名类似于setFOO改变值,getFOO得到值。注意,使用set和get来命名的方法比仅仅使程序员感到方便更重要,并且是Javabean系统的重要组成部分。不过我们的测试还没有涉及到Javabean的内容。
举一个例子,你有一个变量用来存储学生的年龄。你可能简单的用一个public的整型变量来存储。
int iAge;
接下来,当你的应用程序交付使用后,你可能会发现你的某些学生可能有超过200岁的记录,还有小于0岁的记录。你需要一段代码来检查错误条件。所以当你的程序改变年龄的值的时候,你用if语句来检查范围。
if(iAge > 70){
//do something
}
if (iAge <3){
//do something
}
当你正在做这些的时候,你漏掉了一些使用过iAge变量的代码,所以你被召回了,因为你可能有一个19岁的学生,但是你的记录里却是190岁。
面向对象使用封装处理了这样的问题,就是创建一个访问包含年龄值的private域的方法,名字类似于setAge和getAge。setAge方法可能有一个整型的参数并且更新年龄的private值,getAge方法没有参数但从private的年龄域返回值。
public void setAge(int iStudentAge){
iAge = iStudentAge;
}
public int getAge(){
return iAge;
}
开始,我们也许认为这么长的代码来做一小段代码就能完成的工作没有意义,但是,当这些方法能够满足你的需求时,可以帮你做更多的iAge域的确认工作,同时不会影响已经在使用这些信息的代码。
通过这样的代码执行处理方式,实际的程序代码行可以改变,而外面的部分(接口)保持不变。
Private(私有)
私有变量仅仅在创建它的类内部可见。这意味着它们在子类里不可见。这使变量除了当前类之外,绝缘于其他方法的修改。像是修饰语和封装里描述的,这对于将接口与接口实现分离开很有帮助。
class Base{
private int iEnc=10;
public void setEnc(int iEncVal){
if(iEncVal < 1000){
iEnc=iEncVal;
}else
System.out.println("Enc value must be less than 1000");
//Or Perhaps thow an exception
}//End if
}
public class Enc{
public static void main(String argv[]){
Base b = new Base();
b.setEnc(1001);
}//End of main
}
public(共有)
public修饰符可以应用于变量(域)或者类。它可能是你学习Java过程中最先接触的修饰符。想想HelloWorld.Java程序中被这样声明的类的代码
public class HelloWorld
这是因为Java虚拟机仅仅在一个声明为public的类中查找神奇的main启动方法。
public static void main(String argv[])
一个public类有全局的作用范围,一个实例可以在程序内部或外部的任意位置创建。任何文件中只能有一个非内部类可以用public关键字定义。如果你用public关键字在一个文件中定义了超过一个非内部类,编译器将会报错。
使用public修饰符定义一个变量可以使它在任何位置适用。使用方法如下:
public int myint =10;
如果你希望创建一个可以在任何地方修改的变量,你可以将它声明为public。你可以使用类似于调用方法那样的点号来访问它。
class Base {
public int iNoEnc=77;
}
public class NoEnc{
public static void main(String argv[]){
Base b = new Base();
b.iNoEnc=2;
System.out.println(b.iNoEnc);
}//End of main
}
注意,并不建议你对代码的接口和执行不加分隔的使用。如果你想改变iNoEnc的数据类型,你必须修改执行改变代码的每一部分。
protected(保护)
protected有一点古怪。一个protected变量在类,子类和同一个包内部可见,但不是全部可见。限制就是它在包内部的可见性可能超过你的预期。在同一路径下的类都是被默认为在一个包内,因此,protected类将会可见。这就意味着一个protected变量会比一个没有任何访问修饰符的变量更有可见性。
一个没有访问修饰符定义的变量称为它有默认的可见性。默认可见性是说一个变量可以在类内部可见,而包内的其他类中均不可见,不在同一个包的子类内也不可见。
静态的(static)
虽然static可以起到可见性修饰符的作用,但它不是直接的可见性修饰符。static修饰符可以应用于内部类,方法和变量。功能代码经常放在static方法中,例如Math类有完整的功能方法,如:random,sin和round。基本数据类型的包装类Integer,Double等等也有static方法处理包装过的基本数据类型,如返回符合字符串“2”的int值。
标记一个变量为static表明每个类只能有一个副本存在。这是与普通的情况相区别。一般情况下,一个类的每个实例都有一个整型变量的副本。在下面的非static int例子中,三个实例中的int iMyVal都有对应各自实例的不同值。
class MyClass{
public int iMyVal=0;
}
public class NonStat{
public static void main(String argv[]){
MyClass m1 = new MyClass();
m1.iMyVal=1;
MyClass m2 = new MyClass();
m2.iMyVal=2;
MyClass m3 = new MyClass();
m3.iMyVal=99;
//This will output 1 as each instance of the class
//has its own copy of the value iMyVal
System.out.println(m1.iMyVal);
}//End of main
}
下面的例子说明了当你有包含static整型数的类的多个实例时会发生什么
class MyClass{
public static int iMyVal=0;
}
public class Stat{
public static void main(String argv[]){
MyClass m1 = new MyClass();
m1.iMyVal=0;
MyClass m2 = new MyClass();
m2.iMyVal=1;
MyClass m3 = new MyClass();
m2.iMyVal=99;
//Because iMyVal is static, there is only one
//copy of it no matter how many instances
//of the class are created /This code will
//output a value of 99
System.out.println(m1.iMyVal);
}//End of main
}
你必须要忍受这样的事实,你不能在一个static方法内部访问一个非static变量。因此,下面的代码会引起一个编译时错误
public class St{
int i;
public static void main(String argv[]){
i = i + 2;//Will cause compile time error
}
}
一个static方法不能在一个子类中重写为非static方法
一个static方法不能在一个子类中重写为非static方法。同样,一个非static(普通的)方法也不能在子类中重写为static方法。但是同样的规则对方法重载没有作用。下面的代码在它尝试重写类的方法为非static方法amethod时将会引起一个错误。
class Base{
public static void amethod(){
}
}
public class Grimley extends Base{
public void amethod(){}//Causes a compile time error
}
IBM Jikes编译器会产生下面的错误
Found 1 semantic error compiling "Grimley.java":
6. public void amethod(){}
<------->
*** Error: The instance method "void amethod();"
cannot override the static method "void amethod();"
declared in type "Base"
static方法不能在子类中重写,但是可以被隐藏
在我的模拟测验中,我有一个问题问到static方法是否可以被重写,答案是不能,但是引来了大量的email,很多人举例说明static方法被重写了。在子类中,重写过程包括的不仅仅是简单的替代一个方法。它还包括运行时决定哪个方法被调用取决于它的引用类型。
这里有一个例子的代码,看起来显示了一个static方法被重写了
class Base{
public static void stamethod(){
System.out.println("Base");
}
}
public class ItsOver extends Base{
public static void main(String argv[]){
ItsOver so = new ItsOver();
so.stamethod();
}
public static void stamethod(){
System.out.println("amethod in StaOver");
}
}
这段代码会被编译并且输出"amethod in StaOver"
本地的(native)
native修饰符仅仅用来修饰方法,指明代码体不是用Java而是用C或C++所写。native方法经常为平台的特殊目的所写,例如访问某些Java虚拟机不支持的硬件。另一个原因是为了需要获得更好的性能。
一个native方法以一个分号结尾,而不是代码块。例如下面的代码将会调用一个可能用C++所写的外部程序:
public native void fastcalc();
luren04 回复于:2008-04-14 16:58:08
抽象(abstract)
粗略的看一下abstract修饰符显得很容易,但是也会漏掉它的一些隐含内容。属于主考者很喜欢问的那种狡猾的,关于那类修饰符的问题。
abstract修饰符可以被用在类和方法上。当用在方法上时,表明方法会没有方法体(也就是没有花括号的部分),并且代码只能在子类执行时运行。但是,还有一些关于何时何处你能拥有abstract方法的限制和包含这类方法的类的规则。如果一个类有一个或多个abstract方法,或者继承了不准备运行的abstract方法,则它必须声明为abstract。另外一个情况是,如果一个类实现了接口但是不准备运行接口的每个方法。但这种情况很少见。如果一个类有abstract方法,则它需要声明为abstract类不要认为一个abstract类不能有非abstract方法而感到心烦意乱。任何从abstract类继承而来的类都要实现基类的abstract方法,或者声明自身为abstract类。这些规则倾向于问你为什么想要创建abstract方法?
abstract类对于类的设计者很有用。它使类的设计者能够创建应当被实现的方法的原型,但是真正的实现留给以后使用这个类的人。下面的例子是一个包含abstract方法的abstract类。再次注意,类必须被声明为abstract,否则会出现编译时错误。
下面的类是abstract类,它会被正确编译并打印输出字符串
public abstract class abstr{
public static void main(String argv[]){
System.out.println("hello in the abstract");
}
public abstract int amethod();
}
常量(final)
final修饰符可以用在类,方法和变量上。它跟遗传关系的意思很相近,因此很容易记忆。一个final类可能从不被继承。另外一种想法是,一个final类不能作为父类。任何final类中的方法自动成为final方法。如果你不希望别的程序员“弄乱你的代码”,这是一个有效的方法。另一个好处就是效率,编译器对于一个final方法的工作很少。这些内容在Core Java的第一卷中有提及。
final修饰符表明方法不能被重写。因此,如果你在子类中有一个同样签名的方法的话,你会得到一个编译时错误。
下面的例子说明对一个类使用final修饰符。这段代码将会打印字符串"amethod"
final class Base{
public void amethod(){
System.out.println("amethod");
}
}
public class Fin{
public static void main(String argv[]){
Base b = new Base();
b.amethod();
}
}
一个final变量的值不能被改变,并且必须在一定的时刻赋值。这跟其他语言中的constant的思想比较相似。
同步的(Synchronized)
synchronized关键字被用来保证不只有一个的线程在同一时刻访问同一个代码块。参看第七部分关于线程的内容来了解更多的关于它的运行的知识。
瞬时(Transient)
transient修饰符是不常用的修饰符之一。它表明一个变量在序列化过程中不能被写出。
不稳定的(Volatile)
你可能对volatile关键字有疑问。最坏的情况就是你确认它真的是一个Java关键字。根据Barry Boone所说“它告诉编译器一个变量可能在线程异步时被改变”
接受它是Java语言的一部分,然后去担心别的吧。
联合使用修饰符
可见性修饰符不能被联合使用,一个变量不可能同时是private和public,public和protected,protected和private。你当然可以联合使用可见性修饰符和我在下面列表中提及的修饰符。
native
transient
synchronized
volatile
这样你就可以有一个public static native方法了。
修饰符可以用在哪里?
问题
问题1)当你试着编译运行下面的代码的时候,可能会发生什么?
abstract class Base{
abstract public void myfunc();
public void another(){
System.out.println("Another method");
}
}
public class Abs extends Base{
public static void main(String argv[]){
Abs a = new Abs();
a.amethod();
}
public void myfunc(){
System.out.println("My func");
}
public void amethod(){
myfunc();
}
}
1) The code will compile and run, printing out the words "My Func"
2) The compiler will complain that the Base class has non abstract methods
3) The code will compile but complain at run time that the Base class has non abstract methods
4) The compiler will complain that the method myfunc in the base class has no body, nobody at all to looove it
问题2)当你试着编译运行下面的代码的时候,可能会发生什么?
public class MyMain{
public static void main(String argv){
System.out.println("Hello cruel world");
}
}
1) The compiler will complain that main is a reserved word and cannot be used for a class
2) The code will compile and when run will print out "Hello cruel world"
3) The code will compile but will complain at run time that no constructor is defined
4) The code will compile but will complain at run time that main is not correctly defined
问题3)下面的哪个是Java修饰符?
1) public
2) private
3) friendly
4) transient
问题4) 当你试着编译运行下面的代码的时候,可能会发生什么?
class Base{
abstract public void myfunc();
public void another(){
System.out.println("Another method");
}
}
public class Abs extends Base{
public static void main(String argv[]){
Abs a = new Abs();
a.amethod();
}
public void myfunc(){
System.out.println("My func");
}
public void amethod(){
myfunc();
}
}
1) The code will compile and run, printing out the words "My Func"
2) The compiler will complain that the Base class is not declared as abstract.
3) The code will compile but complain at run time that the Base class has non abstract methods
4) The compiler will complain that the method myfunc in the base class has no body, nobody at all to looove it
问题5)你为什么可能会定义一个native方法呢?
1) To get to access hardware that Java does not know about
2) To define a new data type such as an unsigned integer
3) To write optimised code for performance in a language such as C/C++
4) To overcome the limitation of the private scope of a method
问题6)当你试着编译运行下面的代码的时候,可能会发生什么?
class Base{
public final void amethod(){
System.out.println("amethod");
}
}
public class Fin extends Base{
public static void main(String argv[]){
Base b = new Base();
b.amethod();
}
}
1) Compile time error indicating that a class with any final methods must be declared final itself
2) Compile time error indicating that you cannot inherit from a class with final methods
3) Run time error indicating that Base is not defined as final
4) Success in compilation and output of "amethod" at run time.
问题7)当你试着编译运行下面的代码的时候,可能会发生什么?
public class Mod{
public static void main(String argv[]){
}
public static native void amethod();
}
1) Error at compilation: native method cannot be static
2) Error at compilation native method must return value
3) Compilation but error at run time unless you have made code containing native amethod available
4) Compilation and execution without error
问题8)当你试着编译运行下面的代码的时候,可能会发生什么?
private class Base{}
public class Vis{
transient int iVal;
public static void main(String elephant[]){
}
}
1) Compile time error: Base cannot be private
2) Compile time error indicating that an integer cannot be transient
3) Compile time error transient not a data type
4) Compile time error malformed main method
问题9)当你试着编译运行下面的两个放在同一个目录的文件的时候,可能会发生什么?
//File P1.java
package MyPackage;
class P1{
void afancymethod(){
System.out.println("What a fancy method");
}
}
//File P2.java
public class P2 extends P1{
afancymethod();
}
1) Both compile and P2 outputs "What a fancy method" when run
2) Neither will compile
3) Both compile but P2 has an error at run time
4) P1 compiles cleanly but P2 has an error at compile time
问题10)下面的哪一个声明是合法的?
1) public protected amethod(int i)
2) public void amethod(int i)
3) public void amethod(void)
4) void public amethod(int i)
luren04 回复于:2008-04-14 16:58:59
答案
答案1)
1) The code will compile and run, printing out the words "My Func"
一个abstract类可以有非abstract方法,但是任何扩展它的类必须实现所有的abstract方法。
答案2)
4) The code will compile but will complain at run time that main is not correctly defined
main的签名包含一个String参数,而不是string数组。
答案3)
1) public
2) private
4) transient
虽然有些文本使用friendly来表示可见性,但它不是一个Java保留字。注意,测试很可能包含要求你从列表中识别Java关键字的问题。
答案4)
2) The compiler will complain that the Base class is not declared as abstract.
当我使用我的JDK1.1编译器时的真正的错误信息是:
Abs.java:1: class Base must be declared abstract.
It does not define void myfunc() from class Base.
class Base{
^
1 error
答案5)
1) To get to access hardware that Java does not know about
3) To write optimised code for performance in a language such as C/C++
虽然创建“纯正的Java”代码值得鼓励,但是为了允许平台的独立性,我们不能将此作为信仰,有很多时候,我们是需要native代码的。
答案6)
4) Success in compilation and output of "amethod" at run time.
这段代码调用Base类中的amethod版本。如果你在Fin中试着执行amethod的重写版本,你会得到一个编译时错误。
答案7)
4) Compilation and execution without error
因为没有调用native方法,因此运行时不会发生错误。
答案8)
1) Compile time error: Base cannot be private
一个Base类这样的顶级类不能定义为private。
答案9)
4) P1 compiles cleanly but P2 has an error at compile time
虽然P2在P1的同一个路径下,但是P1用package语句声明了,所以对于P2不可见。
答案10)
2) public void amethod(int i)
如果你认为选项3这样携带一个void参数是合法的,你可能需要从你的头脑中清空一些C/C++方面的知识。
选项4不合法是因为方法的返回类型必须紧跟着出现在方法名之前。
目标3,默认的构造方法
对于一个给定的类,如果有一个默认的构造方法被创建或者定义了构造方法的原型,则类也被确定了。
本目标需要注意
这是一个精致小巧的目标,通过轻松的俯瞰Java语言,对各方面集中研究来完成它吧。
什么是构造方法?
你需要通过明白构造方法的概念来明白本节的目标。简单来说,构造方法是一种在类实例化时自动运行的特殊类型的方法。构造器通常被用来初始化类中的值。构造器有和类同样的名字并且没有返回值。你可能会在测验中被问到这样的问题:跟类有同样名字的方法,但是有整型或者字符串型的返回值。你要多加小心并确信,任何被认为是构造方法的方法都是没有返回值的。
如果一个方法有了类同样的名字但还有返回值,它不是构造器。这里有一个例子,一个有构造器的类,当类的实例被创建时打印字符串“Greeting from Crowle”:
public class Crowle{
public static void main(String argv[]){
Crowle c = new Crowle();
}
Crowle(){
System.out.println("Greetings from Crowle");
}
}
何时Java提供默认构造方法?
如果你没有显式定义任何构造方法,编译器会插入一个“后台”的不可见的无参数的构造方法。一般来说,这只是在理论上很重要。但是,一个重要的限制作用是,如果你没有自己创建构造方法,你就只能得到默认的无参数的构造方法了。
如果你自己创建了构造方法,Java就不支持默认的无参数的构造方法了。
一旦你创建了自己的构造方法,你就释放了默认的无参数构造方法。如果接下来你想试着创建一个不传送任何参数的类的实例(也就是通过一个零参数构造方法调用这个类),你会得到一个错误。因此,一旦你为一个类创建了任何的构造方法,你需要创建一个无参数的构造方法。这也是像Borland/Inprise的JBuilder这样的代码产生器在你生成类的框架时会创建一个零参数构造方法的原因之一。
下面例子中的代码不会被编译。当编译器创建名字为c的Base类的实例时,它会插入一个指向无参数的构造方法的调用。由于Base有一个integer型的构造方法,无参数的构造方法此时不允许存在,一个编译期错误产生了。可以通过在Base类中创建一个“什么都不干”的零参数构造方法来修复这个错误。
//Warning: will not compile.
class Base{
Base(int i){
System.out.println("single int constructor");
}
}
public class Cons {
public static void main(String argv[]){
Base c = new Base();
}
}
//This will compile
class Base{
Base(int i){
System.out.println("single int constructor");
}
Base(){}
}
public class Cons {
public static void main(String argv[]){
Base c = new Base();
}
}
默认构造方法的原型
这个目标要求你明白默认构造方法的原型。它当然不能有参数,并且最明显的是默认构造方法没有指定范围,但你可以定义构造方法为public或者protected。
构造方法不能是native, abstract, static, synchronized或final
上面这句话源于一个编译错误信息。看起来像是新版本Java的错误信息质量得到提高了似的。我听说IBM的新Java编译器有好的错误报告。你也许被忠告过去使用多个合适版本的Java编译器来检查你的代码并查找错误。
问题
问题1) 给定下面的类定义
class Base{
Base(int i){}
}
class DefCon extends Base{
DefCon(int i){
//XX
}
}
如果将标记//XX的地方替换为下面的行,哪一行是独立合法的?
1) super();
2) this();
3) this(99);
4)super(99);
问题2)给定下面的类
public class Crowle{
public static void main(String argv[]){
Crowle c = new Crowle();
}
Crowle(){
System.out.println("Greetings from Crowle");
}
}
构造方法会返回哪一种数据类型?
1) null
2) integer
3) String
4) no datatype is returned
问题3)当你试着编译运行下面的代码的时候,可能会发生什么?
public class Crowle{
public static void main(String argv[]){
Crowle c = new Crowle();
}
void Crowle(){
System.out.println("Greetings from Crowle");
}
}
1) Compilation and output of the string "Greetings from Crowle"
2) Compile time error, constructors may not have a return type
3) Compilation and output of string "void"
4) Compilation and no output at runtime
问题4)当你试着编译运行下面的类的时候,可能会发生什么?
class Base{
Base(int i){
System.out.println("Base");
}
}
class Severn extends Base{
public static void main(String argv[]){
Severn s = new Severn();
}
void Severn(){
System.out.println("Severn");
}
}
1) Compilation and output of the string "Severn" at runtime
2) Compile time error
3) Compilation and no output at runtime
4) Compilation and output of the string "Base"
问题5)下面的哪一句陈述是正确的?
1) The default constructor has a return type of void
2) The default constructor takes a parameter of void
3) The default constructor takes no parameters
4) The default constructor is not created if the class has any constructors of its own.
答案
答案1)
4) super(99);
由于类Base定义了一个构造方法,编译器将不会插入默认的0参数的构造方法。因此,super()的调用会引起一个错误。一个this()调用试着在当前类中调用一个不存在的0参数构造方法,this(99)调用会引起一个循环引用并将引起一个编译时错误。
答案2)
4) no datatype is returned
如果定义了一个没有数据类型的构造方法,那么没有返回类型是相当明显的
答案3)
4) Compilation and no output at runtime
方法Crowle因为有一个返回类型而不是构造方法。因此,类将会编译并且在运行时方法Crowle不会调用。
答案4)
2) Compile time error
当类Severn试着在类Base中调用0参数构造方法时会产生一个错误。
答案5)
3) The default constructor takes no parameters
4) The default constructor is not created if the class has any constructors of its own.
选项1相当明显,因为构造方法不会有返回类型。选项2不容易确定,Java没有为方法或构造方法提供void类型。
目标四,重载和覆写
为任意方法定义合法的返回类型,这些方法是在本类或父类中声明过的相关方法。
本目标需要注意的
这个目标可能相当模糊,它主要是要求你理解重载和覆写的不同。为了增强你的目的性,你需要对于方法重载和方法覆写有基本的理解。请参看第六部分:
重载,覆写,运行时类型和面向对象
同一个类中的方法
我假定目标中的相关方法是指有同样名字的方法。如果一个类中的两个或者多个方法有同样的名字,就被称为方法重载。你可以在一个类中有两个同样名字的方法,但是他们必须有不同的参数类型和顺序。
通过参数的顺序和类型来区分两个重载的方法。返回类型对区分方法没有帮助。
下面的代码会引起一个编译时错误:编译器认为amethod试图定义同样的方法两次。这就引起了一个像下面这样的错误,
method redefined with different return type: void amethod(int)
was int amethod(int)
class Same{
public static void main(String argv[]){
Over o = new Over();
int iBase=0;
o.amethod(iBase);
}
//These two cause a compile time error
public void amethod(int iOver){
System.out.println("Over.amethod");
}
public int amethod(int iOver){
System.out.println("Over int return method");
return 0;
}
}
返回值的类型不能帮助区分两个方法。
子类中的方法
你可以在一个子类中重载一个方法,所需要的就是新方法有不同的参数顺序和类型。参数的名字或者返回类型都不作考虑。
如果你想重写一个方法,即在子类中完全取代它的功能,重写后的方法必须跟基类中被取代的原始方法有完全相同的签名。这就包括了返回值。如果你在子类中创建了一个有同样名字和签名但是有不同返回值的方法,你将会得到一个跟上例同样的错误信息:
method redefined with different return type: void amethod(int)
was int amethod(int)
编译器认为这是错误的尝试方法重载,而不认为是方法重写。static方法不能被重写。
如果你认为重写只是在子类中简单的替换了一个方法,你就很容易认为static方法也能被重写。事实上,我有很多包含人们举例指明static方法能被重写的代码的邮件。然而,这些并没有考虑方法重写在运行时决定哪个版本的方法被调用的细节问题。下面的代码似乎表明static方法是怎样被重写的。
class Base{
static void amethod(){
System.out.println("Base.amethod");
}
}
public class Cravengib extends Base{
public static void main(String arg[]){
Cravengib cg = new Cravengib();
cg.amethod();
}
static void amethod(){
System.out.println("Cravengib.amethod");
}
}
如果你编译并运行这段代码,你会发现输出文本Cravengib.amethod,这似乎很好的指明了重写。然而,对于重写,还有相对于在子类中使用一个方法简单替换另一个方法更多的东西。还有运行时决定的方法基于引用的类的类型的问题,这可以通过创建正在被实例化的类的引用类型(实例初始化语句的左半部分)来说明。
在上面的例子中,因为名字叫amethod的方法与类发生了关联,而不是与特定的类的实例相关联,它不在乎什么类型的类正在创建它,而仅仅在意引用的类型。因此,如果你在调用amethod前改变一下这一行,
Base cg= new Cravengib()
你就会发现当你运行程序时,你会得到输出:Base.amethod
cg是一个类Cravengib在内存中的一个Base类型的实例的引用(或者指针)。如果一个static方法被调用了,JVM不会检查什么类型正在指向它,它只会调用跟Base类相关联的方法的实例。
与上面的情况相对比:当一个方法被重写时,JVM通过句柄检查正在指向的类的类型,并调用此类型相关的方法。可以结束这个例子了,如果你将两个版本的amethod方法改变为非static,并依然创建类:
Base cg= new Cravengib()
编译并运行上述代码,你会发现amethod已经被重写了,并且输出Cravengib.amethod。
问题
问题1)给定下面的类定义
public class Upton{
public static void main(String argv[]){
}
public void amethod(int i){}
//Here
}
下面哪一个在替换//Here后是合法的?
1) public int amethod(int z){}
2) public int amethod(int i,int j){return 99;}
3) protected void amethod(long l){ }
4) private void anothermethod(){}
问题2)给定下面的类定义
class Base{
public void amethod(){
System.out.println("Base");
}
}
public class Hay extends Base{
public static void main(String argv[]){
Hay h = new Hay();
h.amethod();
}
}
下面在类Hay中的哪一个方法将会编译并使程序打印出字符串"Hay"?
1) public int amethod(){ System.out.println("Hay");}
2) public void amethod(long l){ System.out.println("Hay");}
3) public void amethod(){ System.out.println("Hay");}
4) public void amethod(void){ System.out.println("Hay");}
问题3)给定下面的类定义
public class ShrubHill{
public void foregate(String sName){}
//Here
}
下面的哪一个方法可以合法的直接替换//Here?
1) public int foregate(String sName){}
2) public void foregate(StringBuffer sName){}
3) public void foreGate(String sName){}
4) private void foregate(String sType){}
答案
答案1)
2) public int amethod(int i, int j) {return 99;}
3) protected void amethod (long l){}
4) private void anothermethod(){}
选项1由于两个原因不会被编译。第一个相当明显,因为它要求返回一个integer。另一个是试着直接在类内部重新定义一个方法。把参数的名字从i换成z是无效的,并且一个方法不能在同一个类里重写。
答案2)
3) public void amethod(){ System.out.println("Hay");}
选项3重写了类Base的方法,因此任何0参数调用都调用这个版本。
选项1将会返回一个表示你尝试重新定义一个不同返回类型的方法的错误。选项2将会编译对于amethod()调用Base类的方法,并且输出字符串"Base"。选项4是为了抓住满脑子C/C++的人而设计的。Java里没有void方法参数这样的事。
答案3)
2) public void foregate(StringBuffer sName){}
3) public void foreGate(String sName){}
选项1是试着定义一个方法两次,有一个int返回值并不能帮助将它与存在的foregate方法相区分。而像选项4那样改变方法的参数名,也不能与存在的方法相区分。注意,选项2里的foreGate方法有一个大写的G。
luren04 回复于:2008-04-14 16:59:44
目标一 if和switch语句
用if和switch编写代码,识别这些语句的合法参数类型
If/else语句
在java中If/else结构和你所了解的其他语言一样,switch/case语句有一些自己的特点
if/else的语法是
if(boolean condition){
//the boolean was true so do this
}else {
//do something else
}
与在Visual Basic语句中不同,Java中不存在"then"关键字
花括号在Java中是一个常用的复合语句的指示器,它可以使你把多行代码作为一些判断语句的一个结果来执行。这可以被看作一个程序块。else部分常常是可选的。你可以像以下这样链接多个if/else语句(但是在链接了几个之后你就要考虑使用case结构来代替了)
int i=1;
if(i==1){
//some code
} else if (i==2){
//some code
} else{
//some code
}
Java中if语句的一个特性是必须带一个boolean类型的值。你不能像使用C/C++习惯的那样使用任何非零的数值来表示true,而用零来表示false。
因此,在Java中以下语句将不会被编译
int k =-1;
if(k){//Will not compile!
System.out.println("do something");
}
因为你必须明确的使k的判断语句返回一个boolean类型的值,就像下面的例子
if(k == -1){
System.out.println("do something"); //Compiles OK!
}
当在C/C++中时,你可以去掉花括号,如下
boolean k=true;
if(k)
System.out.println("do something");
这有时候被认为是不好的设计风格,因为如果你稍后要修改代码来包含更多语句,他们就会在条件语句块外,像这样
if(k)
System.out.println("do something");
System.out.println("also do this");
第二个输出语句将总会被执行
switch语句
Peter van der Lindens 对于switch语句的评价概括起来就像他所说的“毁灭于switch语句”因此,这是一个你必须花费更多的精力关注的问题。switch语句的参数必须是一个byte,char,short或int类型的变量。你也许会遇到考试题中用float或者long做switch语句的参数。有一个非常普遍的问题似乎就是,关于在执行switch语句的过程中使用break语句。这里有一个这类问题的例子。
int k=10;
switch(k){
case 10:
System.out.println("ten");
case 20:
System.out.println("twenty");
}
常识判断,执行case语句后面的指令,然后碰到另一个case语句,编译器就应该结束执行switch语句。但是,就像程序设计者所熟知的,case语句只在碰到break语句的时候才终止执行。结果,在上面例子中,ten和twenty都将被输出。可以作为一个问题提出来的另一个小的特性就是使用default语句。
注意:default语句不是必须在case语句的结尾处出现
按照惯例default语句是放在case选项的结尾处,所以通常代码写成如下形式
int k=10;
switch(k){
case 10:
System.out.println("ten");
break;
case 20:
System.out.println("twenty");
break;
default:
System.out.println("This is the default output");
}
这种方法反映大多数人的思维方式。当你尝试其他可能情况时,会执行default输出。但是,如果不是被要求的话,把defalt语句写在switch语句的顶部,在语法上也是正确的。
int k=10;
switch(k){
default: //Put the default at the bottom, not here
System.out.println("This is the default output");
break;
case 10:
System.out.println("ten");
break;
case 20:
System.out.println("twenty");
break;
}
if和switch语句的合法参数
正如先前所提到的,if语句只能用boolean类型参数,而switch语句只能用byte,char,short或者int类型作参数。
三项 ?操作符
一些程序员主张三项操作符很有用。我不这么认为。在目标中并没有特别提到它,所以如果在考试中出现的话请告诉我。
其他流程控制语句
虽然公布的目标只提到了if/else和case语句,考试中也许会涉及do/while和while loop语句。
练习
习题1)
创建一个文件含有一个公共类叫IfElse。创建一个方法叫go,它接收main方法的字符串数组参数作为它的参数。在这个方法中创建了一个if/else程序块,用来查看来自数组的第一个元素,用字符串的equals方法来判断输出。如果为"true"则打印"ok",如果为"false"则打印"Not ok",如果是true或false以外的字符串则打印"Invalid command parameter",用一个if/else if/else语句这样的次序进行设计。
习题 2)
修改这个IfElse类,使if语句可以检查传到go方法的字符串数组是否是零长度串,使用数组length域来检查。如果长度为零则输出"No parameter supplied",把现有的if/else if/else块放在这个练习的else中,使程序能实现原版本的功能。
答案 1)
public class IfElse{
public static void main(String argv[]){
IfElse ie = new IfElse();
ie.go(argv);
}
public void go(String[] sa){
String s = sa[0];
if(s.equals("true")){
System.out.println("OK");
}else if(s.equals("false")){
System.out.println("Not OK");
}else{
System.out.println("Invalid command parameter");
}
}
}
答案 2)
public class IfElse{
public static void main(String argv[]){
IfElse ie = new IfElse();
ie.go(argv);
}
public void go(String[] sa){
if(sa.length ==0){
System.out.println("No parameter supplied");
}else{
String s = sa[0];
if(s.equals("true")){
System.out.println("OK");
}else if(s.equals("false")){
System.out.println("Not OK");
}else{
System.out.println("Invalid command parameter");
}
}
}
}
问题
问题1) 编译运行下列代码时会发生什么情况?
public class MyIf{
boolean b;
public static void main(String argv[]){
MyIf mi = new MyIf();
}
MyIf(){
if(b){
System.out.println("The value of b was true");
}
else{
System.out.println("The value of b was false");
}
}
}
1) Compile time error variable b was not initialised
2) Compile time error the parameter to the if operator must evaluate to a boolean
3) Compile time error, cannot simultaneously create and assign value for boolean value
4) Compilation and run with output of false
问题2) 编译运行下列代码时会发生什么情况?
public class MyIf{
public static void main(String argv[]){
MyIf mi = new MyIf();
}
MyIf(){
boolean b = false;
if(b=false){
System.out.println("The value of b is"+b);
}
}
}
1) Run time error, a boolean cannot be appended using the + operator
2) Compile time error the parameter to the if operator must evaluate to a boolean
3) Compile time error, cannot simultaneously create and assign value for boolean value
4) Compilation and run with no output
问题3 ) 编译运行下列代码时会发生什么情况?
public class MySwitch{
public static void main(String argv[]){
MySwitch ms= new MySwitch();
ms.amethod();
}
public void amethod(){
char k=10;
switch(k){
default:
System.out.println("This is the default output");
break;
case 10:
System.out.println("ten");
break;
case 20:
System.out.println("twenty");
break;
}
}
}
1) None of these options
2) Compile time error target of switch must be an integral type
3) Compile and run with output "This is the default output"
4) Compile and run with output "ten"
问题4) 编译运行下列代码时会发生什么情况?
public class MySwitch{
public static void main(String argv[]){
MySwitch ms= new MySwitch();
ms.amethod();
}
public void amethod(){
int k=10;
switch(k){
default: //Put the default at the bottom, not here
System.out.println("This is the default output");
break;
case 10:
System.out.println("ten");
case 20:
System.out.println("twenty");
break;
}
}
}
1) None of these options
2) Compile time error target of switch must be an integral type
3) Compile and run with output "This is the default output"
4) Compile and run with output "ten"
问题5) 下面哪个是不能用于switch语句的参数?
1) byte b=1;
2) int i=1;
3) boolean b=false;
4) char c='c';
答案
答案 1)
4) Compilation and run with output of false
因为boolean b在类级中被创建,它不需明确初始化,而且它有默认的boolean值false。if语句判断一个boolean值,所以b符合这个要求。
答案 2)
4) Compilation and run with no output
因为b是boolean类型,if语句不会产生错误。如果b是任何其他的数据类型,在你试图赋值而不是比较的时候错误就产生了。下列表达
if(b=false)
通常是一个程序员的错误。程序员大多要表现
if (b==false)
如果b的类型是boolea以外的任意类型,会导致编译期错误。if表达式的要求是必须返回一个boolean类型,因为
(b=false )
返回一个boolean类型,所以被接受(如果无用处)。
答案 3)
4) Compile and run with output "ten"
答案 4)
1) None of these options
因为下句后缺少break语句
case 10;
实际输出结果会是"ten"接着是"twenty"
答案 5)
1) byte b=1;
2) int i=1;
4) char c='c';
switch语句可以使用byte,char或int作参数。
luren04 回复于:2008-04-14 17:00:29
目标二 循环,break和continue
用循环格式编写代码,使用带标签和不带标签的break和continue语句,声明循环计数器的值在循环执行中或循环结束时
for语句
最常用的循环方法就是应用for语句。对于for语句在其他的编程语言中有非常相似的结构。比如C/C++和perl就有for结构。很多程序员在循环中使用for结构,因为其简洁,自含,容易理解而且不容易混乱。类似C++而与C语言不同,循环控制变量可以在for语句中定义和初始化。如下
public class MyLoop{
public static void main(String argv[]){
MyLoop ml = new MyLoop();
ml.amethod();
}
public void amethod(){
for(int K=0;K<5l;K++){
System.out.println("Outer "+K);
for(int L=0;L<5;L++)
{System.out.println("Inner "+L);}
}
}
}
内循环代码在每次外循环执行时会循环执行五次。所以输出为:
Outer 0;
Inner 0
Inner 1
Inner 2
Inner 3
inner 4
Outer 1;
Inner 0
Inner 2
for语句和Visual Basic的for/next循环一样。你可以认为它的语法是
for(initialization; conditional expression;increment)
其中条件表达式必须是boolean判断,就像if语句的简单形式。在上例的代码中,for语句紧跟着是花括号中的程序块。类似if语句,当不需要使用程序块时,你可以使用下面这样的简单形式
for(int i=0;i<5;i++)
System.out.println(i);
在任何版本中你都不能用分号来结束一个for行,如果你这么做,for循环就会原地打转直到条件满足,然后就会以“直线”的方式执行下面的代码。在此例中你不是必须在for循环中定义变量,但是如果在循环中定义变量,当跳出循环时变量也就跳出了它的作用域。按照变量作用域尽可能小的说法,这可以看作是一个优点。
for中的块为空在语法中也是正确的,这样循环会永远进行下去
for(;;){
System.out.println("forever");
}
但是用while(true)的形式可能会更加简洁
while(true){
System.out.println("true");
}
while循环和do循环,意料之中
while和do循环的运行就像你想象的一样,和在其他语言中相同。
因此,while会依照判断执行零到多次,而do会执行一到多次。while循环的语法是:
while(condition){
bodyOfLoop;
}
像if语句一样,条件是一个boolean类型的判断。同样,你不能像C/C++习惯的那样用零来代表false,而用任意其他值来代表true。
所以,你可能会像下面那样创建一个while循环
while(i<4){
i++;
System.out.println("Loop value is :"i);
}
注意,如果变量i为4,或者比4大,当你到达while语句时,将没有输出。相反,do循环总是会执行一次。所以,不管进入循环时变量i的值是什么,以下代码总是会得到至少一个输出。
goto语句,科学还是迷信?
Java的设计者决定同意写过著名文章"Goto 有害"的编程领袖Edsger Dijkstra的观点。因为不加选择的使用goto语句会导致“意大利面条似的代码”难以维护,不可使用,而且这被认为是不好的编程风格。“意大利面条似的代码”是指不容易表述逻辑开始和结束的代码。goto语句有时会被说成“无条件跳转”,也就是可能会写这样的代码,不进行判断就从程序的一部分跳转到另外一处。这在某些情况下是有用的,即Java为break和continue关键字提供了有标签和无标签两个版本。
public class Br{
public static void main(String argv[]){
Br b = new Br();
b.amethod();
}
public void amethod(){
for(int i=0;i <3;i ++){
System.out.println("i"+i+"\n");
outer://<==Point of this example
if(i>2){
break outer;//<==Point of this example
}//End of if
for(int j=0; j <4 && i<3; j++){
System.out.println("j"+j);
}//End of for
}//End of for
}//end of Br method
}
然后,你需要挑出代码中哪个是要输出的字母组合。顺便说一下,"\n"是输出一个空白行。
跳转到标签
在有些条件下从内循环跳到外循环常被描述,你可以使用带标签的break和continue语句来实现它。一个标签是一个简单的非关键字,后面跟一个冒号。通过在break或continue后使用标签,你的代码可以跳转到此标签处。这是便捷的实现部分条件循环的方法。你当然可以用if语句,但是一个break语句更方便。按照Elliotte Rusty Harold,一个著名的Java作者所说,“在整个Java1.0.1源代码中,只用了七个continue语句写java包。”这意味着在实际编程中你可能不会得到充分的练习,所以为了考试你要花费更大的精力来学好它。考试题的编写者好像热爱设计费解的网状的带有break和continue语句的循环,你可能永远不会遇到有好的设计的代码。
关键概念
break语句完全放弃执行当前循环,continue语句只放弃整个循环中当前本次循环
做下面的例子
public class LabLoop{
public static void main(String argv[]){
LabLoop ml = new LabLoop();
ml.amethod();
}
public void amethod(){
outer:
for(int i=0;i<2;i++){
for(int j=0;j<3;j++){
if(j>1)
//Try this with break instead of continue
continue outer;
System.out.println("i "+ i + " j "+j);
}
}//End of outer for
System.out.println("Continuing");
}
}
这个版本有以下输出
i 0 j 0
i 0 j 1
i 1 j 0
i 1 j 1
Continuing
如果你用break替换continue,i计数器会在零处停止,因为外循环会被放弃,而不会简单的进入下一个递增。
问题
问题1) 编译运行一个方法中的下列代码时会发生什么情况?
for(int i=0;i<5;){
System.out.println(i);
i++;
continue;
}
1) Compile time error, malformed for statement
2) Compile time error continue within for loop
3) runtime error continue statement not reached
4) compile and run with output 0 to 4
问题2)编译运行下列代码时会发生什么情况?
public class LabLoop{
public static void main(String argv[]){
LabLoop ml = new LabLoop();
ml.amethod();
mainmethod:
System.out.println("Continuing");
}
public void amethod(){
outer:
for(int i=0;i<2;i++){
for(int j=0;j<3;j++){
if(j>1)
break mainmethod;
System.out.println("i "+ i + " j "+j);
}
}//End of outer for
}
}
1)
i 0 j 0
i 0 j 1
Continuing
2)
i 0 j 0
i 0 j 1
i 1 j 0
i 1 j 1
Continuing
3)
Compile time error
4)
i 0 j 0
i 0 j 1
i 1 j 0
i 1 j 1
i 2 j 1
Continuing
问题3)编译运行下列代码时会发生什么情况?
public void amethod(){
outer:
for(int i=0;i<2;i++){
for(int j=0;j<2;j++){
System.out.println("i="+i + " j= "+j);
if(i >0)
break outer;
}
}
System.out.println("Continuing with i set to ="+i);
}
1) Compile time error
2)
i=0 j= 0
i=0 j= 1
i=1 j= 0
3)
i=0 j= 0
i=0 j= 1
i=1 j= 0
i=2 j= 0
4)
i=0 j= 0
i=0 j= 1
问题4)编译运行下列代码时会发生什么情况?
int i=0;
while(i>0){
System.out.println("Value of i: "+i);
}
do{
System.out.println(i);
} while (i <2);
}
1)
Value of i: 0
followed by
0
1
2
2)
0
1
2
3)
Value of i: 0
Followed by continuous output of 0
4) Continuous output of 0
问题5) 编译运行下列代码时会发生什么情况?
public class Anova{
public static void main(String argv[]){
Anova an = new Anova();
an.go();
}
public void go(){
int z=0;
for(int i=0;i<10; i++,z++){
System.out.println(z);
}
for(;;){
System.out.println("go");
}
}
}
1) Compile time error, the first for statement is malformed
2) Compile time error, the second for statement is malformed
3) Output of 0 to 9 followed by a single output of "go"
4) Output of 0 to 9 followed by constant output of "go"
问题6)下列代码的输出结果是什么?
public class MyFor{
public static void main(String argv[]){
int i;
int j;
outer:
for (i=1;i <3;i++)
inner:
for(j=1; j<3; j++) {
if (j==2)
continue outer;
System.out.println("Value for i=" + i + " Value for j=" +j);
}
}
}
1) Value for i=1 value for j=1
2) Value for i=2 value for j=1
3) Value for i=2 value for j=2
4) Value for i=3 value for j=1
答案
答案 1)
4) compile and run with output 0 to 4
这是一个很奇怪但是完全正确的语句
答案 2)
3) Compile time error
你不能武断的跳入另一个方法,在goto语句中会带来很多有害的结果。
答案 3)
1) Compile time error
这实际上不是关于break和continue的问题。这段代码不会被编译,因为变量对for循环外部来说永远是不可见的。所以最后的System.out.println语句会引起编译时错误。
答案 4)
1) Continuous output of 0
没有值被增加,而且如果第一次判断不为真时while循环将不会执行。
答案 5)
4) Output of 0 to 9 followed by constant output of "go"
第一个for循环结构不常用但是完全正确。
答案六
1) Value for i=1 value for j=1
2) Value for i=2 value for j=1
luren04 回复于:2008-04-14 17:01:22
目标三 try/catch和方法重写
编写代码合理使用异常和异常处理机制(try catch finally),定义和重写方法抛出异常.
一个异常情况是当程序进入一个不是很正常的状态.异常捕获有时是指错误捕获.一个典型的异常例子是当程序试图打开一个不存在的文件时或者你试图访问一个数组中不存在的元素时.
try和catch语句是构建Java异常处理的一部份.不论C/C++还是Visua Basic都没有直接对应Java异常处理的结构.C++支持异常,但是是可选的,Visial Basic支持On Error/Goto 错误捕获,这带有早期不灵活的BASIC编程时代的味道。
Java异常是Java语言的一个结构。例如如果你要执行I/O操作,你必须把它放在错误处理中。你当然可以不把它放在处理中,这毫无作用。下面是一个小代码片断,我用Borland/Inprise JBuilder临时停止控制台输出,等待按任意键继续
public class Try{
import java.io.*;
public static void main(String argv[]){
Try t = new Try();
t.go();
}//End of main
public void go(){
try{
InputStreamReader isr = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(isr);
br.readLine();
} catch(Exception e){
/*Not doing anything when exception occurs*/
} //End of try
System.out.println("Continuing");
}//End of go
}
在这个例子中,错误出现时没有任何处理,但是程序员一定知道错误有可能发生。如果你移去try和catch字句,代码将完全不会被编译。编译器知道I/O方法会引发异常而且需要异常处理代码。
与Visal Basic和C/C++比较
Visal Basic或C/C++允许抛出混合“快且脏”的程序,假装没有错误发生过,Java比它们严格些。记得DOS的最初版本被他的创作者叫做QDOS,因为是快且脏的DOS,看看我们已经在这样的环境下生活了多久。当你开始把快且脏的程序放到try/catch块当中,也就开始了真正的错误跟踪。这不是完全的束缚和编程律条,这只是劝说你“做正确的事”。
方法重写,抛出异常
在子类中一个重写的方法可能只抛出父类中声明过的异常或者异常的子类。这只适用于方法重写而不适用于方法重载。所以如果如果一个方法有完全相同的名称和参数,它只能抛出父类中声明过的异常或者异常的子类。但是它抛出很少或者不抛出异常。所以下面的例子将不被编译
import java.io.*;
class Base{
public static void amethod()throws FileNotFoundException{}
}
public class ExcepDemo extends Base{
//Will not compile, exception not in base version of method
public static void amethod()throws IOException{}
}
如果在父类中有抛出IOException异常的方法,在子类中的方法抛出FileNotFoundException,代码将编译通过。再次,记住只适用于方法重写,在方法重载中没有类似规定。一个在子类中重写的方法可能会抛出异常。
throw子句
我们在代码中需要包含可能抛出异常的try/catch块的一个原因就是,你的代码可以开始展现出什么可能发生,而不是什么应该发生。你可以通过使用throws字句作为方法声明的一部分来把异常放到堆栈中。这就有效的说明“当一个错误发生时,这个方法抛出这个异常,并且这个异常必须被调用它的方法捕获”。
这有一个使用throw子句的例子
import java.io.*;
public class Throws{
public static void main(String argv[]){
Throws t = new Throws();
try{
t.amethod();
}catch (IOException ioe){}
}
public void amethod() throws IOException{
FileInputStream fis = new FileInputStream("Throws.java");
}
}
问题
问题 1) 编译运行以下代码会发生什么情况?
import java.io.*;
class Base{
public static void amethod()throws FileNotFoundException{}
}
public class ExcepDemo extends Base{
public static void main(String argv[]){
ExcepDemo e = new ExcepDemo();
}
public static void amethod(){}
protected ExcepDemo(){
try{
DataInputStream din = new DataInputStream(System.in);
System.out.println("Pausing");
din.readChar();
System.out.println("Continuing");
this.amethod();
}catch(IOException ioe) {}
}
}
1) Compile time error caused by protected constructor
2) Compile time error caused by amethod not declaring Exception
3) Runtime error caused by amethod not declaring Exception
4) Compile and run with output of "Pausing" and "Continuing" after a key is hit
问题2) 编译运行以下代码会发生什么情况?
import java.io.*;
class Base{
public static void amethod()throws FileNotFoundException{}
}
public class ExcepDemo extends Base{
public static void main(String argv[]){
ExcepDemo e = new ExcepDemo();
}
public static void amethod(int i)throws IOException{}
private ExcepDemo(){
try{
DataInputStream din = new DataInputStream(System.in);
System.out.println("Pausing");
din.readChar();
System.out.println("Continuing");
this.amethod();
}catch(IOException ioe) {}
}
}
1) Compile error caused by private constructor
2) Compile error caused by amethod declaring Exception not in base version
3) Runtime error caused by amethod declaring Exception not in base version
4) Compile and run with output of "Pausing" and "Continuing" after a key is hit
问题3) 编译运行以下代码会发生什么情况?
import java.io.*;
class Base{
public static void amethod()throws FileNotFoundException{}
}
public class ExcepDemo extends Base{
public static void main(String argv[]){
ExcepDemo e = new ExcepDemo();
}
public static void amethod(int i)throws IOException{}
private boolean ExcepDemo(){
try{
DataInputStream din = new DataInputStream(System.in);
System.out.println("Pausing");
din.readChar();
System.out.println("Continuing");
this.amethod();
return true;
}catch(IOException ioe) {}
finally{
System.out.println("finally");
}
return false;
}
}
1) Compilation and run with no output.
2) Compilation and run with output of "Pausing", "Continuing" and "finally"
3) Runtime error caused by amethod declaring Exception not in base version
4) Compile and run with output of "Pausing" and "Continuing" after a key is hit
问题4) 以下哪个要求程序员添加外部的try/catch异常处理。
1)Traversing each member of an array
2) Attempting to open a file
3) Attempting to open a network socket
4) Accessing a method in other class
问题5) 编译运行以下代码会发生什么情况?
import java.io.*;
class granary{
public void canal() throws IOException{
System.out.println("canal");
}
}
public class mmill extends granary{
public static void main(String argv[]){
System.out.println("mmill");
}
public void canal(int i) throws Exception{
System.out.println("mmill.canal");
}
public void canal(long i) {
System.out.print("i");
}
}
1) Compile time error
2) Runtime errors
3) Compile error, mmill version of canal throws Exception not in granary version
4) Compilation and run with output of mmill
答案
问题1) 答案
4) Compile and run with output of "Pausing" and "Continuing" after a key is hit
在子类中的重写方法不能抛出在基类中没有抛出的异常。在这个例子中的方法amethod没有抛出异常,所以编译不会出现问题。构造器不能是protect类型的。
问题2) 答案
4) Compile and run with output of "Pausing" and "Continuing" after a key is hit
在这个版本中amethod被重写了,没有限制抛出或不抛出异常。
问题3) 答案
1) Compilation and run with no output.
好的,我有点跑题了,注意构造器有一个返回值。这把它变成了一个普通方法,而且当没有实例被创建时它将不会被调用。
问题4) 答案
2) Attempting to open a file
3) Atempting to open a network socket
通常来说,所有的I/O操作都需要外在的使用try/catch块的异常处理。JDK1.4考试不明确的覆盖I/O,但是也许会提到错误处理的内容。
问题5) 答案
4) Compilation and run with output of mmill
什么样的异常可以被抛出的限制只是应用于被重写的方法,不用于被重载的方法。因为canal方法在mmill版本中被重载(也就是它带有了不同的参数类型),所以不会有编译或运行错误。
luren04 回复于:2008-04-14 17:01:55
目标四 什么情况下产生异常
识别发生在代码片断指定位置的异常产生的结果。注意:异常必须是运行时异常,一个被检查的异常或者一个错误(代码可能包括try,catch或者finally子句,在任何可能的组合中)
目标注释
这个目标要求你理解可控的和不可控异常(一种你要写代码捕获,另一种不用),理解finally子句如何工作。
检查和非检查异常
虽然Java强调你把捕获异常代码插入到他们可能发生的地方像I/O操作等,这样比较方便,但是如果你必须把这些代码插入到程序员应该控制程序状态的地方,就不方便了。这种情况的一个例子就是遍历数组的每一个元素。Java中一个优美的地方就是它不需要程序员的介入而明确的报告这种异常类型的方式。这种自动异常处理是由把异常分为可控和不可控异常实现的。像内存耗尽或者访问到数组末尾这种情况会自动识别,而试图打开不存在的文件就需要明确的try/catch异常捕获。
默认的非检查信息
非检查异常出现的一个默认结果就是一个信息会被发送到控制台。例如下面代码
public class GetArg{
public static void main(String argv[]){
System.out.println(argv[0]);
}
如果编译运行代码而不输入命令行参数,你会在控制台得到一个错误信息
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException
at GetArg.main(GetArg.java:3)
同时这种情况是可以用于学习目的编写的程序的,在实际程序中,用户大概不会访问控制台,也不会理解这样的信息。最好编写代码来检查可能产生非运行异常的数值。所以代码可以修改成
public class GetArg{
public static void main(String argv[]){
if(argv.length ==0){
System.out.println("Usage: GetArg param");
}else{
System.out.println(argv[0]);
}
}
}
检查异常
要求程序员编写代码处理检查异常。如果没有代码处理检查异常可能会出现编译不通过。就像前面的代码带有try/catch块结构,但是写一个空catch块,不对产生的异常进行任何处理也是可以的。当然这通常不是好的设计,当你碰到要在异常时编写代码的问题时,你可能还是要在这些代码中做些有用的事。在catch块常做的两件事是产生出错信息和打印错误跟踪。异常系统提供一个非常方便的方法,通过getMessage方法产生常见的有意义的错误信息。
看看以下代码
import java.io.*;
public class FileOut{
public static void main(String argv[]){
try{
FileReader fr = new FileReader("FileOut.txt");
}catch(Exception e){
System.out.println(e.getMessage());
}
}
}
如果你在一个没有FileOut.txt文件的目录中编译运行这段代码,你会得到一个错误信息
FileOut.txt (No such file or directory)
finally子句
在考试中你可能会被问到,在什么情况try/catch子句后的finally方法会被执行。简单回答就是finally子句总是会被执行,甚至当你觉得它可能不会被执行时。所以说,try/catch/finally语句的执行顺序是你应该注意确认它在什么情况下是怎么执行的。
关键概念
不论在try/catch部分是不是有返回,try/catch块的finally子句总会执行。
少数情况下当有如下调用时,finally子句不会被执行
System.exit(0);
考试往往不会在这个规则上考你。
考试更可能给你一个包括return语句的例子,来误导你认为代码不执行finally语句就返回。不要被误导,finally子句总是会执行。
try/catch子句在它结构正确时一定会捕获错误。所以你不能试图在捕获特殊的IOException之前写一个捕获一般异常的catch,来捕获所有的Exception.
下面代码将不会通过编译
try{
DataInputStream dis = new DataInputStream(System.in);
dis.read();
}catch (Exception ioe) {}
catch (IOException e) {//Compile time error cause}
finally{}
这段代码会在编译时发出错误信息,更特殊的IOException将不会被达到。
问题
问题1) 下列那个需要建立try/catch块或者重新抛出异常?
1) Opening and reading through a file
2) Accessing each element of an array of int values
3) Accessing each element of an array of Objectts
4) Calling a method defined with a throws clause
问题2) 编译运行下列代码会发生什么情况?
import java.io.*;
class Base{
public static void amethod()throws FileNotFoundException{}
}
public class ExcepDemo extends Base{
public static void main(String argv[]){
ExcepDemo e = new ExcepDemo();
}
public boolean amethod(int i){
try{
DataInputStream din = new DataInputStream(System.in);
System.out.println("Pausing");
din.readChar();
System.out.println("Continuing");
this.amethod();
return true;
}catch(IOException ioe) {}
finally{
System.out.println("Doing finally");
}
return false;
}
ExcepDemo(){
amethod(99);
}
}
1) Compile time error amethod does not throw FileNotFoundException
2) Compile, run and output of Pausing and Continuing
3) Compile, run and output of Pausing, Continuing, Doing Finally
4) Compile time error finally clause never reached
答案 1)
打开读一个文件
4) Calling a method defined with a throws clather Resources on this topic
数组元素的类型对错误处理没有任何影响。通过定义throws子句在方法中的使用,可能会抛出一个异常,这个异常类型会被使用它的代码捕获或者再抛出。
答案 2)
3) Compile, run and output of Pausing, Continuing, Doing Finally
finally子句总是会运行。
目标五、六 使用断言
编写正确使用断言的代码,并且区分适当和不适当的断言使用。识别关于断言机制的正确论述。
目标的评论
断言是随着2002年中期JDK1.4考试版本的发布而添加到Sun Certified Java Programmers考试目标中的。由于它们是考试的新特性,你一定会在考试碰到这类题目。断言是其他面向对象语言的特性,在它被加入到Java中的时候一度面临很大的压力。
断言是因JDK1.4的发布而添加到Java语言中的,所以在写著此书的时候没有太多关于这个主题的笔墨。
断言为何存在
C++中可以使用断言,但C或Visual Basic(或者据我了解还有Pascal)中没有,所以很多人都没有使用过。如果事情到了很多C++程序都没有使用过它们的地步。断言是一个相当简单的概念,你只需要写一个始终都是true的语句,但是在形式上它们可以从最终的编译版本中去除,所以不会导致运行时开销。使用JDK1.4之前模拟断言功能的结构来书写代码是完全可以的,但是以这种方式做起来会很困难,它们在运行时会被关闭。
在C/C++语言中,断言可以使用语言预处理器来创建,在新闻组中有大量关于Java语言是否应该有预处理系统的讨论。观点的分歧在于有人认为预处理宏是恶魔的东西,它带来了创造过于复杂结构的机会,有人认为它会给语言带来不可思议的力量。无论是哪种方式,Java设计者倾向于实现预处理机制,并且在JDK1.4中包含了断言。
如何使用断言
何地以及如何使用断言大概需要一种类似于何地及如何使用注释的判断方式。一些程序员从来不使用注释,这种风格的程序被广泛认为是糟糕的程序。因为大约80%的代码是由其他人而不是原作者维护的,所以注释是很重要的。断言可以被认为是对注释的扩展,因为它相当于告诉人们阅读一段始终都是true的代码的注释。使用断言,而不是通过注释指定一个语句始终为true,你可以使用断言来声明它始终是true的。
然后,如果你运行含有断言的代码,不必像使用注释那样依赖于仔细代码,但运行代码的时候将会检查你的断言是否为true,如果它们不是断言错误就会被抛弃。
正如名字暗示的那样,断言被用来断定某些东西应该始终都是true的。当程序正常运行时,断言就失效了,不会带来性能开销。当程序员在查找问题时,断言可以被激活,如果任何断言语句不为true,断言异常就会被抛出。断言是JDK1.4中的关键部分,而且不需要在源文件中加入额外的import语句。但是,因为过去程序员已经使用了单词assert来创建他们自己的断言,编译过程需要一个命令行的参数来告诉编译器使用JDK1.4中的断言。这需要如下的形式
javac –source1.4 Myprog.java
如果你以如下形式正常运行程序
java Myprog
断言失效了,不会抛出断言异常。如果你后来需要查明一个问题,并确定所有的断言条目都是true,你可以像下面那样激活断言来运行程序。
java –enableassertions Myprog
你应该断言什么为true?
断言可以被用在任何你认为应该始终为true的地方。例如,一个人的年纪大于0应该始终是true的。如果一个人的年纪小于0,你的程序或输出就会有很大的问题。另一个例子,如果你正在登记人们死亡的日期,你的程序(或你的道德)可能会有死亡日期是否可以在未来的问题,所以,你可以对未来的死亡日期进行断言。
例如,如果你正面临case语句或一组if/else语句,你可能相信代码始终会在到达最后的测试之前退出。想象一下,如果你有一个处理媒体类型的程序。你的程序希望能够处理jpg,mpg,avi或gif文件。你设置了一个根据文件类型分支的case语句。因为你相信文件类型将始终是这些类型之一,如果你到达了case语句的末尾而没有分支了,这将是一个很明显的问题,你可以在缺省选项处放置一个断言语句。
你应该在哪里使用断言?
断言不可以用来强制程序的公共接口。一个最常见的程序公共接口是它的命令行参数。因此,传统上程序员会通过查看从命令行传入的String数组args中的值来检查传递给Java程序的命令行。典型地,如果数组没有包含程序期望类型的值,程序将退出并打印出指明正确命令行格式的消息。断言机制的引入不会改变这些。使用断言来检查程序的命令行参数是不合适的,因为断言永远都不会被激活。
使用断言来检查传递给公共方法的参数是不合适的。因为你的公共方法可能会被别人写的程序使用,你无法确定他们是否激活了断言,因此正常运行程序会出错。但是,使用断言来检查传递给私有方法的参数是恰当的,因为这些方法通常是由能够访问源代码的人来调用的。同样的假定可以作用于受保护或同一个包中的受保护方法。如你所见,这些仅仅是指导方针,但是考试可能会询问基于这些指导方针的问题。
断言语法
断言语句由两种格式
简单的
assert somebooleantest
和
assert somebooleantest : someinformativemethod
在第一个简单版本中,断言测试某物是true的,如果它不是断言错误则抛弃。例如,如果你正在测试一个人的年龄是否大于0,你可能创建如下形式的断言
assert (iAge>);
复杂的版本可能是如下形式
assert (iAge) : “age must be greater than zero”;
这个例子很简单,因为右手边的表达式只是一个简单的字符串,但是这可以是任何有返回值的函数调用,例如一个除返回值为void以外的方法。
课后测试题
问题1)下面哪些论断是正确?
Using assetions requires importing the java.util.assert package
Assertions should be used to check the parameters of public methods
Assertions can be used as a substitute for the switch/case construct
An assertion that a persons date of death > date of birth is appropriate
问题 2)如果如下代码没有显式激活断言而成功的编译并运行,会发生什么呢?
class Language {
public static final int java = 1;
public static final int pascal = 2;
public static final int csharp = 3;
}
public class Mgos {
static int lang = 0;
public static void main (String argv []) {
switch (lang) {
case Language.java:
System.out.println (“java”);
break;
case Language.pascal:
System.out.println (“pascal”);
break;
case Language.csharp:
System.out.println (“csharp”);
break;
default:
assert false: lang;
}
}
}
An unmatched parameter exception will be thrown
An assert exception will be thrown
The program will run with not output
Output of “csharp”
问题 3)下面哪些是值得使用断言结构的候选?
An input form has a field for a person’s age. If the person entering the date of birth and the date of death enters an age of death that is before the age of birth the assertion mechanism is used to cause a dialog warning box to be shown and the data will not enter the system.
A Text Editing program has a file save mechanism. The assert mechanism is used to check if the drive that is being save to really exists. If the drive does not exist an assertion will be thrown generating a warning to the program operator.
A program is being created for food preparation that involves cooking meat. Code is included so that if the value of a temperature variable reading appears to be negative an assert exception is thrown.
A school attendance system is being created. In the system is code that will throw an assert exception if a child’s age is calculated to be less than zero.
问题 4)如果激活JDK1.4的断言,编译如下代码会发生什么?
public class Bbridge {
int iRunningTotal = 0;
public static void main (String argv []) {
Bbridge bb = new Bbrige ();
bb.go (argv [0]);
}
public void go (String s) {
int i = Integer.parseInt (s);
setRunningTotal (i);
assert (iRunningTotal > 0) : getRunningTotal ();
}
public String getRunningTotal () {
return “Value of iRunningTotal “ + iRunningTotal;
}
public int setRunningTotal (int i) {
iRunningTotal += i;
return iRunningTotal;
}
}
Compile time error, getRunningTotal does not return a Boolean
Compile time error malformed assert statement
Compilation and no output given a command parameter of 1
Compilation and assert error given a command parameter of 0
问题 5)下面的论断哪些是正确的?
The assert system introduces no backward compatibility issues
The assert system should be used to enforce command line usage
Asserts should be used to check for conditions that should never happen
Asserts can be used to enforce argument constraints on private methods.
答案
答案1)
4)An assertion that a persons date of death > date of birth is appropriate
使用断言不需要引入任何包,但是它确实要求JDK1.4或更高版本,并且需要对JDK工具使用命令行参数。断言不能用来检查方法参数的值,因为在正常(非测试)模式中断言检查会被禁用。一个人死亡的日期比出生日期大的概念始终是正确的,所以使用断言结构是恰当的。
答案 2)
3)The program will run with no output
运行程序时没有从命令行显式激活断言将不会产生断言错误。
答案 3)
3)A program is being created for food preparation that involves cooking meat. Code is included so that if the value of a temperature variable reading appears to be negative an assert exception is thrown.
A school attendance system is being created. In the system is code that will throw an assert exception if a child’s age is calculated to be less than zero.
选项1和2,以及2和4之间的重要不同点在于,选项1和2中断言机制在程序正常运行过程中是必需的。断言对于正常运行程序或标准运行时检查不是必要的。当然,对于选项3和4你可能希望包含运行时检查而不是断言,但是因为描述没有指明程序正常运行的产出依赖于这个测试,所以使用断言是恰当。
答案 4)
3)Compilation and no output given a command parameter of 1
Compilation and assert error given a command parameter of 0
答案 5)
3)Asserts should be used to check for conditions that should never happen
Asserts can be used to enforce argument constraints on private methods.
最少的对于程序设计语言运行方式的知识就能指出新特性都会引起向后兼容问题。如果程序员在JDK1.4之前使用了单词assert作为变量,你将需要在使用JDK1.4时传递一个命令行参数来指出这一点。使用断言来检查命令行参数是不合适的,因为断言检查永远都不会被打开。
luren04 回复于:2008-04-14 17:02:26
为什么想收集垃圾
你可能是一位经验非常丰富的Java程序员,但是你未必想过弄清楚垃圾收集的来龙去脉。的确垃圾收集在Java程序中有点奇怪。本章中垃圾收集是指释放前面分配的内存,这些内存不会再被程序继续使用。当内存已经变得没用的时候,我们把它们叫做垃圾,它们的存在还会使得其他可用内存空间变得混乱。
Java语言设计的非常出色,其中之一就是你不用担心垃圾收集。C/C++程序员必须要手动分配和释放内存,这会导致一个问题出现就是“内存泄露”。有些版本的Windows程序,比如Word和Excel,可能几次简单的打开和关闭应用程序就会引起某些问题出现。有时候内存泄露可能最终导致系统死机,你不得不重新启动电脑。在成千上万的C/C++代码中,程序员很可能分配一块内存却忘记释放它。
Java和垃圾
与C/C++不同,Java语言会自动释放不再使用的引用。你不用从成千上万代码中苦苦查找不会再使用的内存。你也不需要知道如何分配合适大小的空间给不同的数据类型,以确保程序的兼容性。因此,看起来你没有必要知道垃圾收集的细节知识。有一种情况例外,就是你想通过考试或者想了解垃圾收集的真实情况。
如果你编写程序过程中需要创建大量的对象和变量,这时候如果知道引用什么时候会被释放是非常重要的。你需要知道自动垃圾收集的工作原理,你可以建议或者鼓励虚拟机进行垃圾收集,但是记住你不能强迫它作这个工作。
finalize
Java语言保证一个对象的finalize方法在对象被回收之前会调用。与其他类似垃圾回收的行为不同的是,这里是“保证”。但是finalize方法到底做什么呢?
乍一看,finalization像是C/C++语言中的析构器,在对象销毁之前清理其资源。不同的是Java语言不需要释放资源,因为垃圾回收器会处理内存分配。但是如果你引用了其他外部资源,比如文件信息,那么就有必要在finalization中释放资源了,这也是在JDK 1.4里面提出的参考。
当垃圾收集器判断出已经没有引用指向这个对象的时候,垃圾收集器就会调用对象的finalize方法。
因为垃圾收集器回收垃圾的行为是不确定的,你不知道什么时候他们会执行来收集垃圾。因此你也就没有办法知道什么时候finalize方法会被调用。但是,你一定想知道考试对垃圾回收这部分的要求,我们往下看。
垃圾收集的确是一个考点陷阱,因为你没有明显的方法来决定什么时候垃圾收集可用。因此你不能编写下面的代码:
if(EligibleForGC(Object){ //Not real code
System.out.print("Ready for Garbage");
}
正因为如此,你必须掌握下面的原则。
一旦一个对象不被其他任何对象引用的时候,它就变成可回收的对象了。你可以使用System.gc()来建议垃圾回收器收集垃圾,但是这并不能保证执行。
在方法中声明的本地变量在方法退出的时候就无效了,这个时候方法中的本地变量就成为了可回收的,方法每次执行的时候本地变量都会被重新创建。
无法访问
当代码已经无法再访问对象的时候,这个对象就成为了可垃圾回收的。有两种情况下会出现对象无法再被访问,第一,对象的引用设置为null;第二,指向这个对象的引用指向了其他的对象。有这样一种考试题目,在代码的某个部分把引用设置为null,你必须找出在哪里对象成为了可垃圾回收的。这种类型的题目比较简单。但是另外一种情况就不是这么明显了,我们看看下面的代码例子。
class Base{
String s;
Base(String s){
this.s = s;
}
public void setString(String s){
this.s = s;
}
}
public class UnReach{
public static void main(String argv[]){
UnReach ur = new UnReach();
ur.go();
}
public void go(){
Base b1 = new Base("One");
b1.setString("");
Base b2 = new Base("Two");
b1 = b2;
}
什么时候b1成为可垃圾回收的呢?假设你不被b1设置为空字符串所影响,那么你就可以判断出当b1指向b2的时候,原来的b1成为可垃圾回收的了。
课后测试题
问题1)下面哪段代码可以建议虚拟机执行垃圾收集?
1) System.free();
2) System.setGarbageCollection();
3) System.out.gc();
4) System.gc();
问题2) 在下面的代码片断中插入一行代码确保Integer对象被垃圾收集器回收。
public class Rub{
Integer i= new Integer(1);
Integer j=new Integer(2);
Integer k=new Integer(3);
public static void main(String argv[]){
Rub r = new Rub();
r.amethod();
}
public void amethod(){
System.out.println(i);
System.out.println(j);
System.out.println(k);
}
}
1) System.gc();
2) System.free();
3) Set the value of each int to null
4) None of the above
问题3)下面那句话是正确的?
1)You cannot be certain at what point Garbage collection will occur
2) Once an object is unreachable it will be garbage collected
3) Both references and primitives are subject to garbage collection.
3) Garbage collection ensures programs will never run out of memory
问题4)在哪里第8行创建的sb对象成为可垃圾回收的?
public class RJMould{
StringBuffer sb;
public static void main(String argv[]){
RJMould rjm = new RJMould();
rjm.kansas();
}
public void kansas(){
sb = new StringBuffer("Manchester");
StringBuffer sb2 = sb;
StringBuffer sb3 = new StringBuffer("Chester");
sb=sb3;
sb3=null;
sb2=null;
}
}
1) Line 11
2) Line 9
3) Line 12
4) Line 13
答案
答案1)
4) System.gc();
答案2)
4) None of the above 你只能建议垃圾回收器运行,但是无法决定他会在代码的哪个部分执行。注意只有对象的实例才可能成为垃圾回收对象,原始数据类型不会。
答案3)
1) You cannot be certain at what point Garbage collection will occur
一旦一个对象不能在被访问,那么他将成为可垃圾回收的。但是你不能确定它什么时候会被回收。垃圾回收机制只对对象有效,对原始类型无效。你应该知道垃圾收集不能确保程序不会出现内存不足的情况。但是他能保证不再被使用的内存可以成为可用的。
答案4)
4) Line 13
第9行创建的sb2指向了第8行创建的对象,直到它成为不可到达的对象的时候,sb才成为可垃圾回收的。
答案5)
1) finalize will always run before an object is garbage collected
对象在垃圾回收之前,它的finalize方法会被调用。Finalize方法不能在对象被回收后调用,因为那时候对象已经不存在了。当一个对象不能访问的时候,他就成为了可垃圾回收的,但是你无法保证它什么时候会被回收。选项4在java中是不正确的,在C++中正确。
luren04 回复于:2008-04-14 17:03:04
目标一 包,引入,内部类,接口
正确识别结构化的包声明,引入子句,类声明(包含内部类在内的所有形式),接口声明,方法声明(包括类运行入口的main方法),变量声明和标识符。
目标的注解
这是一个奇怪的使用短语表达的目标。它似乎在要求你理解何时,如何以及为何使用引入子句和包子句,以及应该将接口子句和变量子句放在什么地方。
包
名称package意味着类的集合,有点类似于类库。使用包也有点像使用目录。如果你在一个文件中放置一个包子句,此文件只对同一个包中的其他类可见。包有助于解决命名冲突问题。因为你只能使用这么多有意义的名字作为类名,最终,你可能需要使用或创建相同名称的类。通过在类之前附加一个完整的包名,你可以多次使用相同的名字。包名的使用惯例是用组织的internet域名来创建类。因此,当创建一个叫做Question的类来表示一个虚拟的测试题时,我使用我的网站域名www.jchq.net来创建目录结构。
WWW部分无法唯一标识网站的任何信息,所以使用的域名将是net.jchq。为了在我唯一的包中创建类,我创建了目录net,并在此之下创建一个叫做jchq的目录。接着,在那个目录中我可以创建叫做Question的类,类的开头为如下包定义:
package net.jchq.*;
这将赋予你访问此包/目录中任何类的权利。可选地,你可以仅仅指定一个需要获取访问权限的类,使用如下行:
package net.jchq.Question;
引入
import子句必须出现在任何package子句之后和任何代码之前。引入子句不能出现在类中,类声明之后或其他任何地方。
import子句允许你直接使用类名,而不必使用完整的包名来限定它。一个例子就是类名java.awt.Button通常被简写为Button,只要你已经将如下子句放在文件的起始位置:
import java.awt.*;
如果我随后想要创建我的Question类的实例,我只需要引入包或指定此类的完整包名。为了引入其他包中的类,我将需要如下行:
import net.jchq.*;
为了指定类的完整包名,我需要使用如下风格的语法。
jchq.net.Question question = new net.jchq.Question();
你可以想象,经常性地输入完全限定的包名显得不够灵活,所以引入类通常是首选的方案。
请注意,使用引入子句对性能没有影响。这类似于在DOS(或Unix)环境中设定一个路径声明。这只是简单的为类设定有效性或路径,并不是直接将代码引入程序中。仅仅在程序中实际地使用类才会影响性能。
你可以在包自己之前放置一段注释,但不能是其他任何内容。你可能会遇到将引入子句放于包子句前面的考试题。
//你可以在包子句之前放置一段注释
package MyPack;
public class MyPack {}
下面的代码会导致错误
import java.awt.*;
//错误:将引入子句放于包子句前面
//语句会导致编译时错误
package MyPack;
public class MyPack {}
package子句可能会包含点号来指定包层次。因此如下代码将不会导致编译错误
package myprogs.MyPack;
public class MyPack {}
记住,如果你没有在源文件中放置包子句,这将被认为有一个相当于当前目录的缺省包。这与在“1.2节 定义和访问控制”中提到的可见性有关。
类和内部类声明
一个文件只能包含一个外部public类。如果你试图创建一个包含多个public类的文件,编译器将会报告特定的错误。一个文件可以包含多个非公共类,但是记住这将为每个类生成单独的.class输出文件。公共类在文件中的放置位置是没有关系的,只要在文件中仅有一个公共类。
内部类是在JDK1.1中提出的。这个想法是为了允许一个类在另一个类中定义,在一个方法中定义,以及创建匿名内部类。这会带来一些有趣的影响,特别是对于可见性。
这是一个简单的内部类的例子:
class Outer {
class inner{}
}
这会导致生成如下名称的类文件
Outer.class
Outer$Inner.class
内部类的定义仅仅在现有的Outer类的上下文中可见。因此,如下代码会导致编译时错误
class Outer {
class Inner{}
}
class Another {
public void amethod () {
Inner I = new Inner();
}
}
涉及到类Another的时候,类Inner是不存在的。它只能存在于Outer类实例的上下文中。因此如下代码运行良好,因为在创建Inner实例的时候,有一个指向外部类的this实例。
class Outer {
public void mymethod () {
Inner I = new Inner();
}
public class Inner {}
}
但是,如果Outer类的this实例不存在时会发生什么呢。为了弄清楚为此提供的相当古怪的语法的含义,试着将如上例子中的new关键字看作属于this实例当前的上下文。
这样,你可以改变创建实例的代码行,如下
Inner i = this.new Inner ();
这样,如果你需要从一个static方法或其他没有this对象的地方创建Inner的实例,你可以把new当作属于外部类的一个方法来使用
class Outer {
public class Inner {}
}
class another {
public void amethod () {
Outer.Inner i = new Outer ().new Inner ();
}
}
尽管有了我口齿伶俐的解释,我发现这个语法不够直观,并在学完5分钟后就忘记了。你很有可能会在考试中遇到这个问题,所以请给予额外的注意。
内部类的一个好处是内部类一般可以访问它的嵌套类(或外部类)的域。不像外部类,内部类可以是private或static。主考者似乎有可能问一些归结为“一个内部类可以是static或private”的问题。
静态内部类的方法当然可以访问其嵌套类的任何静态域,因为那些域将只会有一个实例。
声明在方法中的内部类
内部类可以在方法中创建。这是像Borland JBuilder那样的GUI生成工具在创建事件处理器时花很多功夫做的事情。
这是一个这种自动生成的代码的例子
buttonControl1.addMouseListener (new java.awt.event.MouseAdapter () {
public void mouseClicked (MouseEvent e) {
buttonControl1_mouseClicked (e);
}
});
请注意第一个圆括号之后的new关键字。它指出在方法addMouseListener中一个匿名内部类正在被定义。通常地,这个类可以使用一个名字定义,这可能会使它更容易被人们读懂,但是由于在其他地方不需要对其进行处理,取名字不会有太多帮助。
如果你手动创建这些代码,很容易会被数字以及花括号和圆括号的层次弄糊涂。请注意完整的结构为何是以分号结束的,因为这实际上是一个方法调用的结束。
正如你猜的那样,一个匿名内部类不能由程序员给定构造函数。考虑一下,构造函数是一个没有返回值,并且名字与类名相同的方法。咄!我们在谈论没有名字的类。一个匿名类可以继承其他类或实现单一接口。这个特别的限制似乎不会在考试中考到。
在方法中定义的类的域可见性
定义在方法中的类只能访问嵌套方法中的域,如果他们是被定义为final的。这是因为定义在方法中的变量通常被认为是自治的(automatic),例如他们仅当方法执行时才存在。在创建在方法中的类中定义的域可能比嵌套方法的生命周期长。
因为final变量不能被修改,JVM可以确保它的值保持恒定,甚至在外部方法运行终止之后。你很可能在考试中遇到这方面的问题,包括考察作为参数传递给方法的变量状态的问题(是的,他们也必须是final的)。
创建接口
接口是Java用来解决缺少多继承的方式。有趣的是,Visual Basic使用关键字interface并以与Java相似的方式来使用此概念。有时候接口方法被认为面向契约编程。通过关键字“implements”来使用接口。因此,类可以被声明为
class Malvern implements Hill, Well {
public
}
主方法
因为java中的所有代码必须存在于类中,必须有一个特别的或“有魔力的”的方法来引导程序开始运行。这个方法具有如下署名
public static void main (String argv[])
从声明中的每一项来分析,关键字public意味着方法到处可见。static部分意味着方法属于类本身,而不是属于任何特定的实例。这意味着不需要创建类的实例就可以调用它。单词void意味着方法没有返回值。注意单词main都是小写的。在圆括号中的部分指出方法接受一个String数组的参数。当然,单词String必须以大写S开头。参数arg的名字没有关系,你可以叫它bicycle或trousers或任何正确的变量名称,它都可以正确运行。但是参数命名为arg是一个值得坚持的惯例。因为数组的方括号可以跟在名字或类型之后,将参数声明为String [] arg也是可以接受的。
请注意,为了Sun Certified Java Programmers,这是正确的署名。你可能发现其他类似的署名在现实中也可以运行,但是为了考试(以及未来兼容性的目的)你应该使用这种署名。因为这个方法是静态的,所以它被调用(或被Java环境有效跟踪)的时候不需要创建类的实例。同样,因为它是静态的,你不可以操作非静态的方法或数据。因为这样,main方法经常包含极少的代码,典型地,它包含代码来创建嵌套类的实例,然后调用真正使程序完成工作的非静态方法。
由系统传递给main方法的String数组包含任何在程序开始时从命令行传入的参数。当然,有了现代图形用户界面环境,更普通的方法是通过点击图标来启动程序,这些都不会给传递参数带来变化。
考试的目标4.2明确地要求你理解命令行参数是如何传递给main方法的,以及如何访问它们。这就是说……
陈述传递给main方法的参数数组的下标值与命令行参数的对应关系。
课后测试题
问题1)假设有如下代码
public class FinAc {
static int l = 4;
private int k = 2;
public static void main (String argv [] ) {
FinAc a = new FinAc();
a.amethod();
}
public void amethod () {
final int i = 99;
int j = 6;
class CInMet {
public void mymethod (int q) {
// Here
}// end of mymethod
}// End of CInMet
CInMet c = new CInMet ();
c.mymethod (i);
}// End of amethod
}
如下变量中,哪些在由注释//Here标记的行上是可见的?
l
k
i
j
问题 2)下面哪个选项可以正确编译?
1)
// A Comment
import java.awt.*;
class Base {}
2)
import java.awt.*;
package Spot;
class Base ();
3)
// Another comment
package myprogs.MyPack;
public class MyPack {}
4)
class Base {}
import java.awt.*;
public class Tiny {}
问题 3)如下论述哪些是正确的?
An inner class may be defined as static
An inner class may NOT be define as private
An anonymous class may have |
|