SCJP复习指南1

 

目标一 创建数组

数组

Java中的数组跟C/C++这些语言中的数组的语法结构很相似。但是,Java去掉了C/C++中的可以通过[]或者使用指针来访问元素的功能。这种在C/C++中被普遍接受的功能虽然强大,但是也让Bug横行的软件更容易出现。因为Java不支持这种直接通过指针来操纵数据,这类的Bug也被消除了。

数组是一类包含被称为元素的值的对象。这就为你在程序中移动或保存一组数据以很方便的支持,并且允许你根据需要访问和改变这些值。用一个小例子来说:你可以创建一个String类型的数组,每一个都包含一个运动队队员名字。数组可以传送给一个需要访问每个队员名字的方法。如果一个新队员加入,其中一个老队员的名字可以被修改成新队员的名字。这就显得比player1player2player3等等很随意的不相关的变量方便很多。跟变量通过变量名来访问不同的是,元素通过从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的数组

JavaC/C++数组的比较

Java数组知道它的大小,并且Java语言支持对意外的移动到数组末端的保护。

如果你从Visual Basic背景下转到Java开发,并且还不习惯于一直从0开始计数,这点是很方便的。这也可以帮你避免一些在C/C++程序中很难发现的错误,例如移动到了数组末端并且指向了任意内存地址。

例如,下面的程序会引起一个ArrayIndexOutOfBoundsException异常。

int[] num= new int[5];

for(int i =0; i<6; i++){

num[i]=i*2;

}

访问一个Java数组的标准习惯用法是使用数组的length成员

例如:

int[] num= new int[5];

for(int i =0; i<num.length; i++){

num[i]=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

声明和初始化相结合

一个数组可以通过一个语句来创建并初始化,这就代替了通过数组循环来初始化的方式。这种方法很适合小数组。下面的语句创建了一个整型数组并且赋值为04

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

答案

答案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

所有的数组的声明都是正确的。如果你觉得不太可能,可以自己编译这段代码。

目标二 定义类和变量

定义类,内部类,方法,实例变量,静态变量和自动(本地方法)变量,需要合适的选用允许的修饰词。(例如publicfinalstaticabstract诸如此类)。这些修饰词或者单独使用或者联合使用,定义了包的关系。

本目标需要注意的

我发现目标中用了“诸如此类”,这让我有些烦恼,我想你需要弄明白下面词的意思:

native

transient

synchronized

volatile


什么是类?

一个类的的定义把它很生硬描述为“方法和数 据的集合”。它把面向对象编程出来之前的编程思想结合起来,这对理解该概念很有帮助。在类和面向对象程序设计前的主要概念是结构化程序设计。结构化程序设 计的理念是程序员将复杂问题划分为小块的代码,一般称为函数或子程序。这符合“做一件很大很复杂的事情的好办法是把它分成一系列比较小但更容易管理的问 题”的理念。

尽管结构化程序设计在管理复杂性方面很有用,但它不能容易的解决代码重用问题。程序员发现他们总是“重复发明”轮子。在试着对现实物理对象的思考中,程序设计方面的思想家找到了面向对象的理念(有时被称为OO)。

举例来说,一个计算机厂商准备生产一种新型个人电脑,如果计算机厂商使用类似于程序设计的方式的话,就要求他建立新团队来设计新CPU芯片,新声卡,没准还需要另一个团队设计规划制造新的主板。事实上,这根本不可能出现。由于电脑组件接口的标准化,计算机厂商只需要联系配件供应商,并商议好他们要生产的新型号的说明书就行了。注意组件接口标准化的重要性。


比较C++/VBJava的类

因为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)


这些例子说明了一些典型的方法签名。使用关键字privateprotected说明它们将会在别处隐藏。

Java方法和其他像C这样的非面向对象语言的方法的区别是Java方法属于类。这表明它们通过点号指明代码属于哪个类的实例来调用。(static方法是一个例外,但我们现在无需担心)

因此在HelloWorldamethod通过下面的语句调用

HelloWorld hw = new HelloWorld();

hw.amethod();

HelloWorld类中创建的其他实例中,方法被类的每个实例所调用。每个类的实例将能够访问它自己的变量。因此下面的代码将调用不同实例的amethod方法

HelloWorld hw = new HelloWorld();

HelloWorld hw2 = new HelloWorld();

hw.amethod();

hw2.amethod();

类的两个实例hwhw2可能访问不同的变量。

自动局部变量

自动变量是方法变量。它们在方法代码开始运行时生效,并在方法结束时失效。因为它们只能在方法内可见,因此临时操作数据时比较有用。如果你希望一个值在方法被调用时保持,你需要将变量创建在类级别。

一个自动变量将“屏蔽”类级别的变量。

因此,下面的代码将打印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类的变量值的方法的形式。这些方法一般是accessormutator方法。访问方法找回值而设置方法改变值。命名惯例是这些方法名类似于setFOO改变值,getFOO得到值。注意,使用setget来命名的方法比仅仅使程序员感到方便更重要,并且是Javabean系统的重要组成部分。不过我们的测试还没有涉及到Javabean的内容。

举一个例子,你有一个变量用来存储学生的年龄。你可能简单的用一个public的整型变量来存储。

int iAge

接下来,当你的应用程序交付使用后,你可能会发现你的某些学生可能有超过200岁的记录,还有小于0岁的记录。你需要一段代码来检查错误条件。所以当你的程序改变年龄的值的时候,你用if语句来检查范围。

if(iAge > 70){

//do something

}

if (iAge <3){

//do something

}

当你正在做这些的时候,你漏掉了一些使用过iAge变量的代码,所以你被召回了,因为你可能有一个19岁的学生,但是你的记录里却是190岁。

面向对象使用封装处理了这样的问题,就是创建一个访问包含年龄值的private域的方法,名字类似于setAgegetAgesetAge方法可能有一个整型的参数并且更新年龄的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类有完整的功能方法,如:randomsinround。基本数据类型的包装类IntegerDouble等等也有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而是用CC++所写。native方法经常为平台的特殊目的所写,例如访问某些Java虚拟机不支持的硬件。另一个原因是为了需要获得更好的性能。

一个native方法以一个分号结尾,而不是代码块。例如下面的代码将会调用一个可能用C++所写的外部程序:

public native void fastcalc();

抽象(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语言的一部分,然后去担心别的吧。

联合使用修饰符

可见性修饰符不能被联合使用,一个变量不可能同时是privatepublicpublicprotectedprotectedprivate。你当然可以联合使用可见性修饰符和我在下面列表中提及的修饰符。

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)

答案

答案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

虽然P2P1的同一个路径下,但是P1package语句声明了,所以对于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/InpriseJBuilder这样的代码产生器在你生成类的框架时会创建一个零参数构造方法的原因之一。

下面例子中的代码不会被编译。当编译器创建名字为cBase类的实例时,它会插入一个指向无参数的构造方法的调用。由于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, synchronizedfinal

上面这句话源于一个编译错误信息。看起来像是新版本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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

光义

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值