Java基础就业培训教程第二章 张孝祥

更新时间:2024-03-08 16:49:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

第2章 Java 编程基础

2.1 Java基本语法格式

2.1.1 Java代码的落脚点

Java中的所有程序代码都必须存在于一个类中,用class关键字定义类,在class前面可以有一些修饰符。格式如下:

修饰符 class 类名 {

程序代码 }

2.1.2 Java是严格区分大小写的

例如,我们不能将class写成Class。

2.1.3 Java是一种自由格式的语言

在Java中,所有的程序代码分为结构定义语句和功能执行语句,一条语句可以写在若干行上,功能执行语句的最后必须用分号(;)结束,不必要对齐或缩进一致。可以按自己的意愿任意编排,只要每个词之间用空格、制表符、换行符或大括号、小括号这样的分隔符隔开就行。例如第一章的例子程序改成下面这种编排方式也是可以的:

class Test{public static void main(String [

] args){System.out.println(“My first java program”);}}

用哪种代码书写的格式因个人爱好而定,但出于可读性的考虑不建议使用这种格式。

?

脚下留心:

1. Java程序中一句连续的字符串不能分开在两行中写,以上程序如果写成下面这种方式是会编译出错的:

class Test{public static void main(String [ ] args){System.out.println(“My first java program”);}}

2. 功能执行语句的最后必须用分号(;)结束,但中国的初学者常将这个英文的(;)误写成中文的(;)自己却找不出错误的原因来,对于这样的情况,编译器通常会报告“illegal character”(非法字符)这样的错误信息。

2.1.4 Java程序的注释

为程序添加注释可以提高程序的可读性,它是写在程序里的信息,用来说明某段程序的作用和功能。Java里的注释根据不同的用途分为三种类型:

? 单行注释

24

? 多行注释 ? 文档注释

