2. Java 语言的基础
2.1. 引言
我们将首先将Java视为一种传统的编程语言。关于对象的内容,我们稍后再讲。
在程序中,有两样东西
- 数据
- 操作数据的指令
我们通常力求将数据与指令分离:

2.2. Java 数据
Java 使用以下数据类型:
- 整数
- 浮点数
- 字符和字符串
- 布尔值
- 对象
2.2.1. 预定义数据类型
类型 | 编码 | 范围 |
char | 2字节 | Unicode字符 |
int | 4字节 | [-231, 231 -1] |
long | 8字节 | [-263, 263 -1] |
字节 | 1字节 | [-27, 27 -1] |
短整型 | 2字节 | [-2(15), 2(15),-1] |
float | 4字节 | [3.410⁻³⁸, 3.410³⁸](绝对值) |
双精度 | 8字节 | [1.7 × 10⁻³⁰⁸, 1.7 × 10³⁰⁸] 的绝对值 |
布尔型 | 1 位 | true, false |
字符串 | 对象引用 | 字符串 |
日期 | 对象引用 | 日期 |
字符 | 对象引用 | 字符 |
整数 | 对象引用 | int |
长整型 | 对象引用 | long |
字节 | 对象引用 | 字节 |
浮点数 | 对象引用 | float |
双精度 | 对象引用 | double |
布尔值 | 对象引用 | 布尔 |
2.2.2. 字面量数据表示法
整数 | 145、-7、0xFF(十六进制) |
双精度 | 134.789, -45E-18 (-45 × 10⁻¹⁸) |
浮点数 | 134.789F, -45E-18F (-45 × 10⁻¹⁸) |
字符 | 'A', 'b' |
字符串 | "today" |
布尔值 | true, false |
日期 | new Date(13,10,1954) (日, 月, 年) |
2.2.3. 数据声明
2.2.3.1. 声明的作用
程序操作的数据由名称和类型来描述。这些数据存储在内存中。当程序被编译时,编译器会根据程序员所做的声明,为每段数据分配一个由地址和大小定义的内存位置。
此外,这些声明还使编译器能够检测编程错误。因此,以下操作
x=x*2;
将被判定为错误,例如当 x 是字符串时。
2.2.3.2. 常量的声明
常量的声明语法如下:
**<mark style="background-color: #ffff00">final</mark>**<mark style="background-color: #ffff00"> 类型 名称=值; </mark> //定义常量 name=value
例:final float PI=3.141592F;
注
为什么要声明常量?
- 如果给常量起一个有意义的名称,程序会更易于阅读:
例:*final float VAT\_rate=0.186F*;
- 如果需要修改“常量”,程序的修改也会更加容易。因此,在前面的例子中,如果增值税税率变为 33%,唯一需要修改的就是更改定义其值的语句:
*final float tax\_rate=0.33F*;
如果我们在程序中显式使用了 0.186,那么就必须修改大量语句。
2.2.3.3. 变量声明
变量通过名称来标识,并指向一种数据类型。Java 变量名由 n 个字符组成,其中第一个字符必须是字母,其余字符可以是字母或数字。Java 区分大小写。因此,变量 FIN 和 fin 是不同的。
变量可以在声明时进行初始化。声明一个或多个变量的语法如下:
其中类型标识符可以是预定义的类型,也可以是程序员定义的对象类型。
2.2.4. 数字与字符串之间的转换
数字 -> 字符串 | "" + 数字 |
字符串 -> 整数 | Integer.parseInt(字符串) |
字符串 -> 长整型 | Long.parseLong(字符串) |
字符串 -> 双精度 | Double.valueOf(字符串).doubleValue() |
字符串 -> 浮点数 | Float.valueOf(string).floatValue() |
以下是一个演示数字与字符串之间转换主要技巧的程序。如果字符串不表示一个有效的数字,将其转换为数字可能会失败。 这会导致一个致命错误,在 Java 中称为异常。可以使用以下 try/catch 代码块来处理此错误:
try{
appel de la fonction susceptible de générer l'exception
} catch (Exception e){
traiter l'exception e
}
instruction suivante
如果函数未抛出异常,程序将执行下一条语句;否则,程序将进入 catch 子句的处理块,随后继续执行下一条语句。我们稍后将再次讨论异常处理。
import java.io.*;
public class conv1{
public static void main(String arg[]){
String S;
final int i=10;
final long l=100000;
final float f=(float)45.78;
double d=-14.98;
// number --> string
S=""+i;
affiche(S);
S=""+l;
affiche(S);
S=""+f;
affiche(S);
S=""+d;
affiche(S);
//boolean --> string
final boolean b=false;
S=""+new Boolean(b);
affiche(S);
// string --> int
int i1;
i1=Integer.parseInt("10");
affiche(""+i1);
try{
i1=Integer.parseInt("10.67");
affiche(""+i1);
} catch (Exception e){
affiche("Erreur "+e);
}
// string --> long
long l1;
l1=Long.parseLong("100");
affiche(""+l1);
try{
l1=Long.parseLong("10.675");
affiche(""+l1);
} catch (Exception e){
affiche("Erreur "+e);
}
// chain --> double
double d1;
d1=Double.valueOf("100.87").doubleValue();
affiche(""+d1);
try{
d1=Double.valueOf("abcd").doubleValue();
affiche(""+d1);
} catch (Exception e){
affiche("Erreur "+e);
}
// string --> float
float f1;
f1=Float.valueOf("100.87").floatValue();
affiche(""+f1);
try{
d1=Float.valueOf("abcd").floatValue();
affiche(""+f1);
} catch (Exception e){
affiche("Erreur "+e);
}
}// fine hand
public static void affiche(String S){
System.out.println("S="+S);
}
}// end of class
结果如下:
S=10
S=100000
S=45.78
S=-14.98
S=false
S=10
S=Erreur java.lang.NumberFormatException: 10.67
S=100
S=Erreur java.lang.NumberFormatException: 10.675
S=100.87
S=Erreur java.lang.NumberFormatException: abcd
S=100.87
S=Erreur java.lang.NumberFormatException: abcd
2.2.5. 数据数组
Java 数组是一种允许将同类型数据归类到单一标识符下的对象。其声明方式如下:
Type Array[] = new Type[n] 或 Type[] Array = new Type[n]
这两种语法都是有效的。n 表示数组可容纳的元素个数。语法 Array[i] 表示索引为 i 的元素,其中 i 属于 [0,n-1] 范围。如果 i 不属于 [0,n-1] 范围,对元素 Array[i] 的任何引用都会引发异常。
二维数组可以按以下方式声明:
Type Array[][] = new Type[n][p] 或 Type[][] Array = new Type[n][p]
语法 Array[i] 表示数组 Array 的第 i 个数据元素,其中 i 属于区间 [0,n-1]。Array[i] 本身也是一个数组:Array[i][j] 表示 Array[i] 的第 j 个元素,其中 j 属于区间 [0,p-1]。对 Array 元素的任何索引错误引用都会引发致命错误。
以下是一个示例:
public class test1{
public static void main(String arg[]){
float[][] taux=new float[2][2];
taux[1][0]=0.24F;
taux[1][1]=0.33F;
System.out.println(taux[1].length);
System.out.println(taux[1][1]);
}
}
以及执行结果:
数组是一个具有 length 属性的对象:该属性表示数组的大小。
2.3. Java 基本命令
我们区分
- 由计算机执行的基本指令。
- 控制程序流程的指令。
在考察微计算机及其外围设备的结构时,基本指令的含义便会变得清晰。

