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] |
byte | 1字节 | [-27 , 27 -1] |
short | 2字节 | [-215, 215-1] |
float | 4字节 | [3.4 10-38, 3.4 10+38](绝对值) |
double | 8字节 | [1.7 10-308 , 1.7 10+308](绝对值) |
boolean | 1 位 | true, false |
String | 对象引用 | 字符串 |
Date | 对象引用 | 日期 |
Character | 对象编号 | 字符 |
Integer | 对象引用 | int |
Long | 对象引用 | long |
Byte | 对象引用 | 字节 |
Float | 对象引用 | float |
Double | 对象引用 | double |
Boolean | 对象引用 | 布尔型 |
2.2.2. 字面量表示法
entier | 145, -7, 0xFF (十六进制) |
双精度实数 | 134.789, -45E-18 (-45 × 10⁻¹⁸) |
浮点数 | 134.789F, -45E-18F (-45 × 10⁻¹⁸) |
caractère | 'A', 'b' |
字符串 | "今天" |
booléen | true, false |
date | new Date(13,10,1954) (日, 月, 年) |
2.2.3. 数据声明
2.2.3.1. 声明的作用
程序处理的数据由名称和类型来定义。这些数据存储在内存中。在程序编译时,编译器会根据程序员的声明,为每项数据分配一个由地址和大小定义的内存位置。
此外,这些声明还使编译器能够检测编程错误。例如,运算
x=x*2;
若 x 是一个字符串,则会被判定为错误。
2.2.3.2. 常量的声明
常量的声明语法如下:
final type 名称=值; //定义常量 名称=值
例:final float PI=3.141592F;
注
为什么要声明常量?
- 如果给常量起一个有意义的名称,程序会更易于阅读:
例如:final float taux_tva=0.186F;
- 如果“常量”需要更改,修改程序也会更加方便。因此,在前面的例子中,如果增值税税率变为33%,唯一需要做的修改就是修改定义其值的语句:
final float taux_tva=0.33F;
如果程序中明确使用了0.186,那么就需要修改大量指令。
2.2.3.3. 变量的声明
变量通过名称来标识,并关联一种数据类型。Java 变量的名称由 n 个字符组成,第一个字符必须是字母,其余字符可以是字母或数字。Java 区分大小写。因此,变量 FIN 和 fin 是不同的。
变量可以在声明时进行初始化。声明一个或多个变量的语法如下:
其中 identificateur_de_type 是一个预定义类型,或者是由程序员定义的对象类型。
2.2.4. 数字与字符串之间的转换
数字 -> 字符串 | "" + 数字 |
字符串 -> 整数 | Integer.parseInt(字符串) |
字符串 -> 长整型 | Long.parseLong(字符串) |
字符串 -> 双精度 | Double.valueOf(字符串).doubleValue() |
字符串 -> 浮点数 | Float.valueOf(字符串).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;
// 数字 --> 字符串
S=""+i;
affiche(S);
S=""+l;
affiche(S);
S=""+f;
affiche(S);
S=""+d;
affiche(S);
//布尔值 --> 字符串
final boolean b=false;
S=""+new Boolean(b);
affiche(S);
// 字符串 --> 整数
int i1;
i1=Integer.parseInt("10");
affiche(""+i1);
try{
i1=Integer.parseInt("10.67");
affiche(""+i1);
} catch (Exception e){
affiche("Erreur "+e);
}
// 字符串 --> long
long l1;
l1=Long.parseLong("100");
affiche(""+l1);
try{
l1=Long.parseLong("10.675");
affiche(""+l1);
} catch (Exception e){
affiche("Erreur "+e);
}
// 字符串 --> 双精度
double d1;
d1=Double.valueOf("100.87").doubleValue();
affiche(""+d1);
try{
d1=Double.valueOf("abcd").doubleValue();
affiche(""+d1);
} catch (Exception e){
affiche("Erreur "+e);
}
// 字符串 --> 浮点数
float f1;
f1=Float.valueOf("100.87").floatValue();
affiche(""+f1);
try{
d1=Float.valueOf("abcd").floatValue();
affiche(""+f1);
} catch (Exception e){
affiche("Erreur "+e);
}
}// 结束 main
public static void affiche(String S){
System.out.println("S="+S);
}
}// 类结束
所得结果如下:
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 数组[] = new Type[n] 或 Type[] 数组 = new Type[n]
这两种语法都是合法的。n 表示数组可容纳的数据数量。语法 Tableau[i] 表示编号为 i 的数据,其中 i 属于区间 [0,n-1]。 任何对数据 Tableau[i] 的引用,若 i 不属于区间 [0,n-1],都将引发异常。
一个二维数组可以如下声明:
Type Tableau[][] = new Type[n][p] 或 Type[][] Tableau = new Type[n][p]
语法 Tableau[i] 表示 Tableau 中编号为 i 的数据,其中 i 属于区间 [0,n-1]。 Tableau[i] 本身是一个数组:Tableau[i][j] 表示数据编号 j,其中 j 属于区间 [i]。任何使用错误索引引用 Tableau 中数据的行为都会引发致命错误。
以下是一个示例:
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(表达式)
其中 expression 代表任何可转换为字符串并在屏幕上显示的数据类型。在前面的示例中,我们看到了两条写入语句:
System.out 写入文本文件,默认输出到屏幕。System.err 也是如此。 这些文件分别带有编号 1 和 2(如 descripteur)。来自键盘的输入流(System.in)也被视为一个文本文件,其描述符为 0。DOS 和 Unix 都支持命令管道:
commande1 通过 System.out 写入的所有内容都会被重定向(管线传输)到 commande2 的 System.in 输入端。 换句话说,commande2 通过 System.in 读取由 commande2 与 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.*; // 使用I/O流所必需
public class io1{
public static void main (String[] arg){
// 写入流 System.out
Object obj=new Object();
System.out.println(""+obj);
System.out.println(obj.getClass().getName());
// 写入流 System.err
int i=10;
System.err.println("i="+i);
// 读取键盘输入的一行
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);
}
}//结束
public static void affiche(Exception e){
System.err.println("Erreur : "+e);
}
}//类结束
以及执行结果:
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 的对象,此处通过调用 affiche(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:将 expression 的值赋给变量 V1 和 V2
2:返回 expression 的值。
我们可以将其推广为如下形式的表达式: 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)
{
// 字段
public final static double E; §1.10.1
public final static double PI; §1.10.2
// 方法
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. 算术表达式求值中的运算优先级
在计算算术表达式时,运算符的优先级如下(从最高优先级到最低优先级):
[fonctions]、[ ( )]、[ *, /, %]、[+, -]
同一运算块内的运算符具有相同的优先级。
2.3.4.4. 关系运算符
运算符包括:<, <=, ==, !=, >, >=
优先级
>, >=, <, <=
==, !=
关系表达式的结果为布尔值 false(若表达式为假),否则为 true。
示例:
两个字符的比较
假设有两个字符 C1 和 C2。可以使用以下运算符对它们进行比较
<、<=、==、!=、>、>=
此时,实际比较的是它们的代码 ASCII(即数字)。需要提醒的是,根据 ASCII 的排序规则,存在以下关系:
空格 < .. < '0' < '1' < .. < '9' < .. < 'A' < 'B' < .. < 'Z' < .. < 'a' < 'b' < .. < 'z'
两个字符串的比较
字符串按字符逐个比较。两个字符之间首次出现的不相等关系,将导致字符串之间产生相同方向的不相等关系。
示例:
假设要比较字符串“Chat”和“Chien”
最后一次不相等表明 "Chat" < "Chien"。

假设要比较字符串“Chat”和“Chaton”。在“Chat”字符串耗尽之前,两者始终相等。在此情况下,耗尽的字符串被判定为“较小”的一方。因此,我们得到关系
“猫” < “小猫”。
两个字符串的比较函数
此处不能使用关系运算符 <、<=、==、!=、>、>=。必须使用 String 类的方法:
String chaine1, chaine2;
chaine1=…;
chaine2=…;
int i=chaine1.compareTo(chaine2);
boolean egal=chaine1.equals(chaine2)
在上述情况下,变量 i 的取值为:
0:如果两个字符串相等
1:若字符串1 > 字符串2
-1:若字符串1 < 字符串2
如果两个字符串相等,变量 egal 的值将为 true。
2.3.4.5. 布尔表达式
布尔运算符 包括 &(and)和 ||(or 以及 !(not)。布尔表达式的结果是一个布尔值。
优先级 ! , &&, ||
示例:
关系运算符的优先级高于 && 和 || 运算符。
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 进行 OU EXCLUSIF 运算 |
即
运算 | 值 |
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。这是一个取值为真或假的条件表达式
2:若为真,则该表达式的值为 expr1。expr2 不被求值。
3:如果为假,则情况相反:该表达式的值为 expr2。expr1 不会被求值。
示例
i=(j>4 ? j+1:j-1);
将使变量 i 赋值为:
j+1(若 j>4 为真),否则为 j-1
这与编写 if(j>4) i=j+1; else i=j-1; 效果相同,但更为简洁。
2.3.4.10. 运算符的一般优先级
() [] 函数 | gd |
! ~ ++ -- | dg |
new (类型) 类型转换运算符 | dg |
* / % | gd |
+ - | gd |
<< >> | gd |
< <= > >= instanceof | gd |
== != | gd |
& | gd |
^ | gd |
| | gd |
&& | gd |
|| | gd |
? : | dg |
= += -= 等。 | dg |
gd:表示在优先级相同时,遵循从左到右的优先级规则。这意味着当表达式中存在优先级相同的运算符时,表达式中最左侧的运算符将首先被求值。gd表示从右到左的优先级。
2.3.4.11. 类型转换
在表达式中,可以暂时改变一个值的编码。这被称为改变数据的类型,英文称为类型转换(type casting)。表达式中改变值类型的语法如下:(类型) 值。此时,该值将采用指定的类型。这会导致值的编码发生改变。
示例:
在此,必须将 i 或 j 的类型转换为实数,否则除法运算将返回整数商而非实数。
i 是 2 字节精确编码的值
(float) i 是该值以 4 字节浮点数形式近似编码后的结果
因此,i 的值发生了转码。这种转码仅在计算过程中发生,变量 i 始终保持其 int 类型。
2.4. 程序执行监控说明
2.4.1. 停止
在类 System 中定义的方法 exit 可用于停止程序的执行。
作用:终止当前进程,并将 status 值返回给父进程
exit 会导致当前进程终止,并将控制权交还给调用进程。调用进程可使用 status 的值。 在 DOS 系统下,该变量 status 会被返回至 DOS,并存储在系统变量 ERRORLEVEL 中,其值可在批处理文件中进行检测。 在 Unix 系统中,如果命令解释器是 Bourne Shell(/bin/sh),则变量 $? 将获取 status 的值。
示例:
用于以状态值 0 终止程序。
2.4.2. 简单选择结构
syntaxe : if (condition) {actions_condition_vraie;} else {actions_condition_fausse;}
注:
- 条件用圆括号括起。
- 每个操作以分号结尾。
- 大括号内不以分号结尾。
- 只有当存在多个操作时才需要大括号。
- else子句可以省略。
- 没有 then 子句。
该结构的算法等价形式是“如果……那么……否则”结构:
示例
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; // if(n>6) 的 else 部分:无需执行
else System.out.println("<=1"); // if(n>1) 的 else 部分
}
这里我们希望将 else 映射到 if(n>1),而不是将 else 映射到 if(n>6)。 由于前面的说明,我们不得不为 if(n>6) 添加一个 else 语句,其中没有任何指令。
2.4.3. case 语句结构
语法如下:
switch(expression) {
case v1:
actions1;
break;
case v2:
actions2;
break;
. .. .. .. .. ..
default: actions_sinon;
}
注释
- 控制表达式的值只能是整数或字符。
- 控制表达式必须用圆括号括起来。
- default子句可以省略。
- 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 的 3 个参数位于圆括号内。
- for 的 3 个参数由分号分隔。
- for 的每个操作都以分号结尾。
- 只有当存在多个操作时才需要大括号。
- 大括号后不跟分号。
其算法等价形式为结构 pour:
这可以转换为结构 tantque:
2.4.4.2. 重复次数未知
Java 中有许多适用于这种情况的结构。
while 循环
while(condition){
actions;
}
只要条件成立,循环就会继续。该循环可能永远不会被执行。
注:
- 条件用圆括号括起来。
- 每个操作以分号结尾。
- 只有当存在多个操作时才需要大括号。
- 大括号后不加分号。
相应的算法结构是“直到”结构:
重复直到(do while)结构
语法如下:
do{
instructions;
}while(condition);
循环将持续进行,直到条件变为假,或者只要条件为真。在此情况下,循环至少执行一次。
注
- 条件用圆括号括起来。
- 每个操作以分号结尾。
- 只有当存在多个操作时才需要大括号。
- 大括号后不跟分号。
相应的算法结构是“重复……直到”结构:
for 循环结构
语法如下:
for(instructions_départ;condition;instructions_fin_boucle){
instructions;
}
只要条件为真(在每次循环迭代前进行评估),循环就会持续进行。Instructions_départ 在首次进入循环前执行。Instructions_fin_boucle 在每次循环迭代后执行。
注
- for 的 3 个参数位于圆括号内。
- for 的 3 个参数用分号分隔。
- for 循环中的每个操作都以分号结尾。
- 只有当存在多个操作时才需要大括号。
- 大括号后不跟分号。
- instructions_depart 和 instructions_fin_boucle 中的不同指令用逗号分隔。
相应的算法结构如下:
示例
以下程序均计算前 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
}// main
}// 类
函数 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){
// 错误处理
}// try
}// main
}// 类
在源代码开头以及类定义之前,通常会看到类导入语句。例如:
import java.io.*;
public class test1{
public static void main(String arg[]){
… code du programme
}// main
}// 类
我们来看一个例子。假设以下是一条写入指令:
该指令会在屏幕上输出 java。在这条简单的指令中包含了许多内容:
- System 是一个类,其全名为 java.lang.System
- out 是该类的属性,其类型为 java.io.PrintStream(另一个类)
- println 是类 java.io.PrintStream 的一个方法。
我们不会无谓地让这个解释变得复杂,因为它出现得太早,需要理解尚未涉及的“类”这一概念。可以将类视为一种资源。在此,编译器需要访问 java.lang.System 和 java.io.PrintStream 这两个类。 Java中的数百个类被分发在称为“包”(package)的归档中。程序开头处的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 运算,编译器会将其自动转换为字符串 + Exception.toString(),以实现两个字符串的连接。
以下示例展示了因使用不存在的数组元素而引发的异常:
// 画作
// 导入
import java.io.*;
public class tab1{
public static void main(String[] args){
// 数组的声明与初始化
int[] tab=new int[] {0,1,2,3};
int i;
// 使用 for 循环显示数组
for (i=0; i<tab.length; i++)
System.out.println("tab[" + i + "]=" + tab[i]);
// 异常生成
try{
tab[100]=6;
}catch (Exception e){
System.err.println("L'erreur suivante s'est produite : " + e);
}//try-catch
}//main
}//类
运行该程序得到以下结果:
tab[0]=0
tab[1]=1
tab[2]=2
tab[3]=3
L'erreur suivante s'est produite : java.lang.ArrayIndexOutOfBoundsException
以下是另一个示例,演示了当字符串不代表数字时,将字符串赋值给数字所引发的异常的处理方法:
// 导入
import java.io.*;
public class console1{
public static void main(String[] args){
// 创建输入流
BufferedReader IN=null;
try{
IN=new BufferedReader(new InputStreamReader(System.in));
}catch(Exception ex){}
// 请求名称
System.out.print("Nom : ");
// 读取回答
String nom=null;
try{
nom=IN.readLine();
}catch(Exception ex){}
// 询问年龄
int age=0;
boolean ageOK=false;
while ( ! ageOK){
// 提问
System.out.print("âge : ");
// 读取并验证回答
try{
age=Integer.parseInt(IN.readLine());
ageOK=true;
}catch(Exception ex) {
System.err.println("Age incorrect, recommencez...");
}//try-catch
}//while
// 最终显示
System.out.println("Vous vous appelez " + nom + " et vous avez " + age + " ans");
}//Main
}//类
部分执行结果:
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程序的编译与运行
请编译并运行以下程序:
// 导入类
import java.io.*;
// 测试类
public class coucou{
// main 函数
public static void main(String args[]){
// 屏幕显示
System.out.println("coucou");
}//main
}//类
包含上述类 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. 主程序的参数
主函数 main 接受一个字符串数组作为参数:String[]。该数组包含用于启动应用程序的命令行参数。因此,如果使用以下命令启动程序 P:
且 main 函数声明如下:
则将得到 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);
}
}
所得结果如下:
实际参数“papa”和20的值已被复制到形式参数S和a中。随后这些形式参数被修改,而实际参数则保持不变。此处需特别注意实际参数的类型:
- S 是对象 c.a.d 的引用,即内存中对象的地址
- age 是一个整数值
2.10. 税费示例
本章将以一个示例作为结尾,该示例将在本文档中多次出现。我们的目标是编写一个程序,用于计算纳税人的税款。我们假设一种简化情况:纳税人仅需申报工资收入:
- 若未婚,则计算该雇员的税额份额 nbParts=nbEnfants/2 +1; 已婚者为 nbEnfants/2+2,其中 nbEnfants 表示其子女数。
- 如果他至少有三个孩子,则额外增加半份
- 计算其应税收入 R=0.72*S,其中 S 为其年薪
- 计算其家庭系数 QF=R/nbParts
- 计算其应纳税额 I。请看下表:
12620.0 | 0 | 0 |
13190 | 0.05 | 631 |
15640 | 0.1 | 1290.5 |
24740 | 0.15 | 2072.5 |
31810 | 0.2 | 3309.5 |
39970 | 0.25 | 4900 |
48360 | 0.3 | 6898.5 |
55790 | 0.35 | 9316.5 |
92970 | 0.4 | 12106 |
127860 | 0.45 | 16754.5 |
151250 | 0.50 | 23147.5 |
172040 | 0.55 | 30710 |
195000 | 0.60 | 39312 |
0 | 0.65 | 49062 |
每行有 3 个字段。要计算税款 I,需查找满足 QF<=字段1 的第一行。例如,若 QF=23000,则会找到该行
此时税额 I 等于 0.15*R - 2072.5*nbParts。 如果 QF 使得关系 QF<=field1 从未成立,则使用最后一行中的系数。此处:
由此得出的税额 I=0.65*R - 49062*nbParts。
相应的 Java 程序如下:
import java.io.*;
public class impots{
// ------------ main
public static void main(String arg[]){
// 数据
// 税率区间限额
double Limites[]={12620, 13190, 15640, 24740, 31810, 39970, 48360,55790, 92970, 127860, 151250, 172040, 195000, 0};
// 份额数量的适用系数
double Coeffn[]={0, 631, 1290.5, 2072.5, 3309.5, 4900, 6898.5, 9316.5,12106, 16754.5, 23147.5, 30710, 39312, 49062};
// 程序
// 创建键盘输入流
BufferedReader IN=null;
try{
IN=new BufferedReader(new InputStreamReader(System.in));
}
catch(Exception e){
erreur("Création du flux d'entrée", e, 1);
}
// 获取婚姻状况
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");
// 子女数量
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
// 工资
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
// 份额计算
double NbParts;
if(Marie) NbParts=(double)NbEnfants/2+2;
else NbParts=(double)NbEnfants/2+1;
if (NbEnfants>=3) NbParts+=0.5;
// 应税收入
double Revenu;
Revenu=0.72*Salaire;
// 家庭分摊系数
double QF;
QF=Revenu/NbParts;
// 查找与 QF 对应的税率档位
int i;
int NbTranches=Limites.length;
Limites[NbTranches-1]=QF;
i=0;
while(QF>Limites[i]) i++;
// 税额
long impots=(long)(i*0.05*Revenu-Coeffn[i]*NbParts);
// 显示结果
System.out.println("Impôt à payer : " + impots);
}// 主程序
// ------------ 错误
private static void erreur(String msg, Exception e, int exitCode){
System.err.println(msg+"("+e+")");
System.exit(exitCode);
}// 错误
}// 类
所得结果如下:
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