第一种是单行注释,就是在注释内容前面加双斜线(//),java编译器会忽略掉这部分信息。如下例:

int c = 10; // 定义一个整型

第二种是多行注释,就是在注释内容前面以单斜线加一个星形标记(/*)开头,并在注释内容末尾以一个星形标记加单斜线(*/)结束。当注释内容超过一行时一般使用这种方法,如:

/* int c = 10; // 定义一个整型

int x=5; */

第三种注释方法是文档注释,是以单斜线加两个星形标记(/**)开头,并以一个星形标记加单斜线(*/)结束。用这种方法注释的内容会被解释成程序的正式文档,并能被包含在诸如javadoc之类的工具程序提取的文档里,用以说明该程序的层次结构及其方法。关于这种注释的详细用法,我们会在后面的章节中讲解。

?

脚下留心:

/*??*/中可以嵌套“//”注释,但不能嵌套“ /**/”,如:下面的注释是非法的:

/*

/*int c = 10;*/ int x=5; */

?

不得不说:我们要从开始就养成良好的编程风格,软件编码规范中说:“可读性第一,效

率第二”。在程序中必须包含适量的注释,以提高程序的可读性和易于维护性,程序注释一般占程序代码总量的20%-50%。

2.1.5 Java中的标识符

Java中的包、类、方法、参数和变量的名字,可由任意顺序的大小写字母、数字、下划线(_)和美元符号($)组成,但标识符不能以数字开头,不能是关键字。

下面是合法的标识符: indentifier, username user_name _userName $username

下面是非法的标识符: class 98.3

Hello World

正确的路有一条,错误的路千万条,何苦要去记住有哪些错误的路呢?永远用字母开头,尽量不要包含其他的符号就行了。

25

2.1.6 Java的关键字

和其他语言一样,Java中也有许多保留关键字,如public,break等,这些保留关键字不能被当作标识符使用。其实大家不用死记硬背到底有哪些关键字,知道有这回事就足够了,万一不小心把某个关键字用作标识符了,编译器就能告诉我们这个错误。下面是Java的关键字列表,大家就留个初步的印象吧!

abstract boolean break byte case catch char class continue default do double else extend false final finally float for if implement import instanceof int interface long native new null package private protected public return short static strictfp super switch this throw throws

transient true try void volatile while synchronized

注意:Java 没有sizeof、goto、const这些关键字,但不能用goto、const作为变量名。

2.1.7 Java中的常量

常量就是程序里持续不变的值(有的书中称其为字面量或实字),Java中的常量包含整型常量,浮点数常量,布尔常量等,下面我们来看一下它们是如何表示的: 整型常量

整型常量可以分为十进制,十六进制和八进制来表示: 十进制:

0 1 2 3 4 5 6 7 8 9

注意:以十进制表示时,第一位不能是0(数字0除外)。 十六进制:

0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F 注意:以十六进制表示时,需以0x或0X开头,如: 0x8a 0Xff 0X9A 0x12 八进制:

0 1 2 3 4 5 6 7

注意:八进制必须以0开头。如: 0123 045 098 046 长整型:

长整型必须以L做结尾,如: 9L 156L 浮点数常量:

浮点数常量有float(32位)和double(64位)两种类型,分别叫做单精度浮点数和双精度浮点数,表示浮点数时,要在后面加上f(F)或者d(D),用指数表示也可以。注意:由于小数常量的默认类型为double型,所以float类型的后面一定要加f(F),用以区分。如:

2e3f 3.6d .4f 0f

3.84d 5.022e+23f 都是合法的。 布尔常量:

26

布尔常量用于区分一个事物的正反两面,不是真就是假。其值只有两种:true和false。 字符常量:

字符常量是由英文字母、数字、转义序列、特殊字符等的字符所表示,它的值就是字符本身,如:

'a' '8' '\\t' '\''

字符常量要用两个单引号括起来,Java中的字符占用两个字节,是用unicode码表示的,我们也可以使用unicode码值加上\%u来表示对应的字符。 字符串常量:

字符串常量和字符型常量的区别就是:前者是用双引号括起来的常量,用于表示一连串的字符。而后者是用单引号括起来的,用于表示单个字符。下面是一些字符串常量:

\

?多学两招:有些时候,我们在无法直接往程序里面写一些特殊的按键,比如你想打印出一

句带引号的字符串,或者判断用户的输入是不是一个回车键,等等。其实它们都可以用一些转义字符来表示,以下一些特殊字符的意义,供参考:

\\r表示接受键盘输入,相当于按下了回车键; \\n表示换行;

\\t表示制表符,相当于table键; \\b表示退格键,相当于Back Space; \\'表示单引号,\\\是双引号; \\\\表示一个斜杠“\\”。

比如上面的\它的运行结果是: Welcome XXX

null常量:

null常量只有一个值,用null表示,表示对象的引用为空。

2.2 变量及变量的作用域

2.2.1 变量的概念

变量就是系统为程序分配的一块内存单元,用来存储各种类型的数据。根据所存储的数据类型的不同,有各种不同类型的变量。

用一个变量定义一块内存以后,程序就可以用变量名代表这块内存中的数据。我们来看一下:

int x=0,y; y=x+3;

第一句代码分配了两块内存用于存储整数,分别用x,y作为这两块内存的变量名,并将x标识的内存中的数据置为0,y标识的内存中的数据为其原始状态,可以认为是一个未知数。第二句代码的执行过程,程序首先取出x代表的那块内存单元的数,加上3,然后把结果放到y所在的那块内存单元,这样就完成了y=x+3的运算。

27

2.2.2 Java的变量类型

在Java中内建有八种基本变量类型来存储整数、浮点数、字符和布尔值。

整数类型(byte, short, int, long)

数值型

浮点类型(float, double)

基本数据类型

字符型(char) 布尔型(boolean)

数据类型 类(class)

引用数据类型

接口(interface) 数组

其中引用数据类型会在以后章节详细讲解,这里只讲基本数据类型。

与其他编程语言不同的是,Java的基本数据类型在任何操作系统中都具有相同的大小和属性,不像C语言,在不同的系统中变量的取值范围不一样,在所有系统中,Java变量的取值都是一样的,如表2.1。这也是Java跨平台的一个特性。

有四种数据类型用来存储整数,它们具有不同的取值范围,分别如表2.1所示: 类型名 大小 取值范围 byte 8位 -128~127 short 16位 -32768~32767 int 32位 -2147483648~2147483647 long 64位 -9223372036854775808~9223372036854775807 表2.1

这些类型都是有符号的,所有整数变量都无法可靠地存储其取值范围以外的数据值,因此定义数据类型时一定要谨慎。

有两种数据类型用来存储浮点数,它们是单精度浮点型(float)和双精度浮点型(double)。浮点数在计算机内存中的表示方式比较复杂,我们在后面的课程为大家进行分析,单精度浮点型和双精度浮点型的取值范围见表2.2:

类型名 大小 取值范围 float 32位 1.4E-45~3.4E+38,-1.4E-45~-3.4E+38 double 64位 4.9E-324~1.7E+308, -4.9E-324~-1.7E+308 表2.2

char类型用来存储诸如字母、数字、标点符号及其他符号之类的单一字符。与C语言不同,Java

28

的字符占两个字节,是unicode编码的。

?

独家见解:计算机里只有数值,当你在内存中看到一个数值时,这个数值可能代表各种意

义,比如你看到的文字、图像和听到的声音等都是使用数字形式来表示的。生活中的数值也可以代表其他意义,如1234可以代表密码,存款额,电报信息等。根据上下线索,我们能够知道这些数值代表的意义。其实,字符也是一个数字,当我们要给一个字符变量赋值时,就可以直接用整数,如:97对应字符'a',我们使用 char ch=97将字符'a'赋值给变量ch。98对应的是字符'b',当在内存里面躺着一个99时,请问,它对应键盘上的哪个字母呢?大家都能够猜出就是字符'c'。

如果我们要将字符'x'赋给一个char变量,该填一个怎样的整数呢?显然,我们不太容易记住每个字符所对应的数字,所以,我们就用单引号加上这个字符本身来表示那个字符对应的数字,如char ch='x'。

boolean类型用来存储布尔值,在Java里布尔值只有两个,要么是true,要么就是false。 Java里的这八种基本类型都是小写的,有一些与它们同名但大小写不同的类,例如Boolean等,它们在Java里具有不同的功能,切记不要互换使用。

2.2.3 注意变量的有效取值范围

系统为不同的变量类型分配不同的空间大小,如double型常量在内存中占八个字节, float的变量占四个字节,byte型占一个字节等,如图2.1所示:

图2.1

byte b=129;// 编译报错,因为129超出了byte类型的取值范围。

float f=3.5;//编译报错,因为小数常量的默认类型为double型。double型常量在内存中占八个字节,而Java只为float的变量分配四个字节的空间,要将八个字节的内容装入四个字节的容器,显然有问题。改为:

float f=3.5f;

编译就可以通过了,因为3.5f是一个float型常数,在内存中只占4个字节。

2.2.4 基本数据类型之间的转换

29

在编写程序过程中,我们经常会遇到的一种情况,就是需要将一种数据类型的值赋给另一种不同数据类型的变量,由于数据类型有差异,在赋值时就需要进行数据类型的转换,这里就涉及到两个关于数据转换的概念:自动类型转换和强制类型转换。

◆ 自动类型转换(也叫隐式类型转换)

要实现自动类型转换,需要同时满足两个条件,第一是两种类型彼此兼容,第二是目标类型的取值范围要大于源类型。例如,当byte型向int型转换时,由于int型取值范围大于byte型,就会发生自动转换。所有的数字类型,包括整型和浮点型彼此都可以进行这样的转换。

请看下面的例子:

byte b=3;

int x=b; //没有问题,程序把b的结果自动转换成了int型了

◆ 强制类型转换(也叫显式类型转换)

当两种类型彼此不兼容,或目标类型取值范围小于源类型时,自动转换无法进行,这时就需要进行强制类型转换。强制类型转换的通用格式如下:

目标类型 变量=(目标类型)值 例如:

byte a; int b;

a = (byte) b;

这段代码的含义就是先将int型的变量b的取值强制转换成byte型,再将该值赋给变量a,注意,变量b本身的数据类型并没有改变。由于这类转换中,源类型的值可能大于目标类型,因此强制类型转换可能会造成你的数值不准确,从下面的图中就可以看出强制类型转换时数据传递的过程。

图2.2

请看下面的程序:

程序清单:Conversion.java

public class Conversion {

public static void main(String[] args) { } }

byte b ; int i = 266 ; b = (byte)i ;

System.out.println(\

程序输出入下:

byte to int is 10

字符串可以使用加号(+)同其他的数据类型相连而形成一个新的字符串,读者只要明白二

30

进制与十进制数之间的转换关系,就不难明白上面程序打印的结果了。

?独家见解:要理解类型转换,大家可以这么想像,大脑前面是一片内存,源和目标分别是两

个大小不同的内存块(由变量及数据的类型来决定),将源数据赋值给目标内存的过程,就是用目标内存块去套取源内存中的数据,能套多少算多少。

2.2.5 表达式的数据类型自动提升

我们先看下面一个错误程序: 程序清单:Test.java

class Test

{

public static void main(String[] args) {

byte b = 5; b = (b-2);

System.out.println(b); }

}

这段代码中,5-2的值并未超出byte型取值范围,然而当执行这段代码时,Java报出如下错误:

Test.java:6: possible loss of precision found : int required: byte

b = (b-2); ^ 1 error

这是因为在表达式求值时,变量值被自动提升为int型,表达式的结果也就成了int型,这时要想把它赋给byte型变量就必须强制转换了。因此前面代码中粗体的部分就应该改成:

b = (byte)(b-2);

这种特殊情况在编程过程中如果遇到了,只要知道怎么解决就可以了。

关于类型的自动提升,Java定义了若干适用于表达式的类型提升规则。 第一,所有的byte型、short型和char的值将被提升到int型。 第二,如果一个操作数是long型,计算结果就是long型; 第三,如果一个操作数是float型,计算结果就是float型; 第四,如果一个操作数是double型,计算结果就是double型。 以下代码演示了Java的类型自动提升规则:

程序清单:Promote.java

class Promote {

public static void main(String args[]) {

31

byte b = 50; char c = 'a'; short s = 1024; int i = 50000; float f = 5.67f; double d = .1234;

double result = (f * b) + (i / c) - (d * s);

System.out.println((f * b) + \System.out.println(\} }

我们看看在下列代码行的类型提升:

double result = (f * b) + (i / c) - (d * s);

在第一个子表达式f*b中,变量b被提升为float类型,该子表达式的结果也提升为float类型。接下来,在子表达式i/c中,变量c被提升为int类型,该子表达式的结果提升为int类型。

然后,子表达式d*s中的变量s被提升为double类型,该子表达式的结果提升为double类型。 最后,这三个结果值类型分别是float类型,int类型,和double类型。float类型加int类型的结果是float类型。然后float类型减去double类型,该表达式的最后结果就是double类型。

?多学两招:

System.out.println()除了可以在屏幕上打印字符串外,还可以直接在屏幕上打印其他类型的数据,读者再想想System.out.println('a'+1)在屏幕上的打印结果与System.out.println(\'a'+1)在屏幕上的打印结果有什么不同呢?前者将字符'a'与整数1相加后得到的结果是整数98,后者将一个空字符串与字符'a'相连后再与整数1相连形成一个新的字符串“a1”。

2.2.6 变量的作用域

大多数程序设计语言都提供了“变量作用域”(Scope)的概念。在C、C++和Java里,一对花括号中间的部分就是一个代码块,代码块决定其中定义的变量的作用域。代码块由若干语句组成,必须用大括号包起来形成一个复合语句,多个复合语句可以嵌套在另外的一对大括号中形成更复杂的复合语句。如:

{

int x=0; { }

x=x+1; }

int y=0; y=y+1;

代码块决定了变量的作用域,作用域决定了变量的“可见性”以及“存在时间”。 参考下面这个例子:

程序清单:TestScope.java

public class TestScope {

32

public static void main(String[] args)

{

int x = 12;

{

int q = 96; // x和q都可用

System.out.println(\ System.out.println(\ }

q = x; /* 错误的行,只有x可用, q 超出了作用域范围 */ System.out.println(\

} }

q作为在里层的代码块中定义的一个变量,只有在那个代码块中位于这个变量定义之后的语句,才可使用这个变量,q=x语句已经超过了q的作用域,所以编译无法通过。记住这样的一个道理,在定义变量的语句所属于的那层大括号之间,就是这个变量的有效作用范围,但不能违背变量先定义后使用的原则。

?

脚下留心:下面这样书写的代码在C和C++里是合法的:

{

int x = 12; {

int x = 96; x = x + 4;// x运算后的结果为100 }

x = x - 5;//x运算后的结果为7,而不是95。 }

在C和C++里面,上面的两个x相当于定义了两个变量,第二层大括号里面的代码对x的操作,都是对第二个x的操作,不会影响到第一个x。第一层大括号里面的代码对x的操作,都是对第一个x的操作,跟第二个x没有任何关系。

但这种做法在Java里是不允许的,因为Java的设计者认为这样做使程序产生了混淆,编译器会认为变量x已在第一层大括号中被定义,不能在第二层大括号被重复定义。

2.2.7 局部变量的初始化

在一个函数或函数里面的代码块中定义的变量称为局部变量,局部变量在函数或代码块被执行时被创建,在函数或代码块结束时被销毁。局部变量在进行取值操作前必须被初始化或进行过赋值操作,否则会出现编译错误,如下面的代码: 程序清单:TestVar.java

public class TestVar {

public static void main(String [] args) {

int x;//应改为int x=0;

x=x+1; //这个x由于没有初始化,编译会报错。 System.out.println(\

} }

2.3 函数与函数的重载

2.3.1函数

33

假设我们有一个游戏程序,在程序运行过程中,我们要不断地发射炮弹。发射炮弹的动作都需要使用一段百行左右的程序代码,我们在每次发射炮弹的地方都要重复加入这一段百行程序代码,程序会变得非常臃肿,程序的可读性也就非常差。假如我们要修改发射炮弹的程序代码,需要修改每个发射炮弹的地方,很可能就会发生遗漏。几乎所有的编程语言中都要碰到这个问题,各种编程语言都将发射炮弹的程序代码从原来的主程序中单独拿出来,做成一个子程序,并为这个子程序安排一个名称,在主程序中需要使用到子程序的功能的每个地方,只要写上子程序的名称就行了,计算机便会去执行子程序中的程序代码,当子程序中的代码执行完后,计算机又会回到主程序中接着往下执行。在Java中,我们将这种子程序叫函数。

我们来写一个程序来说明函数的作用与编写方式,程序在窗口上打印出3个由*组成的矩形,如下图所示。

图2.3

我们可以用下面的程序来实现: 程序清单:Func1.java

public class Func1 {

public static void main(String [] args) {

//下面是打印出第一个矩形的程序代码

for(int i=0;i<3;i++) { }

System.out.println();

//下面是打印出第二个矩形的程序代码 for(int i=0;i<2;i++) {

for(int j=0;j<5;j++) { }

System.out.println(); //换行

System.out.print(\

34

} }

}

for(int j=0;j<4;j++) { }

System.out.println();

System.out.print(\

System.out.println();

//下面是打印出第三个矩形的程序代码 for(int i=0;i<6;i++) { }

System.out.println();

for(int j=0;j<10;j++) { }

System.out.println();

System.out.print(\

看不懂上面程序中的for语句的读者,可以先看本章后面关于for语句的讲解,即使你现在不愿意去看,也没多大关系,你还是能够发现,上面程序中的每一段打印出矩形的代码,除了宽度和高度不一样,其他的地方都一样。我们可以把打印出矩形的代码作为子程序单独从程序中提出来,并用一个“名称”来标记这段代码,以后再碰到要打印矩形时,直接调用这个“名称”就可以了。这样,我们对打印矩形的程序代码只写了一遍,而不用重复书写那么多次了。代码如下: 程序清单:Func2.java

public class Func2 {

public static void drawRectangle(int x,int y) { }

public static void main(String [] args) {

drawRectangle(3,5);

for(int i=0;i

System.out.println();

for(int j=0;j

System.out.println(); //换行

System.out.print(\

35

} }

drawRectangle(2,4);

drawRectangle(6,10);

以上代码中,我们提出的这段代码就是函数体,用来标记这段代码的“名称”(drawRectangle),就是函数名,函数名和函数体共同组成了函数,在Java中,我们也称之为方法。这个函数需要接受两个整数类型的参数,一个代表矩形的宽度,另一个代表这个矩形的高度。有时候,函数还需要返回一个结果,如果我们要编写一个求解矩形面积的函数,该函数就要返回一个代表面积的结果,函数的返回结果都是有类型的。所以一个函数的定义必须由三部分组成,定义一个函数的格式如下:

返回值类型 函数名(参数类型 形式参数1,参数类型 形式参数2,….) {

程序代码

return返回值; } 其中:

形式参数:在方法被调用时用于接收外部传入的数据的变量。 参数类型:就是该形式参数的数据类型。

返回值:方法在执行完毕后返还给调用它的程序的数据。 返回值类型:函数要返回的结果的数据类型。

实参:调用函数时实际传给函数形式参数的数据。 如果一个函数不需要返回值,我们可以省略最后的return语句。如果你的函数里没有return 语句,则编译时系统会自动在函数的最后添加一个“return;”。

?

独家见解:如何理解函数返回值类型为void的情况,如上面打印矩形的函数,不用返回任

何结果,那么,返回值类型那里该填什么呢?对于这种情况,我们就用void作为返回值类型,意思是“不知道是什么类型,可定义函数时又非要填写一个返回值类型,就用它充数吧!”

下面我们再编写一个求矩形面积的函数,来了解一下函数有返回值的情况。 程序清单:Func3.java

public class Func3 {

public static int getArea(int x,int y)

{ }

public static void main(String [] args) {

int area = getArea(3,5);

System.out.println(\

System.out.println(\ getArea(6,10); }

return x*y;

36

在上面的getArea函数中,用到了一个“return(返回值)” 语句,用于终止函数的运行并返回该函数的结果给调用者。

如果函数没有返回值或调用程序不关心函数的返回值,可以用下面这样的格式调用定义的函数:

函数名(实参1,实参2,….) 如上面的getArea(6,10);语句。

如果调用程序需要函数的返回结果,我们要用下面这样的格式调用定义的函数:: 变量 = 函数名(实参1,实参2,….) 如上面int area = getArea(3,5);语句。

对于有返回值的函数调用,我们也可以在程序中直接使用返回的结果,如这一句:

System.out.println(\

我们还可以在函数的中间使用return 语句提前返回,如打印矩形面积的函数,首先应检查传入的参数(即宽度和高度)是否为负数,为负则提前返回。修改上面的Func3.java,如下: 程序清单:Func4.javapublic class Func4

{

public static int getArea(int x,int y) { }

public static void main(String [] args) {

int area = getArea(3,5);

System.out.println(\

System.out.println(\ getArea(6,10); } }

if(x<=0|y<=0) return x*y; return -1;

这样的程序对传入的参数值进行了检查控制,明显要专业得多,也是软件编码规范中的一个起码要求。很多程序错误都是由非法参数引起的,我们应该充分理解并有效地使用类似上面的方式来防止此类错误。

Java中所有函数都包含在类里面,在Java的一个类中定义的函数也叫这个类的方法(method),本书中提到的函数就是方法,方法就是函数。

2.3.2函数的参数传递过程

下面我们来看一下函数的参数传递过程,如图:

37

图2.4

前面讲过,Java程序运行时虚拟机会先找到这个程序的main函数,接着从main函数里面取出一条一条代码来执行,以上面的程序Func4.java为例,当执行到int area=getArea(3,5);这个语句时,程序会跳转到getArea(int x,int y)这个函数的内部去执行,先把实参(3,5)分别赋值给形式参数(int x,int y),然后返回x*y的结果(return x*y;),接着回到main里面,把结果赋值给整型变量area。这就是函数的参数传值整个过程。

?

多学两招:形式参数x和y就相当于函数getArea中定义的两个局部变量,在函数被调用

时创建,并以传入的实参作为初始值,函数调用结束时也就被释放了,不会影响到主程序中其他的x和y(如果有的话),因为它们属于不同作用域中的变量,它们是互不相干的变量。

2.3.3函数的重载

函数的重载就是在同一个类中允许同时存在一个以上的同名函数,只要它们的参数个数或类型不同即可。在这种情况下,该函数就叫被重载(overloaded)了,这个过程称为函数的重载(method overloading)。

如下面的程序: 程序清单:Test.java

public class Test {

public static void main(String [] args) { }

int isum; double fsum; isum=add(3,5); isum=add(3,5,6); fsum=add(3.2,6.5);

38

public static int add(int x,int y) { }

public static int add(int x,int y,int z) { }

public static double add(double x,double y) { } }

return x+y; return x+y+z; reutrn x+y;

Java的编译器很聪明,能够根据调用函数时所传递的参数的个数和类型选择相应的函数。重载函数的参数列表必须不同,要么是参数的个数不同,要么是参数的类型不同。重载函数的返回值类型可以相同,也可以不同。

思考一下:如果两个方法的参数类型和个数完全一样,返回值类型不同,行不行呢?如果你是Java的设计者,而且你的用户在程序里编写了这样的两个方法,在调用时,你能根据他所传递的参数来为他选择到底该用那个吗?显然没有办法吧!那就是不能这样做呗!学编程不需要死记硬背,靠的是动脑筋来思考,这样的学习才能做到举一反三、触类旁通。

2.4 Java中的运算符

运算符是一种特殊符号,用以表示数据的运算、赋值和比较。一般由一至三个字符组成,但Java将其视为一个符号。运算符共分以下几种:

? 算术运算符 ? 赋值运算符 ? 比较运算符 ? 逻辑运算符 ? 移位运算符

2.4.1 算术运算符

加减乘除的四则运算相信读者都很熟悉了,在此不再赘述,这里给出一个表来供读者参考:

算术运算符 运算符 + - + - * / 运算 正号 负号 加 减 乘 除 范例 +3 b=4;-b; 5+5 6-4 3*4 5/5 结果 3 -4 10 2 12 1 39

% ++ ++ -- -- + 取模 自增(前) 自增(后) 自减(前) 自减(后) 字符串相加 5%5 a=2;b=++a; a=2;b=a++; a=2;b=--a a=2;b=a-- \0 a=3;b=3 a=3;b=2 a=1;b=1 a=1;b=2 \表2.3

“+”除字符串相加功能外,还能把非字符串转换成字符串,条件是该表达式中至少有一个字符串对象,如: \的结果是\。

?

脚下留心:

? 由于Java运算符有从左到右的优先顺序,这里请特别注意一下++a和a++以及--a和a--的区别,

b=++a是a先自增,a自己变了后才赋值给b,而b=a++是先赋值给b,a后自增。

? 如果对负数取模,可以把模数负号忽略不记,如:5%-2=1。但被模数是负数就另当别论了。 ? 对于除号“/”,它的整数除和小数除是有区别的:整数之间做除法时,只保留整数部分而舍

弃小数部分。猜一下这三句代码的结果:int x=3510;x=x/1000*1000;System.out.println(x),再运行一下,看看结果。再想一下为什么?你猜想的结果可能是3510,但实际运行结果是3000,其实很简单,在程序运行到表达式“x/1000”的时候,它的结果是3,而不是3.51。

?多学两招:读者在日常的编程当中,尽量多读一些别人优秀的源程序,积累一些好的算法,

这样对你以后的工作会有很大帮助。如下面的留言板分页问题,和走马灯问题,有的程序员可能会用到一大堆的if else和for语句来判断和循环,作者在这里给出两个经典的算法。

思考题1:某个培训中心要为新到的学员安排房间,假设共有x个学员,每个房间可以住6人,让你用一个公式来计算他们要住的房间数(千万不要像向我以前的有些学员开玩笑,说男生和女生是不能分在一块的,我们就不在这考虑了)?

答案:(x+5)/6。这种算法还可用在查看留言板的分页显示上: 其中x是总共的留言数,6是每页显示的留言数,结果就是总共有多少页。

思考题2:假设你要让x的值在0至9之间循环变化,请写出相应的程序代码。 答案: int x=0; while(true) {

x = (x+1); }

这样,x的取值总在0与9之间循环。

2.4.2 赋值运算符

如下表:

赋值运算符 运算符 = += 运算 赋值 加等于 范例 a=3;b=2; a=3;b=2;a+=b; 结果 a=3;b=2; a=5;b=2; 40

-= *= /= %= 减等于 乘等于 除等于 模等于 a=3;b=2;a-=b; a=3;b=2;a*=b; a=3;b=2;a/=b a=3;b=2;a%=b a=1;b=2; a=6;b=2; a=1;b=2; a=1;b=2; 表2.4

注:在JAVA里可以把赋值语句连在一起,如: x = y = z = 5 ;

在这个语句中,所有三个变量都得到同样的值5。

还可以把x = x + 3简写成x += 3,所有运算符都可以此类推,看多了,写多了就习惯了。

2.4.3 比较运算符

比较运算符的结果都是boolean型的,也就是要么是true,要么是false,如下表:

比较运算符 运算符 == != < > <= >= instanceof 运算 相等于 不等于 小于 大于 小于等于 大于等于 范例 4==3 4!=3 4<3 4>3 4<=3 4>= 表2.5 结果 False True False True False False 检查是否是类的对象 \Ture ?

脚下留心:比较运算符“==”不能误写成“=”,特别是在C/C++中,如果你少写了一个“=”,

那就不是比较了,整个语句变成了赋值语句,所以有经验的人干脆写成int x=5;if(3==x)这种样式,将常量放在“==”前面,万一不小心而少写了一个“=”,就变成了给常量赋值,编译器报错。对于Java,则不用这样处心积虑,即使写成if(x =3),编译器也会报错,因为Java的条件判断只接受布尔值,要么是true,要么是false。不过,也不能掉以轻心,如果你正好定义了一个布尔变量b,比如b=false;然后用if(b = true);这一句,编译器就不会报错了,显然与你的愿望背道而驰。

2.4.4 逻辑运算符

逻辑运算符用于对boolean型结果的表达式进行运算,运算的结果都是boolean型,如下表:

逻辑运算符 运算符 & | ^ ! && || 运算 AND(与) OR(或) XOR(异或) Not(非) AND(短路) OR(短路) 范例 false&true false|true true^false !true false&&true false||true 表2.6

41

结果 false true true false false true “&”和“&&”的区别在于,如果使用前者连接,那么无论任何情况,“&”两边的表达式都会参与计算。如果使用后者连接,当“&&”的左边为false,则将不会计算其右边的表达式。

请看下面的例子:

public class TestAnd {

public static void main(String[] args) {

int x=0; int y=0;

if(x!=0 && y==y/x) } }

System.out.println(\

上面例子中,由于while语句的判断条件中的第一个布尔表达式是不成立的,程序就不会判断第二个布尔表达式的值,这就是“短路”。如果两个表达式之间用“&”来连接,而且恰好又碰到上面所示的特殊情况,程序运行时就会出错。

OR运算符叫逻辑或,由“|”或“||”连接两个布尔表达式,只要运算符两边任何一个布尔表达式为真,该组合就会返回true值。

“|”和“||”的区别与“&”和“&&”的区别一样。

XOR运算符叫做异或,只有当“^”连接的两个布尔表达式的值不相同时,该组合才返回true值。如果两个都是true或都是false,该组合将返回false值。

2.4.5位运算符

我们知道,任何信息在计算机中都是以二进制的形式保存的,“&”、“|”和“^”除了可以作为逻辑运算符,也可以做为位算符,它们对两个操作数中的每一个二进制位都进行运算。

只有参加运算的两位都为1,&运算的结果才为1,否则就为0。 只有参加运算的两位都为0,|运算的结果才为0,否则就为1。 只有参加运算的两位不同,^运算的结果才为1,否则就为0。 除了这些位运算操作外,我们还可以对数据按二进制位进行移位操作,Java的移位运算符有三种:

<< 左移 >> 右移

>>> 无符号右移

左移很简单,就不用多说了,右移则有些道道要讲了。对于C语言来说,无符号位的整数右移时,左边移空的高位都会填入0。有符号的数右移时,如果最高位为0,左边移空的高位都会填入0,但对最高位为1时,左边移空的高位是填入0还是1,要取决于所在的计算机系统,有的系统填入0,也有的系统是填入1,这也是C语言不跨平台的一个小的方面。

Java语言是跨平台的,不能像C语言有这样的二义性,对Java来说,有符号的数据(Java语言中没有无符号的数据类型)用“>>”移位时,如果最高位是0,左边移空的高位就填入0,如果最高位是1,左边移空的高位就填入1。同时,Java也提供了一个新的移位运算符“>>>”,不管通过“>>>”移位的整数最高为是0还是1,左边移空的高位填入0。

42

图2.5

为了让读者能够亲身感受到这“>>”和“>>>”的区别,我们编写一个程序,来查看其输出结果。

程序清单:ShiftTest.java

public class ShiftTest {

public static void main(String [] args) {

int x=0x80000000; int y=0x80000000;

x=x>>1; y=y>>>1;

System.out.println(“0x80000000>>1 = ” + Integer.toHexString(x)); System.out.println(“0x80000000>>>1 = ” + Integer.toHexString(y)); } }

运行结果如下:

0x80000000>>1 = c0000000 0x80000000>>>1 = 40000000

在上面的程序中,我们用0x80000000这个十六进制表示的整数来作为我们的用例数据,因为我们很容易看出它的二进制形式1000,0000, 0000, 0000, 0000, 0000, 0000, 0000。

为了直观,对移位后的结果,我们也用十六进制来显示,Java提供的Integer.toHexString这个静态函数正好可以完成这种需求。

0xc0000000的二进制形式为1100,0000, 0000, 0000, 0000, 0000, 0000, 0000。 0x40000000的二进制形式为0100,0000, 0000, 0000, 0000, 0000, 0000, 0000。 通过上面的例子,我们很清楚的看到了“>>”和“>>>”运算符的区别。

读者可能会问:你怎么想到将整数转换成十六进制的字符串,要用Integer.toHexString方法呢?

还记得我们在第一章中讲过的JDK文档工具吗?当我们要完成某个功能时,可以通过模糊查询来查找具体的信息。Hex是十六进制的英文单词,我首先在chm格式的帮助文档的“索引”栏中输入hex,没有发现有价值的提示,如图2.6:

43

图2.6

接着,我想到了tohex(到十六进制),在“索引”栏中输入tohex,果然就找到了我期望的东西,如图2.7

图2.7

查阅文档没有什么难的,唯一要求的就是你的英语水平不要太低,一般说来词汇量要到英语三级的水平。等用多了,你就会变得非常熟悉这种方式的,初学者没必要为此担心!

注意:以上移位运算符适用数据类型有byte、short、char、int、long。

1.对低于int型的操作数将先自动转换为int型再移位。

2.对于int型整数移位a>>b,系统先将b对32取模,得到的结果才是真正移位的位数。 例如:a>>33和a>>1结果是一样的,a>>32的结果还是a原来的数字。 3.对于long型整数移位时a>>b ,则是先将移位位数b对64取模。

?多学两招:移位能为我们实现整数除以或乘以2的n次方的效果,如:

44

x>>1的结果和x/2的结果是一样的,x<<2和x*4的结果也是一样的。总之,一个数左移n位,就是等于这个数乘以2的n次方,一个数右移n位,就是等于这个数除以2的n次方。请思考:屏幕上每个像素的颜色用一个8位的二进制数据表示,那么屏幕上最多能显示多少种颜色?这其实就是如何用程序实现求2的x次方的问题。答案:y = 1<< x;

?

脚下留心:移位不会改变变量本身的值。如a>>1;在一行语句中单独存在,毫无意义,因为

它没有改变a的值,也没有把它赋值给别的变量。作者在深更半夜编程时,常犯这错误,也帮不少人排除过类似的错误。

2.4.6 运算符的优先级

以上介绍的运算符都有不同的优先级,所谓优先级就是在表达式运算中的运算顺序,表2.3列出了包括分隔符在内的所有运算符的优先级顺序,上一行中的运算符总是优先于下一行的:

. [] () {} ; , ++ -- ~ ! (数据类型) * / % + - << >> >>> < > <= >= == != & ^ | && || ?: = *= /= %= += -= <<= >>= >>>= &= ^= |= 表2.3

根据上面表中显示的优先级,我们来分析一下int a =2;int b = a + 3*a;语句的执行过程,程序先执行3*a后再与a相加,最后将结果赋值给等号左边的b,所以b的结果为8。我们可以使用括号改变运算符的优先级,如我们将第二句修改成int b = (a+3) * a;后,程序先执行括号中的a+3后再与a相乘,最后将结果赋值给等号左边的b,所以b的的结果为10。

对于int a =2; int b= a + 3 * a++;这样的语句,b最终等于多少呢?作者试验得到的结果是8。

对于int a =2; int b= (a ++)+ 3 * a;这样的语句,b最终等于多少呢?作者试验得到的结果是11。

其中的原因难以理解,与其去思考其中的原因,我们还不如将上面的第二句改为几条语句来实现我们想要的结果。我可以给大家一个宝贵的经验,不要在一行中编写太复杂的表达式,也就是不要在一行中进行太多的运算,在一行中进行太多的运算并不能为你带来什么好处,相反只能带来坏处,它并不比改成几条语句的运行速度快,它除可读行差外,还极容易出错。

对于这些优先级的顺序,读者不用刻意去记,有个印象就行。如果你实在弄不清这些运算先后关系的话,就用括号或是分成多条语句来完成你想要的功能,因为括号的优先级是最高的,多使用括号能增加程序的可读性,也是一种良好的编程习惯,这也是软件编码规范的一个要求。

45

2.5 程序的流程控制

从结构化程序设计角度出发,程序有三种结构:

? 顺序结构 ? 选择结构 ? 循环结构

2.5.1 顺序结构

顾名思义,顺序结构就是程序从上到下一行一行执行的结构,中间没有判断和跳转,直到程序结束。

2.5.2 if选择语句

第一种应用:

if 语句是条件判断语句,下面的代码是if语句的一种形式: int x=0; if (x==1)

System.out.println(\;

如果x的值等于1则打印出“x=1”,否则什么也不做。

第二种应用:

下面的代码是if语句的另外一种形式,其中包括了else关键字: int x=0; if(x==1)

System.out.println(\; else

System.out.println(\;

如果x的值等于1则打印出“x=1”,否则将打印出“x!=1”。

?多学两招:

还有一种更专业的写法:变量 = 布尔表达式?语句1:语句2; 我们看一下下面的代码:

if(x>0)

y=x; else

y=-x;

这段代码也可以简写成下面的形式:

y = x>0?x:-x;

这是一个求绝对值的语句,如果x>0,就把x赋值给变量y,如果x不大于0,就把-x赋值给前面的y。也就是:如果问号“?”前的表达式为真,则计算问号和冒号中间的表达式,并把结果赋

46

值给变量y,否则将计算冒号后面的表达式,并把结果赋值给变量y,这样的写法在C语言中经常用到,好处在于代码简洁,并且有一个返回值。 第三种应用:

if语句中还可以有多个语句的情况:

int x=0; if(x==1) {

System.out.println(\; System.out.println(\; } else {

System.out.println(\; System.out println(\}

由此可以看出,多个语句必须用大括号括起来形成一个复合语句。 第四种应用:

我们还可以用else if进行更多的选择,下面是使用else if 语句的情况。

if (x==1)

System.out.println(\else if (x==2)

System.out.println(\; else if (x==3)

System.out.println(\; else

System.out.println(\

程序首先判断x是否等于1,如果是,就执行打印“x=1”,如果不是,程序将继续判断x是否等于2,如果x等于2,则打印“x=2”,如果也不等于2,程序将判断x是否等于3,如果是,则打印“x=3”,如果还不等于,就执行else后的语句。也可以不要else 语句,那就是上面的条件都不满足时,就什么也不做。 第五种应用:

if语句还可以嵌套使用,如:

if (x == 1)

if(y == 1)

System.out.println(\System.out.println(\else

else

if(y == 1)

System.out.println(\System.out.println(\else

在使用if嵌套语句时,最好使用{}来确定相互的层次关系,如下面的语句:

if (x == 1)

47

if(y == 1)

System.out.println(\System.out.println(\else

else if(x !=1)

if(y == 1)

System.out.println(\System.out.println(\else

我们很难判定最后的else语句到底属于哪一层的,编译器是不能根据书写格式来判定的,我们可以使用{}来加以明确。

if (x == 1) { }

else if(x !=1) { }

if(y == 1)

System.out.println(\System.out.println(\else if(y == 1)

System.out.println(\System.out.println(\else

或者改为下面的格式,来表达另外的一种意思。

if (x == 1) { }

else if(x !=1) { } else

System.out.println(\if(y == 1)

System.out.println(\if(y == 1)

System.out.println(\System.out.println(\else

在Java中,if()和else if()括号中的表达式的结果必须是布尔型的(即ture或者false),这一点和C、C++不一样。

2.5.3 switch选择语句

48

我们可以把上面含有两个else if语句的代码写成switch语句的格式:

int x=2; switch(x) {

case 1:

System.out.println(\ break; case 2:

System.out.println(\ break; case 3:

System.out.println(\ break; default:

System.out.println(\}

x x=1 x=2 1

2

3

i

x=i … …

程序结果:

x=2

上面代码中,default语句是可选的,它接受除上面接受值以外的其他值,通俗的讲,就是谁也不要的都归它。switch语句判断条件可以接受int, byte, char, short型,不可以接受其他类型。

注意,不要混淆case与else if。 else if是一旦匹配就不在执行后面的else语句,而switch一旦碰到第一次case匹配,就会开始顺序执行以后所有的程序代码,而不管后面的case条件是否匹配,后面case条件下的所有代码都将被执行,直到碰到break语句为止。所以,如果不写break语句,例如删除System.out.print(\后面的break语句,程序运行的结果将是:

x=2 x=3

所以一定要记住用break退出switch 。case后面可以跟多个语句,这些语句可以不用大括号括起来,如果你喜欢,非要将多个语句用大括号括起来当然也可以。

思考题:用同一段语句来处理多个case条件,程序该如何编写?答案如下:

case 1: case 2: case 3:

System.out.println(\

System.out.println(\ break;

case 4: case 5:

System.out.println(\”);

不要死记硬背,要从原理上去思考,还记得前面刚才讲的“case是一旦碰到第一次匹配,如果没有break,就会继续执行”这个原理吗?

2.5.4 while循环语句

49

while 语句是循环语句,也是条件判断语句,条件满足时执行,不满足时退出,请看下面代码:

int x=1; while(x<3) {

System.out.println(\ x++; }

运行结果如下:

x=1 x=2

?

脚下留心:while表达式的括号后一定不要加“;”,如:

int x=3; while(x==3);

System.out.println(\

这是初学者常犯的一个毛病,程序将认为要执行一条空语句,而进入无限循环,永远不去执行后面的代码,Java编译器又不会报错。所以你可能要浪费许多时间来调试也不知道错在哪。

do while语句

我们将上面while循环的代码改成:

int x=1; do {

System.out.println(\x++; }

while(x<3);

程序运行结果如下:

x=1 x=2

我们发现这两次的运行结果是一样的,区别只是do while的判断语句在结尾,所以中间代码是无论条件是否成立都要至少执行一次的。下面例子的while循环里面的代码就没有机会执行了,但尽管条件不成立,do while 循环中的代码还是执行了一次: 程序清单:TestDo.java

public class TestDo {

public static void main(String[] args) {

int x=3; while(x==0) {

System.out.println(\

50

x++;

}

int y=3; do {

System.out.println(\ y++; }

while(y==0); } }

程序运行结果如下:

ok2

2.5.5 for循环语句

先看一个例子:

for(int x=1;x<10;x++) {

System.out.println(\}

程序运行结果如下:

x=1 x=2 … x=9

在这里,我们介绍一下for语句后面小括号中的部分,这部分内容又被“;”隔离成三部分,其

中第一部分x=1是给x赋一个初值,只在刚进入for时执行一次;第二部分x<3是一个条件语句,满足就进入for循环, 循环执行一次后又回来执行这条语句,直到条件不成立为止;第三部分x++是对变量x的操作,在每次循环的末尾执行,读者可以把x++分别换成x+=2和x-=2来试验一下每次加2和每次减2的情景。

如上所述,上面的代码可以改写为:

int x=1; for(;x<10;) {

System.out.println(\ x++; }

通过这样改写,读者应该能够更好地理解for后面小括号中的三部分的各自作用了。

?多学两招:

for语句还有下面一种特定的语法格式:

for(;;) {

51

....... }

同样意义的还有while(true),这些都是无限循环,需要用break语言跳出循环,我们在以后的编程中都会遇到。

例如上面的代码又可以改为:

for(;;) { }

if(x<10) break; x++;

2.5.6 break与continue语句

break语句

break语句可以中止循环中的子语句和switch语句。一个无标号的break语句会把控制传给当前(最内)循环(while,do.for或Switch)的下一条语句。如果有标号,控制会被传递给当前方法中的带有这一标号的语句。如:

st:while(true) {

while(true) {

break st; } }

执行完break st;语句后,程序会跳出外面的while循环,如果不使用st标号,程序只会跳出里面的while循环。 continue语句

continue语句只能出现在循环语句(while,do,for)的子语句块中,无标号的continue语句的作用是跳过当前循环的剩余语句块,接着执行下一次循环。

请看下面打印1到10之间的所有奇数的例子,当i是偶数时就跳过本次循环后的代码,直接执行for语句中的第三部分,然后进入下一次循环的比较,是奇数就打印i:

程序清单:PrintOddNum.java

public class PrintOddNum {

public static void main(String [] args) {

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

if(i%2==0) continue;

System.out.println(i); }

}

52

}

2.6 数组

2.6.1 数组的基本概念

我们还是通过一个应用问题来引入我们对数组的讲解,来让大家了解什么是数组以及数组的作用。假设你需要在程序中定义一百个整数变量,并要求出这些变量相加的结果,如果你没有用过数组,这个程序该怎么写呢?至少我们需要定义一百个整数变量,如下所示:

int x0; int x1; …… …… int x98; int x99;

一下就要定义100个相似的变量,然后还要将这些变量一个一个的相加,这是一件令人畏惧的事情。有什么简单的方法来替代上述变量的定义方式呢?

我们可以将上面的定义改写为:

int x[] = new int[100];

上述语句的意义相当于一下子就定义了一百个int变量,变量的名称分别为x[0],x[1],??,x[98],x[99]。注意,第一个变量名为x[0],而不是x[1],最后一个变量名为x[99],而不是x[100],这种定义变量的方式就是数组。定义了这个数组,我们接着就可以使用简单的for循环语句来实现数组中的所有元素的相加了,程序代码如下:

int sum=0;

for(int i=0;i<100;i++)

sum += x[i];

?多学两招:为了充分和深入了解数组,我必须向大家讲解有关内存分配的一些背后知识。Java

把内存划分成了两种:一种是栈内存,另一种是堆内存。

我们在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配,当我们在一段代码块(也就是一对花括号{}之间)定义一个变量时,Java就在栈中为这个变量分配内存空间,当超过变量的作用域后,Java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用,我们先前讲到的知识都属于栈中分配的变量。

堆内存用来存放由new创建的对象和数组,在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理(关于自动垃圾回收器请参看第一章中的垃圾回收器介绍)。在堆中产生了一个数组或对象后,我们还可以在栈中定义一个特殊的变量,让栈中的这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量,我们以后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象,引用变量就相当于是我们为数组或对象起的一个名称(叫代号也行)。引用变量是普通的变量,定义时在栈中分配,引用变量在程序运行到其作用域之外后被释放。而数组和对象本身在堆中分配,即使程序运行到使用new产生数组和对象的语句所在的代码块之外,数组和对象本身占据的内存不会被释放,数组和对象在没有引用变量指向它时,才会变为垃圾,不能再被使用,但仍然占据内存空间不放,在随后一个不确定的时间被垃圾回收器收走(释放掉)。这也是Java比较吃内存的原因。

数组是多个相同类型数据的组合,实现对这些数据的统一管理,数组中的每一个数据也叫数组

53

的一个元素。我们来解释下面这句代码的语法。

int x[] = new int[100];

等号左边的int x[]相当于定义了一个特殊的变量符号x,x的数据类型是一个对int型的数组对象的引用,x就是一个数组的引用变量,其引用的数组的元素个数不定,就象我们定义一个基本类型的变量,变量值开始也是不确定的。等号右边的new int[100]就是在堆内存中创建一个具有一百个int变量的数组对象。int x[] = new int[100];就是将右边的数组对象赋值给左边的数组引用变量。因此,我们也可以将这一行代码分成两行来写。

int x[]; //定义了一个数组x,这条语句执行完后的内存状态如图2.8所示。 x=new int[100]; //数组初始化,这条语句执行完后的内存状态如图2.9所示。

图2.8

图2.9

执行第二句(x=new int[100];),在堆里面创建了一个数组对象,为这个数组对象分配了100个整数单元,并将数组对象赋值给了数组引用变量x。精通C语言的读者可能已经明白了,数组引用变量不就是C语言中的指针变量吗?数组对象不就是指针变量要指向的那个内存块吗?是的,Java内部还是有指针的,只是把指针的概念对用户隐藏起来了。

我们也可以改变x的值,让它指向另外一个数组对象,或者不指向任何数组对象。要想让x不指向任何数组对象,我们只需要将常量null赋值给x即可。如:x =null;, 这条语句执行完后的内存状态如图2.10所示。

54

图2.10

执行完x=null;语句后,原来通过new int[100]产生的数组对象不再被任何引用变量所引用,变成了“孤儿”,也就成了垃圾,直到垃圾回收器来将它释放掉。

new int[100]产生的的数组对象中的每个元素的初始值都是0,读者可以用下面的程序来测试一下:

程序清单:TestArray.java

public class TestArray {

public static void main(String [] args) {

int x[];

x=new int[100];

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

System.out.println(\}

} }

在Java中,我们还可以用下面的方式定义数组:

int [] x; // 方括号([])位于变量名之前。 x=new int[100];

两种定义数组方式的效果是完全一样的,差别只是人们的使用习惯不同罢了。

2.6.2 数组的静态初始化

我们也可以在定义数组的同时就为数组元素分配空间并赋值,也就是对数组的静态初始化。 如这一句:

int ia[] ={1,2,3,4};

等号右边相当于产生了一个数组对象,该数组有4个元素变量,这4个变量的取值分别是整数1、2、3、4。数组的长度等于右边{}中的元素的个数。我们有时也能见到下面定义数组的方式: int ia[]=new int[]{3,4,5};

注意:在Java语言中声明数组时,无论用何种方式定义数组,都不能指定其长度,例如下面的定义将是非法的。

int a[5];//编译时将出错

55

2.6.3 使用数组时要注意的问题

必须对数组引用变量赋予一个有效的数组对象(通过new产生或是用{}静态初始化而产生)后,才可以引用数组中的每个元素,下面的代码将会导致运行时出错,如图2.11。

public class TestArray {

public static void main(String [] args)

{

int a[] = null; a[0]=1;

System.out.println(a[0]); } }

图2.11

上面的错误告诉我们,运行时会有空指针异常错误(NullPointerException,关于异常的知识我们放在后面的章节讲解),因为a还没有指向任何数组对象(相当于c语言中的指针还没有指向任何内存块),所以还无法引用其中的元素。

还有一点请初学者注意,譬如说我们下面的代码:

int ia[]=new int[]{1,2,3,4,5};

这行代码中我们定义了一个ia数组,它里面包含了5个元素,它们分别是: ia[0]=1 ia[1]=2 ia[2]=3 ia[3]=4 ia[4]=5

也就是说数组的第一个元素是ia[0],而不是ia[1]。最后一个元素是ia[4],而不是ia[5],如果我们不小心使用了ia[5],如下面的程序:

程序清单:TestArray.java

public class TestArray {

public static void main(String [] args) {

int ia[]=new int[]{1,2,3,4,5}; System.out.println(ia[5]); } }

56

就会发生“数组越界异常(ArrayIndexOutOfBoundsException)”,如图2.12。读者以后必须学会根据程序所报出的异常来判断究竟出了什么错误,并且看到这样的错误,就应该明白错误的原因所在了。

图2.12

要想避免“数组越界异常”这样的错误,我们必须要知道数组长度。数组引用对象的length属性可以返回数组的长度。示例程序代码如下:

public class TestArrayLength {

public static void main(String [] args) {

int ia[]=new int[]{1,2,3,4,5}; System.out.println(ia.length);

for(int i=0;i

System.out.println(\}

} }

2.6.4 多维数组

在Java 中并没有真正的多维数组,只有数组的数组,虽然在应用上很象C语言中的多维数组,但还是有区别的。在C语言中定义一个二维数组,必须是一个x*y二维矩阵块,类似我们通常所见到的棋盘,如图2.13所示:

图2.13

Java中多维数组不一定是规则矩阵形式,如图2.14所示:

57

图2.14

我们这样定义一个多维数组:

int xx[][];

表示定义了一个数组引用变量xx,第一个元素变量为xx[0],第n个元素变量为xx[n-1]。xx中的每个元素变量(xx[0]到xx[n-1])正好又是一个整数类型的数组引用变量,注意,这里只是要求每个元素都是一个数组引用变量,并没有要求它们所引用数组的长度是多少,也就是每个引用数组的长度可以不一样,我们还是看看下面的程序代码。

int[][] xx; xx=new int[3][];

这两句代码表示数组xx有三个元素,每个元素都是int []类型的一维数组。相当于定义了三个数组引用变量,分别为int xx[0][],int [] xx[1],int [] xx[2]。你完全可以把xx[0]想成是一个普通的变量名,只是正好是x、x、[、0、]这五个字母的组合罢了,另外,作者是故意对int xx[0][],int [] xx[1]这两个数组引用变量用不一样的方式书写,如果你还记得前面讲过的一维数组的几种表示方式,就知道作者想达到一箭双雕的目的,既帮助读者巩固了前面所学的知识,又便于读者通过对比而真正理解作者“把xx[1]想成是一个普通的变量名”的思想。

由于xx[0],xx[1],xx[2]都是数组引用变量,必须对它们赋值,指向真正的数组对象,才可以引用这些数组中的元素。

xx[0]=new int[3]; xx[1]=new int[2];

注意,xx[0]和xx[1]的长度可以不一样,数组对象中也可以只有一个元素。程序运行到这之后的内存分配情况如图2.15所示:

图2.15

xx[0]中的第二个元素用xx[0][1]来表示,如果我们要将整数5赋值给xx[0]中的第二个元素,写法如下:

xx[0][1] = 5;

如果数组对象正好是一个x*y形式的规则矩阵,我们不必向上面的程序一样,先产生高维的数组对象后,再逐一产生低维的数组对象,完全可以用一句代码在产生高维数组对象的同时,产生所有的低维数组对象。

int xx[][]=new int[2][3];

上面的代码产生了一个2*3形式的二维数组,其内存布局如图2.16:

58

图2.16

我们也可以象一维数组一样,在定义数组的同时就为多维数组元素分配空间并赋值,也就是对多维数组的静态初始化。如下面这句代码 int[][] xx={{3,2,7},{1,5},{6}}; 定义了一个如图2.17中的多维数组:

图2.17

与一维数组一样,在声明多维数组时不能指定其长度,例如下面的定义将是非法的。

int xx[3][2] = {{3,2},{1,3},{7,5}}; //编译出错

2.6.5 一些与数组操作相关的函数

既然学到数组,我们就介绍两个与数组操作相关的函数: 1. 使用System.arraycopy()函数拷贝数组。方法如下:

System.arraycopy(source, 0, dest, 0, x);

这行代码的意思就是:复制源数组中从下标0开始的x个元素到目的数组,从目标数组的下标0所对应的位置开始存储。我们可以从JDK文档中了解到arraycopy更详细的使用说明,如图2.18所示:

59

图 2.18

程序清单:TestArrayCopy.java

public class TestArrayCopy {

public static void main(String [] args) {

int ia[]=new int[]{1,2,3,4,5}; int ib[]=new int[]{9,8,7,6,5,4,3};

System.arraycopy(ia,0,ib,0,3);

// 复制源数组中从下标0开始的3个元素到目的数组,从下标0的位置开始存储。 for(int i=0;i

for(int j=0;j

注意:你复制的数组元素的个数一定不要超过目的数组的长度。否则会有异常产生。

60

2. 用Arrays.sort 来排序数组,把里面的元素按从小到大的顺序逐一排列,然后把排序后的数组输出在命令行窗口上: 程序清单:ArrSort.java

import java.util.*;

//关于上面这条语句的细节,读者暂且不用理会,在后面的章节中会有详细的讲解。 public class ArrSort {

public static void main(String [] args) {

int ia[]=new int[]{1,2,4,8,3};

Arrays.sort(ia); //对数组ia进行排序

for(int i=0;i

System.out.print(ia[i]);//输出数组ia }

}

程序运行结果是:

12348

关于Arrays.sort方法的帮助信息,读者现在应该可以自己动手查阅JDK文档解决了吧!

第2章 Java 编程基础 ................................................................................................................. 24

2.1 Java基本语法格式 ......................................................................................................... 24

2.1.1 Java代码的落脚点 .............................................................................................. 24 2.1.2 Java是严格区分大小写的 .................................................................................. 24 2.1.3 Java是一种自由格式的语言 .............................................................................. 24

脚下留心:1.字符串的断行问题 2. 分号(;)问题

2.1.4 java程序的注释 .................................................................................................. 24

脚下留心:程序注释中应注意的一些问题 不得不说:养成良好的编程风格

2.1.5 Java中的标识符 .................................................................................................. 25 2.1.6 Java的关键字 ...................................................................................................... 26 2.1.7 Java中的常量 ...................................................................................................... 26

多学两招:特殊字符的表示

2.2 变量及变量的作用域 ....................................................................................................... 27

2.2.1 变量的概念 .......................................................................................................... 27 2.2.2 Java的变量类型 .................................................................................................. 28

独家见解:数值所代表的意义

2.2.3 注意变量的有效取值范围 .................................................................................. 29 2.2.4 基本数据类型之间的转换 .................................................................................. 29

独家见解:轻松理解类型转换

2.2.5 表达式的数据类型自动提升 .............................................................................. 31

多学两招:字符串中的加号(+)问题

61

2.2.6 变量的作用域 ...................................................................................................... 32 脚下留心:Java与C/C++的区别

2.2.7 局部变量的初始化 .............................................................................................. 33 2.3 函数与函数的重载 ........................................................................................................... 33

2.3.1函数 ......................................................................................................................... 33 独家见解:如何理解函数返回值类型为void的情况

2.3.2函数的参数传递过程 ............................................................................................. 37 多学两招:形式参数的作用

2.3.3函数的重载 ............................................................................................................. 38 2.4 Java中的运算符 ............................................................................................................. 39

2.4.1 算术运算符 ............................................................................................................ 39 脚下留心:1.注意++a和a++以及--a和a--的区别 2.对负数取模的问题

3.的整数除和小数除的区别 多学两招:两个经典算法

2.4.2 赋值运算符 ............................................................................................................ 40 2.4.3 比较运算符 ............................................................................................................ 41 脚下留心:“==” 运算符的注意事项

2.4.4 逻辑运算符 ............................................................................................................ 41 2.4.5位运算符 ................................................................................................................. 42 多学两招:移位的特殊功能 脚下留心:使用移位运算符应注意的事项

2.4.6 运算符的优先级 .................................................................................................... 45 2.5 程序的流程控制 ............................................................................................................. 46

2.5.1 顺序结构 ................................................................................................................ 46 2.5.2 if选择语句 .......................................................................................................... 46 多学两招:一种更简单的写法

2.5.3 switch选择语句 .................................................................................................. 48 2.5.4 while循环语句 .................................................................................................... 49 脚下留心:while表达式的注意事项

2.5.5 for循环语句 ........................................................................................................ 51 多学两招:无限循环

2.5.6 break与continue语句 ........................................................................................ 52 2.6 数组 ................................................................................................................................. 53

2.6.1 数组的基本概念 .................................................................................................. 53 多学两招:内存分配的奥秘

2.6.2 数组的静态初始化 .............................................................................................. 55 2.6.3 使用数组时要注意的问题 .................................................................................. 56 2.6.4 多维数组 .............................................................................................................. 57 2.6.5 一些与数组操作相关的函数 ................................................................................ 59

62

本文来源:https://www.bwwdw.com/article/146a.html

Top