-
从键盘读取信息
-
处理信息
-
将信息写入屏幕
-
从磁盘文件中读取信息
-
将信息写入磁盘文件
2.3.1. 将信息写入屏幕
屏幕输出语句的语法如下:
System.out.println(表达式) 或 System.err.println(表达式)
其中表达式可以是任何能够转换为字符串并在屏幕上显示的数据类型。在前面的示例中,我们看到了两个打印语句:
System.out 会向文本文件写入内容,默认情况下该文件即为屏幕。System.err 也是如此。这些文件分别被分配了编号(或描述符)1 和 2。键盘输入流(System.in)也被视为文本文件,其描述符为 0。DOS 和 Unix 都支持命令管道:
命令1写入 System.out 的所有内容都会通过管道(重定向)传输到命令2的 System.in 输入端。换句话说,命令2 从 System.in 读取由命令1通过 System.out 生成的数据,因此这些数据不再显示在屏幕上。这种机制在 Unix 中被广泛使用。 在此管道连接中,System.err 流不会被重定向:它会写入屏幕。这就是为什么它被用于输出错误消息(因此得名 err):我们可以确信,当命令通过管道连接时,错误消息仍会出现在屏幕上。因此,我们应该养成使用 System.err 流而非 System.out 流将错误消息写入屏幕的习惯。
2.3.2. 读取键盘输入的数据
来自键盘的数据流由类型为 InputStream 的 System.in 对象表示。此类对象允许逐字符读取数据。程序员需要自行从该字符流中提取相关信息。InputStream 类型不支持一次性读取整行文本。而 BufferedReader 类型则可以通过 readLine 方法实现这一功能。
要读取通过键盘输入的文本行,我们需要从类型为 InputStream* 的 System.in* 输入流中创建一个类型为 BufferedReader 的新输入流:
此处不再详细解释该语句,因其涉及对象构造的概念。我们将直接使用它。
创建流可能会失败:此时会产生一个致命错误,在 Java 中称为异常。每当某个方法可能引发异常时,Java 编译器都要求程序员对其进行处理。因此,要创建前面的输入流,我们实际上必须这样写:
BufferedReader IN=null;
try{
IN=new BufferedReader(new InputStreamReader(System.in));
} catch (Exception e){
System.err.println("Erreur " +e);
System.exit(1);
}
同样,这里我们不会详细讨论异常处理。一旦创建了前面的 IN 流,我们就可以使用以下语句读取一行文本:
键盘输入的行被存储在变量 ligne 中,随后程序即可使用该行。
2.3.3. 输入/输出示例
以下是一个演示键盘/屏幕输入/输出操作的程序:
import java.io.*; // required to use I/O streams
public class io1{
public static void main (String[] arg){
// write to System.out stream
Object obj=new Object();
System.out.println(""+obj);
System.out.println(obj.getClass().getName());
// write to System.err stream
int i=10;
System.err.println("i="+i);
// reading a line entered on the keyboard
String ligne;
BufferedReader IN=null;
try{
IN=new BufferedReader(new InputStreamReader(System.in));
} catch (Exception e){
affiche(e);
System.exit(1);
}
System.out.print("Tapez une ligne : ");
try{
ligne=IN.readLine();
System.out.println("ligne="+ligne);
} catch (Exception e){
affiche(e);
System.exit(2);
}
}//fine hand
public static void affiche(Exception e){
System.err.println("Erreur : "+e);
}
}//end of class
以及执行结果:
C:\Serge\java\bases\iostream>java io1
java.lang.Object@1ee78b
java.lang.Object
i=10
Tapez une ligne : je suis là
ligne=je suis là
操作说明
旨在展示任何对象均可被显示。此处我们不打算解释所显示内容的含义。该代码块中还包含对象值的显示:
try{
IN=new BufferedReader(new InputStreamReader(System.in));
} catch (Exception e){
affiche(e);
System.exit(1);
}
变量 e 是一个 Exception 对象,此处通过调用 display(e) 将其显示出来。我们在之前看到的转换程序中曾遇到过这种显示异常值的情况,不过当时并未对此进行讨论。
2.3.4. 将表达式的值赋给变量
这里我们关注的是操作 variable=expression;
表达式可以是以下类型:算术、关系、布尔、字符串
2.3.4.1. 赋值运算的解释
运算 variable=expression; 本身是一个表达式,其求值过程如下:
- 求值赋值语句的右侧:结果是一个值 V。
- 将值 V 赋给变量
- 值 V 也是赋值语句的值,此时该语句被视为一个表达式。
这就是为什么表达式 V1=V2=expression 是有效的。由于运算优先级,最右侧的 = 运算符将被求值。因此我们得到 V1=(V2=expression)。表达式 V2=expression 被求值,其值为 V。求值该表达式导致 V 被赋值给 V2。随后下一个 = 运算符被求值为 V1=V。该表达式的值仍是 V。其求值导致 V 被赋值给 V1。因此,运算 V1=V2=表达式是一个表达式,其求值
1:导致表达式的值被赋给变量 V1 和 V2
2: 返回表达式的值作为结果。
我们可以将其推广为以下形式的表达式: V1=V2=....=Vn=表达式
2.3.4.2. 算术表达式
算术表达式的运算符如下:
+:加法
-:减法
*:乘法
/:除法:若至少有一个操作数为实数,则结果为精确商。若两个操作数均为整数,则结果为整数商。因此,5/2 → 2,而 5.0/2 → 2.5。
%:除法:无论被除数性质如何,结果均为余数,且商为整数。因此,这即为取模运算。
存在多种数学函数:
double sqrt(double x) | 平方根 |
double cos(double x) | 余弦 |
double sin(double x) | 正弦 |
double tan(double x) | 正切 |
double pow(double x, double y) | x 的 y 次方 (x > 0) |
double exp(double x) | 指数 |
double log(double x) | 自然对数 |
double abs(double x) | 绝对值 |
等等...
所有这些函数都在一个名为 Math 的 Java 类中定义。使用它们时,必须在函数名前加上其所属类的名称。因此,应写为:
Math类的定义如下:
public final class java.lang.Math
extends java.lang.Object (I-§1.12)
{
// Fields
public final static double E; §1.10.1
public final static double PI; §1.10.2
// Methods
public static double abs(double a); §1.10.3
public static float abs(float a); §1.10.4
public static int abs(int a); §1.10.5
public static long abs(long a); §1.10.6
public static double acos(double a); §1.10.7
public static double asin(double a); §1.10.8
public static double atan(double a); §1.10.9
public static double atan2(double a, double b); §1.10.10
public static double ceil(double a); §1.10.11
public static double cos(double a); §1.10.12
public static double exp(double a); §1.10.13
public static double floor(double a); §1.10.14
public static double §1.10.15
IEEEremainder(double f1, double f2);
public static double log(double a); §1.10.16
public static double max(double a, double b); §1.10.17
public static float max(float a, float b); §1.10.18
public static int max(int a, int b); §1.10.19
public static long max(long a, long b); §1.10.20
public static double min(double a, double b); §1.10.21
public static float min(float a, float b); §1.10.22
public static int min(int a, int b); §1.10.23
public static long min(long a, long b); §1.10.24
public static double pow(double a, double b); §1.10.25
public static double random(); §1.10.26
public static double rint(double a); §1.10.27
public static long round(double a); §1.10.28
public static int round(float a); §1.10.29
public static double sin(double a); §1.10.30
public static double sqrt(double a); §1.10.31
public static double tan(double a); §1.10.32
}
2.3.4.3. 算术表达式求值中的运算符
在求值算术表达式时,运算符的优先级如下(从高到低):
[函数], [ ( )], [ *, /, %], [+, -]
同一方括号 [ ] 内的运算符具有相同的优先级。
2.3.4.4. 关系运算符
这些运算符如下: <, <=, ==, !=, >, >=
运算优先级
>, >=, <, <=
==, !=
如果关系表达式为假,则其结果为布尔值 false;否则,结果为 true。
示例:
两个字符的比较
假设有两个字符 C1 和 C2。可以使用以下运算符对它们进行比较
<, <=, ==, !=, >, >=
进行比较。此时,比较的是它们的 ASCII 码(即数字)。请注意,根据 ASCII 排序规则,以下关系成立:
空格 < .. < '0' < '1' < .. < '9' < .. < 'A' < 'B' < .. < 'Z' < .. < 'a' < 'b' < .. < 'z'
两个字符串的比较
它们按字符逐个比较。两个字符之间遇到的第一个不等关系,将导致两个字符串之间出现相同方向的不等关系。
示例:
考虑比较字符串“Cat”和“Dog”
根据上述不等式,我们可以得出结论:“Cat” < “Dog”。

考虑比较字符串“Cat”和“Kitten”。它们在整个过程中都相等,直到字符串“Cat”被耗尽。在这种情况下,被耗尽的字符串被判定为“较小”的那个。因此,我们有如下关系
“Cat” < “Kitten”。
用于比较两个字符串的函数
在此处不能使用关系运算符 <、<=、==、!=、>、>=。必须使用 String 类中的方法:
String chaine1, chaine2;
chaine1=…;
chaine2=…;
int i=chaine1.compareTo(chaine2);
boolean egal=chaine1.equals(chaine2)
在上述代码中,变量 i 的值将为:
0:如果两个字符串相等
1:如果字符串 1 > 字符串 2
-1:如果字符串 1 小于字符串 2
如果两个字符串相等,变量 equal 的值将为 true。
2.3.4.5. 布尔表达式
运算符包括 &(与)、||(或)和 !(非)。布尔表达式的结果是一个布尔值。
运算优先级 ! , &&, ||
示例:
关系运算符的优先级高于 && 和 || 运算符。
2.3.4.6. 位运算
运算符
设 i 和 j 是两个整数。
i<<n | 将 i 向左移 n 位。移入的位均为零。 |
i>>n | 将 i 向右移位 n 位。如果 i 是带符号整数(signed char、int、long),则保留符号位。 |
i & j | 对 i 和 j 进行逐位逻辑与运算。 |
i | j | 对 i 和 j 进行逐位逻辑或运算。 |
~i | 将 i 补为 1 |
i^j | 对 i 和 j 进行异或运算 |
令
操作 | 值 |
i<<4 | 0x23F0 |
i>>4 | 0x0123 符号位被保留。 |
k>>4 | 0xFF12 符号位被保留。 |
i&j | 0x1023 |
i|j | 0xF33F |
~i | 0xEDC0 |
2.3.4.7. 运算符组合
a=a+b 可以写成 a+=b
a=a-b 可以写成 a-=b
运算符 /, %, *, <<, >>, &, |, ^ 也适用此规则
因此,a=a+2; 可以写成 a+=2;
2.3.4.8. 递增和递减运算符
符号 variable++ 表示 variable=variable+1 或 variable+=1
符号 variable-- 表示 variable=variable-1 或 variable-=1。
2.3.4.9. ? 运算符
表达式 expr_cond ? expr1:expr2 的求值方式如下:
1: 先求值表达式 expr_cond。这是一个条件表达式,其值为 true 或 false
2: 若为真,则该表达式的值为 expr1 的值。expr2 不被求值。
3: 若为假,则情况相反:该表达式的值为 expr2 的值。expr1 不被求值。
示例
*i = (j > 4 ? j + 1 : j - 1);*
将向变量 i 赋值:
当 j > 4 时赋值为 j+1,否则赋值为 j-1
这相当于写 if(j>4) i=j+1; else i=j-1; 但更加简洁。
2.3.4.10. 运算符优先级概览
() [] 函数 | gd |
! ~ ++ -- | dg |
新的 (类型) 强制转换运算符 | dg |
* / % | gd |
+ - | gd |
<< >> | gd |
< <= > >= instanceof | gd |
== != | gd |
& | gd |
^ | gd |
| | gd |
&& | gd |
|| | gd |
? : | dg |
= += -= 等。 | dg |
gd:表示对于优先级相同的运算符,遵循从左到右的优先级规则。这意味着当表达式包含优先级相同的运算符时,表达式中最左侧的运算符将首先被求值。dg表示从右到左的优先级。
2.3.4.11. 类型转换
在表达式中,可以临时改变一个值的表示形式。这被称为类型转换。在表达式中改变值类型的语法是 (类型) 值。此时,该值将采用指定的类型,从而导致其表示形式发生改变。
示例:
在此,必须将 i 或 j 强制转换为浮点类型;否则,除法运算将返回整数商而非浮点数值。
*i* 是一个精确编码为 2 字节的值
*(float) i* 是相同的值,近似地以 4 字节的浮点数形式编码
因此,i 的值会发生类型转换。这种转换仅在计算期间发生;变量 i 始终保留其 int 类型。
2.4. 程序流程控制语句
2.4.1. 停止
System 类中定义的 exit 方法允许您停止程序的执行。
作用:停止当前进程,并将状态值返回给父进程
exit 会终止当前进程并将控制权交还给调用进程。调用进程可以使用该状态值。在 DOS 环境下,该状态值将通过系统变量 ERRORLEVEL 返回给 DOS,其值可在批处理文件中进行检查。在 Unix 环境下,如果命令解释器是 Bourne Shell(/bin/sh),则可通过 $? 变量获取该状态值。
示例:
以状态值 0 终止程序。
2.4.2. 简单的条件结构
syntaxe : if (condition) {actions_condition_vraie;} else {actions_condition_fausse;}
注:
- 条件用圆括号括起来。
- 每个操作以分号结尾。
- 大括号后不跟分号。
- 只有当存在多个操作时才需要使用大括号。
- else 子句可以省略。
- 没有“then”。
该结构在算法上的等价形式是 if-then-else 结构:
示例
if (x>0) { nx=nx+1;sx=sx+x;} else dx=dx-x;
你可以嵌套决策结构:
有时会出现以下问题:
public static void main(void){
int n=5;
if(n>1)
if(n>6)
System.out.println(">6");
else System.out.println("<=6");
}
在上一个示例中,else 指的是哪个 if 语句?规则是 else 总是指最近的 *if 语句:在本例中即 if(n>6)*。让我们再看一个例子:
public static void main(void)
{ int n=0;
if(n>1)
if(n>6) System.out.println(">6");
else; // else from if(n>6): nothing to do
else System.out.println("<=1"); // else du if(n>1)
}
这里我们本想在 if(n>1) 语句中添加一个 else,而在 if(n>6) 语句中不添加 else。由于前面的说明,我们被迫在 if(n>6) 语句中添加一个 else,而该语句中并没有任何语句。
2.4.3. 案例结构
语法如下:
switch(expression) {
case v1:
actions1;
break;
case v2:
actions2;
break;
. .. .. .. .. ..
default: actions_sinon;
}
注释
- 控制表达式的值只能是整数或字符。
- 控制表达式应置于圆括号内。
- 默认子句可以省略。
- 值 vi 是该表达式的可能值。如果表达式计算结果为 vi,则执行 case vi 子句后面的操作。
- break 语句将退出 case 结构。如果它在值 vi 的指令块末尾缺失,则执行将从值 vi+1 的指令继续。
示例
在算法中
selon la valeur de choix
cas 0
arrêt
cas 1
exécuter module M1
cas 2
exécuter module M2
sinon
erreur<--vrai
findescas
在 Java 中
int choix, erreur;
switch(choix){
case 0: System.exit(0);
case 1: M1();break;
case 2: M2();break;
default: erreur=1;
}
2.4.4. 循环结构
2.4.4.1. 已知的重复次数
语法
for (i=id;i<=if;i=i+ip){
actions;
}
注释
- for 循环的三个参数用圆括号括起来。
- for 循环的三个参数之间用分号分隔。
- for 循环中的每条语句末尾都以分号结尾。
- 只有当存在多个操作时,才需要使用大括号。
- 大括号后面不跟分号。
其算法等价形式是 for 结构:
这可以转换为一个 while 结构:
2.4.4.2. 重复次数未知
Java 中针对这种情况提供了许多控制结构。
while 循环
while(condition){
actions;
}
只要条件为真,循环就会继续执行。该循环可能永远不会被执行。
注:
- 条件用圆括号括起来。
- 每个操作以分号结尾。
- 只有当存在多个操作时才需要使用大括号。
- 大括号后不跟分号。
相应的算法结构是 while 结构:
Do-while 结构
语法如下:
do{
instructions;
}while(condition);
循环将持续执行,直到条件变为假,或者只要条件为真。在此情况下,循环至少会被执行一次。
注释
- 该条件用圆括号括起来。
- 每个操作以分号结尾。
- 只有当存在多个操作时,才需要使用大括号。
- 大括号后面不跟分号。
相应的算法结构是“重复……直到”结构:
通用(for)结构
语法如下:
for(instructions_départ;condition;instructions_fin_boucle){
instructions;
}
只要条件为真(在每次迭代前进行评估),循环就会继续执行。首次进入循环前会执行起始语句,每次迭代结束后会执行结束语句。
注释
- for 循环的三个参数用圆括号括起来。
- for 循环的三个参数用分号分隔。
- 每个 for 语句末尾都以分号结尾。
- 只有当有多个操作时,才需要大括号。
- 大括号后面不跟分号。
- start_statements 和 end_loop_statements 中的各个语句用逗号分隔。
相应的算法结构如下:
示例
以下程序均计算前 n 个整数的和。
1 for(i=1, somme=0;i<=n;i=i+1)
somme=somme+a[i];
2 for (i=1, somme=0;i<=n;somme=somme+a[i], i=i+1);
3 i=1;somme=0;
while(i<=n)
{ somme+=i; i++; }
4 i=1; somme=0;
do somme+=i++;
while (i<=n);
循环控制语句
break | 退出 for、while 或 do...while 循环。 |
continue | 跳转到 for、while 和 do...while 循环的下一轮迭代 |
2.5. Java 程序的结构
一个不使用用户定义类或除 main 函数以外的其他函数的 Java 程序可能具有以下结构:
public class test1{
public static void main(String arg[]){
… code du programme
}// hand
}// class
main 函数(也称为方法)是运行 Java 程序时首先被执行的函数。它必须具有以下签名:
public static void main(String arg[]){
或
public static void main(String[] arg){
arg 参数的名称可以是任意名称。它是一个字符串数组,表示命令行参数。我们稍后会再回到这一点。
如果你使用的函数可能会抛出你不希望显式处理的异常,可以将程序代码包裹在 try/catch 代码块中:
public class test1{
public static void main(String arg[]){
try{
… code du programme
} catch (Exception e){
// error handling
}// try
}// hand
}// class
在源代码的开头以及类定义之前,通常会看到类导入语句。例如:
import java.io.*;
public class test1{
public static void main(String arg[]){
… code du programme
}// hand
}// class
让我们举个例子。考虑以下写入语句:
这会将“java”打印到屏幕上。在这条简单的语句中,其实蕴含着许多内容:
- System 是一个类,其全名为 java.lang.System
- out 是该类的属性,类型为 java.io.PrintStream,这是一个子类
- println 是 java.io.PrintStream 类的成员方法。
我们不会不必要地让这个解释变得复杂,因为现在讨论它为时尚早——这需要理解“类”的概念,而该概念尚未涉及。可以将类视为一种资源。在此,编译器需要访问 java.lang.System 和 java.io.PrintStream 这两个类。 数百个 Java 类被组织成归档,也称为包。程序开头放置的 import 语句用于告知编译器程序需要哪些外部类(即将在待编译的源文件中使用但未在其中定义的类)。因此,在我们的示例中,程序需要 java.lang.System 和 java.io.PrintStream 类。我们通过 import 语句来指定这一点。我们可以在程序开头写:
由于 Java 程序通常会使用数十个外部类,如果逐一编写所有必要的 import 语句会非常繁琐。类已被分组到包中,我们可以导入整个包。因此,要导入 java.lang 和 java.io 包,我们写:
java.lang 包包含所有 Java 基类,并会被编译器自动导入。因此,最终我们只需编写:
2.6. 异常处理
许多 Java 函数都可能引发异常,即错误。我们已经遇到过这样的函数,即 readLine 函数:
String ligne=null;
try{
ligne=IN.readLine();
System.out.println("ligne="+ligne);
} catch (Exception e){
affiche(e);
System.exit(2);
}// try
当函数可能抛出异常时,Java 编译器要求程序员进行异常处理,以构建更具容错性的程序:必须始终避免应用程序意外“崩溃”。在此,如果无内容可读取(例如输入流已被关闭),readLine 函数会抛出异常。异常处理遵循以下模式:
try{
appel de la fonction susceptible de générer l'exception
} catch (Exception e){
traiter l'exception e
}
instruction suivante
如果函数未抛出异常,程序将执行下一条语句;否则,程序将进入 catch 子句的主体,然后执行下一条语句。请注意以下几点:
- e 是从 Exception 类型派生的对象。我们可以使用 IOException、SecurityException、ArithmeticException 等类型来进一步指定:异常类型大约有二十种。通过编写 catch (Exception e),我们表明希望处理所有类型的异常。如果 try 代码块中的代码可能引发多种类型的异常,我们可能需要通过使用多个 catch 代码块来更具体地处理异常:
try{
appel de la fonction susceptible de générer l'exception
} catch (IOException e){
traiter l'exception e
}
} catch (ArrayIndexOutOfBoundsException e){
traiter l'exception e
}
} catch (RunTimeException e){
traiter l'exception e
}
instruction suivante
- 可以在 try/catch 代码块中添加 finally 子句:
try{
appel de la fonction susceptible de générer l'exception
} catch (Exception e){
traiter l'exception e
}
finally{
code exécuté après try ou catch
}
instruction suivante
在此,无论是否发生异常,finally 子句中的代码都会被执行。
- Exception 类有一个 getMessage() 方法,该方法会返回一条详细描述所发生错误的消息。因此,如果我们要显示这条消息,可以这样写:
catch (Exception ex){
System.err.println("L'erreur suivante s'est produite : "+ex.getMessage());
...
}//catch
- Exception 类有一个 toString() 方法,该方法返回一个字符串,其中包含异常类型和 Message 属性的值。因此,我们可以这样写:
catch (Exception ex){
System.err.println ("L'erreur suivante s'est produite : "+ex.toString());
...
}//catch
我们还可以这样写:
这里有一个字符串与异常的连接操作,编译器会将其自动转换为字符串与 Exception.toString() 的组合,以便将两个字符串连接起来。
以下示例展示了因使用不存在的数组元素而引发的异常:
// tables
// imports
import java.io.*;
public class tab1{
public static void main(String[] args){
// declaring & initializing an array
int[] tab=new int[] {0,1,2,3};
int i;
// table display with for
for (i=0; i<tab.length; i++)
System.out.println("tab[" + i + "]=" + tab[i]);
// generating an exception
try{
tab[100]=6;
}catch (Exception e){
System.err.println("L'erreur suivante s'est produite : " + e);
}//try-catch
}//hand
}//class
运行程序会产生以下结果:
tab[0]=0
tab[1]=1
tab[2]=2
tab[3]=3
L'erreur suivante s'est produite : java.lang.ArrayIndexOutOfBoundsException
以下是另一个示例,其中我们处理了将字符串赋值给数字时因字符串不表示数字而引发的异常:
// imports
import java.io.*;
public class console1{
public static void main(String[] args){
// creation of an input stream
BufferedReader IN=null;
try{
IN=new BufferedReader(new InputStreamReader(System.in));
}catch(Exception ex){}
// We ask for the name
System.out.print("Nom : ");
// reading response
String nom=null;
try{
nom=IN.readLine();
}catch(Exception ex){}
// age requested
int age=0;
boolean ageOK=false;
while ( ! ageOK){
// question
System.out.print("âge : ");
// read-verify answer
try{
age=Integer.parseInt(IN.readLine());
ageOK=true;
}catch(Exception ex) {
System.err.println("Age incorrect, recommencez...");
}//try-catch
}//while
// final display
System.out.println("Vous vous appelez " + nom + " et vous avez " + age + " ans");
}//Main
}//class
部分执行结果:
E:\data\serge\MSNET\c#\bases\1>console1
Nom : dupont
âge : xx
Age incorrect, recommencez...
âge : 12
Vous vous appelez dupont et vous avez 12 ans
2.7. 编译和运行 Java 程序
编译并运行以下程序:
// importing classes
import java.io.*;
// test class
public class coucou{
// hand function
public static void main(String args[]){
// screen display
System.out.println("coucou");
}//hand
}//class
包含上述 coucou 类的源文件必须命名为 coucou.java:
Java 程序的编译和运行是在 DOS 窗口中进行的。可执行文件 javac.exe(编译器)和 java.exe(解释器)位于 JDK 安装目录的 bin 目录中:
E:\data\serge\JAVA\classes\paquetages\personne>dir "e:\program files\jdk14\bin\java?.exe"
07/02/2002 12:52 24 649 java.exe
07/02/2002 12:52 28 766 javac.exe
javac.exe 编译器将分析 .java 源文件并生成编译后的 .class 文件。该文件无法被处理器直接执行,需要一个 Java 解释器(java.exe),即虚拟机(JVM,Java Virtual Machine)。虚拟机根据 .class 文件中的中间代码,生成针对运行机器上处理器特有的指令。 针对不同类型的操作系统(Windows、Unix、Mac OS 等)都有相应的 Java 虚拟机。一个 .class 文件可以由这些虚拟机中的任何一个执行,因此可以在任何操作系统上运行。这种跨系统移植性是 Java 的主要优势之一。
现在让我们编译前面的程序:
E:\data\serge\JAVA\ESSAIS\intro1>"e:\program files\jdk14\bin\javac" coucou.java
E:\data\serge\JAVA\ESSAIS\intro1>dir
10/06/2002 08:42 228 coucou.java
10/06/2002 08:48 403 coucou.class
让我们运行生成的 .class 文件:
请注意,在上面的执行命令中,我们没有为要执行的 coucou.class 文件指定 .class 后缀。这是默认包含的。如果 JDK 的 bin 目录在 DOS 系统的 PATH 环境变量中,我们无需提供 javac.exe 和 java.exe 可执行文件的完整路径。我们可以直接写
2.8. 主程序参数
主函数接受一个字符串数组作为参数:String[]。该数组包含用于启动应用程序的命令行参数。因此,如果我们使用以下命令启动程序 P:
且主函数声明如下:
那么我们将得到 arg[0]="arg0", arg[1]="arg1" … 以下是一个示例:
import java.io.*;
public class param1{
public static void main(String[] arg){
int i;
System.out.println("Nombre d'arguments="+arg.length);
for (i=0;i<arg.length;i++)
System.out.println("arg["+i+"]="+arg[i]);
}
}
结果如下:
2.9. 向函数传递参数
前面的示例仅展示了包含单个函数(即 main 函数)的 Java 程序。下面的示例演示了如何使用函数以及函数之间如何交换信息。函数参数总是按值传递:也就是说,实际参数的值会被复制到相应的形式参数中。
import java.io.*;
public class param2{
public static void main(String[] arg){
String S="papa";
changeString(S);
System.out.println("Paramètre effectif S="+S);
int age=20;
changeInt(age);
System.out.println("Paramètre effectif age="+age);
}
private static void changeString(String S){
S="maman";
System.out.println("Paramètre formel S="+S);
}
private static void changeInt(int a){
a=30;
System.out.println("Paramètre formel a="+a);
}
}
所得结果如下:
实际参数“dad”和20的值被复制到形式参数S和a中。随后这些参数被修改。实际参数保持不变。请注意此处的实际参数类型:
- S 是对象引用,即内存中某个对象的地址
- age 是一个整数
2.10. 一个税费计算的示例
我们将通过一个示例来结束本章,该示例在本文档中会多次出现。我们建议编写一个程序来计算纳税人的税款。我们考虑一种简化情况,即纳税人只需申报工资收入:
- 对于该雇员,若其未婚,则税级数 nbParts = nbEnfants / 2 + 1;若已婚,则 nbParts = nbEnfants / 2 + 2,其中 nbEnfants 表示子女数量。
- 若其子女数至少为三名,则可额外获得半份税额抵免。
- 我们计算应税收入 R = 0.72 × S,其中 S 为年薪
- 我们计算家庭系数 QF = R / nbParts
- 计算您的税额 I。请参阅下表:
12,620.0 | 0 | 0 |
13,190 | 0.05 | 631 |
15,640 | 0.1 | 1,290.5 |
24,740 | 0.15 | 2,072.5 |
31,810 | 0.2 | 3,309.5 |
39,970 | 0.25 | 4,900 |
48,360 | 0.3 | 6,898.5 |
55,790 | 0.35 | 9,316.5 |
92,970 | 0.4 | 12,106 |
127,860 | 0.45 | 16,754.5 |
151,250 | 0.50 | 23,147.5 |
172,040 | 0.55 | 30,710 |
195,000 | 0.60 | 39,312 |
0 | 0.65 | 49,062 |
每行有 3 个字段。要计算税款 I,我们寻找 QF <= 字段 1 的第一行。例如,如果 QF = 23,000,我们将找到该行
此时,Tax I 等于 0.15*R - 2072.5*nbParts。如果 QF 使得条件 QF<=field1 永远不成立,则使用最后一行中的系数。这里:
由此可得税额 I = 0.65*R - 49062*nbParts。
相应的 Java 程序如下:
import java.io.*;
public class impots{
// ------------ hand
public static void main(String arg[]){
// data
// tax bracket limits
double Limites[]={12620, 13190, 15640, 24740, 31810, 39970, 48360,55790, 92970, 127860, 151250, 172040, 195000, 0};
// coefficient applied to the number of shares
double Coeffn[]={0, 631, 1290.5, 2072.5, 3309.5, 4900, 6898.5, 9316.5,12106, 16754.5, 23147.5, 30710, 39312, 49062};
// the program
// keyboard input stream creation
BufferedReader IN=null;
try{
IN=new BufferedReader(new InputStreamReader(System.in));
}
catch(Exception e){
erreur("Création du flux d'entrée", e, 1);
}
// we recover marital status
boolean OK=false;
String reponse=null;
while(! OK){
try{
System.out.print("Etes-vous marié(e) (O/N) ? ");
reponse=IN.readLine();
reponse=reponse.trim().toLowerCase();
if (! reponse.equals("o") && !reponse.equals("n"))
System.out.println("Réponse incorrecte. Recommencez");
else OK=true;
} catch(Exception e){
erreur("Lecture état marital",e,2);
}
}
boolean Marie = reponse.equals("o");
// number of children
OK=false;
int NbEnfants=0;
while(! OK){
try{
System.out.print("Nombre d'enfants : ");
reponse=IN.readLine();
try{
NbEnfants=Integer.parseInt(reponse);
if(NbEnfants>=0) OK=true;
else System.err.println("Réponse incorrecte. Recommencez");
} catch(Exception e){
System.err.println("Réponse incorrecte. Recommencez");
}// try
} catch(Exception e){
erreur("Lecture état marital",e,2);
}// try
}// while
// salary
OK=false;
long Salaire=0;
while(! OK){
try{
System.out.print("Salaire annuel : ");
reponse=IN.readLine();
try{
Salaire=Long.parseLong(reponse);
if(Salaire>=0) OK=true;
else System.err.println("Réponse incorrecte. Recommencez");
} catch(Exception e){
System.err.println("Réponse incorrecte. Recommencez");
}// try
} catch(Exception e){
erreur("Lecture Salaire",e,4);
}// try
}// while
// calculating the number of shares
double NbParts;
if(Marie) NbParts=(double)NbEnfants/2+2;
else NbParts=(double)NbEnfants/2+1;
if (NbEnfants>=3) NbParts+=0.5;
// taxable income
double Revenu;
Revenu=0.72*Salaire;
// family quotient
double QF;
QF=Revenu/NbParts;
// search for tax bracket corresponding to QF
int i;
int NbTranches=Limites.length;
Limites[NbTranches-1]=QF;
i=0;
while(QF>Limites[i]) i++;
// tax
long impots=(long)(i*0.05*Revenu-Coeffn[i]*NbParts);
// the result is displayed
System.out.println("Impôt à payer : " + impots);
}// hand
// ------------ error
private static void erreur(String msg, Exception e, int exitCode){
System.err.println(msg+"("+e+")");
System.exit(exitCode);
}// error
}// class
所得结果如下:
C:\Serge\java\impots\1>java impots
Etes-vous marié(e) (O/N) ? o
Nombre d'enfants : 3
Salaire annuel : 200000
Impôt à payer : 16400
C:\Serge\java\impots\1>java impots
Etes-vous marié(e) (O/N) ? n
Nombre d'enfants : 2
Salaire annuel : 200000
Impôt à payer : 33388
C:\Serge\java\impots\1>java impots
Etes-vous marié(e) (O/N) ? w
Réponse incorrecte. Recommencez
Etes-vous marié(e) (O/N) ? q
Réponse incorrecte. Recommencez
Etes-vous marié(e) (O/N) ? o
Nombre d'enfants : q
Réponse incorrecte. Recommencez
Nombre d'enfants : 2
Salaire annuel : q
Réponse incorrecte. Recommencez
Salaire annuel : 1
Impôt à payer : 0