Skip to content

2. Java语言基础

2.1. 引言

我们首先将Java视为一种经典的编程语言。关于对象的内容将在后续章节中介绍。

在程序中,我们通常会遇到两类内容

  • 数据
  • 操作数据的指令

通常,我们力求将数据与指令分离:

Image

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 区分大小写。因此,变量 FINfin 是不同的。

变量可以在声明时进行初始化。声明一个或多个变量的语法如下:

identificateur_de_type variable1,variable2,...,variablen;

其中 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]);
  }
}

以及执行结果:

2
0.33

数组是一个具有属性 length 的对象:该属性表示数组的大小。

2.3. Java的基本语句

需要区分

  • 由计算机执行的基本指令。
  • 控制程序运行的指令。

当考察微计算机及其外围设备的结构时,基本指令便显而易见。

Image

  1. 读取来自键盘的信息

  2. 信息处理

  3. 将信息写入屏幕

  4. 从磁盘文件读取信息

  5. 将信息写入磁盘文件

2.3.1. 屏幕输出

屏幕写入语句的语法如下:

System.out.println(表达式) System.err.println(表达式)

其中 expression 代表任何可转换为字符串并在屏幕上显示的数据类型。在前面的示例中,我们看到了两条写入语句:

System.out.println(taux[1].length);
System.out.println(taux[1][1]);

System.out 写入文本文件,默认输出到屏幕。System.err 也是如此。 这些文件分别带有编号 1 和 2(如 descripteur)。来自键盘的输入流(System.in)也被视为一个文本文件,其描述符为 0。DOS 和 Unix 都支持命令管道:

    commande1 | commande2

commande1 通过 System.out 写入的所有内容都会被重定向(管线传输)到 commande2System.in 输入端。 换句话说,commande2 通过 System.in 读取由 commande2System.out 生成的数据,因此这些数据不再显示在屏幕上。该系统在 Unix 系统中被广泛使用。 在此管道中,System.err 数据流并未被重定向:它直接输出到屏幕上。正因如此,它被用于输出错误信息(故其名称为 err): 这样可以确保在命令管道化时,错误消息仍会继续显示在屏幕上。因此,我们通常会使用数据流 System.err 而不是 System.out 来将错误消息输出到屏幕上。

2.3.2. 读取键盘输入的数据

来自键盘的数据流由类型为 InputStream 的对象 System.in 表示。此类对象支持逐字符读取数据。程序员需自行从该字符流中提取所需信息。 InputStream 类型不支持一次性读取整行文本。而 BufferedReader 类型通过 readLine 方法支持此功能。

为了能够读取键盘输入的文本行,我们基于类型为 InputStream 的输入流 System.in,创建另一个类型为 BufferedReader 的输入流:

BufferedReader IN=new BufferedReader(new InputStreamReader(System.in));

此处不再详述该语句的细节,因其涉及对象构造的概念。我们将直接使用该语句。

流的构建可能会失败:此时会产生一个致命错误,在 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 数据流后,可以通过以下语句读取一行文本:

    String ligne;
    ligne=IN.readLine();

通过键盘输入的行会被存储在变量 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à

说明

Object obj=new Object();
System.out.println(""+obj);
System.out.println(obj.getClass().getName());

旨在说明任何对象都可以被显示出来。这里我们不会解释所显示内容的含义。在代码块中,我们还显示了对象的值:

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 类中定义。使用时,必须在函数名前加上其所属类的名称。因此,我们应这样写:

double x, y=4;
x=Math.sqrt(y);

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

示例:

      boolean fin;
      int x;
      fin=x>4;

两个字符的比较

假设有两个字符 C1 和 C2。可以使用以下运算符对它们进行比较

<、<=、==、!=、>、>=

此时,实际比较的是它们的代码 ASCII(即数字)。需要提醒的是,根据 ASCII 的排序规则,存在以下关系:

空格 < .. < '0' < '1' < .. < '9' < .. < 'A' < 'B' < .. < 'Z' < .. < 'a' < 'b' < .. < 'z'

两个字符串的比较

字符串按字符逐个比较。两个字符之间首次出现的不相等关系,将导致字符串之间产生相同方向的不相等关系。

示例

假设要比较字符串“Chat”和“Chien”

最后一次不相等表明 "Chat" < "Chien"。

Image

假设要比较字符串“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)。布尔表达式的结果是一个布尔值。

优先级 ! , &&, ||

示例:

int fin;
int x;
fin= x>2 && x<4;

关系运算符的优先级高于 && 和 || 运算符。

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 运算

    int i=0x123F, k=0xF123;
    unsigned j=0xF123;
运算
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+1variable+=1

表示法 variable-- 表示 variable=variable-1variable-=1

2.3.4.9. 运算符 ?

表达式 expr_cond ? expr1:expr2 的求值方式如下:

1:先求值表达式 expr_cond。这是一个取值为真或假的条件表达式

2:若为真,则该表达式的值为 expr1expr2 不被求值。

3:如果为假,则情况相反:该表达式的值为 expr2expr1 不会被求值。

示例

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)。表达式中改变值类型的语法如下:(类型) 值。此时,该值将采用指定的类型。这会导致值的编码发生改变。

示例:

int i, j;
float isurj;
isurj= (float)i/j;   // () 的优先级高于 /

在此,必须将 ij 的类型转换为实数,否则除法运算将返回整数商而非实数。

i 是 2 字节精确编码的值

(float) i 是该值以 4 字节浮点数形式近似编码后的结果

因此,i 的值发生了转码。这种转码仅在计算过程中发生,变量 i 始终保持其 int 类型。

2.4. 程序执行监控说明

2.4.1. 停止

在类 System 中定义的方法 exit 可用于停止程序的执行。

语法 : void exit(int status)

作用:终止当前进程,并将 status 值返回给父进程

exit 会导致当前进程终止,并将控制权交还给调用进程。调用进程可使用 status 的值。 在 DOS 系统下,该变量 status 会被返回至 DOS,并存储在系统变量 ERRORLEVEL 中,其值可在批处理文件中进行检测。 在 Unix 系统中,如果命令解释器是 Bourne Shell(/bin/sh),则变量 $? 将获取 status 的值。

示例:

    System.exit(0);

用于以状态值 0 终止程序。

2.4.2. 简单选择结构


 syntaxe :  if (condition) {actions_condition_vraie;} else {actions_condition_fausse;}

  • 条件用圆括号括起。
  • 每个操作以分号结尾。
  • 大括号内不以分号结尾。
  • 只有当存在多个操作时才需要大括号。
  • else子句可以省略。
  • 没有 then 子句。

该结构的算法等价形式是“如果……那么……否则”结构:

si condition
  alors actions_condition_vraie
  sinon actions_conditions_fausse
finsi

示例


    if (x>0)  { nx=nx+1;sx=sx+x;} else dx=dx-x;

可以选择结构进行嵌套:

if(condition1)
if (condition2)
        {......}
      else         //条件2
         {......}
else         //条件1
     {.......}

有时会出现以下问题:


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

pour i variant de id à if avec un pas de ip
        actions
finpour

这可以转换为结构 tantque

    i  id
    tantque i<=if
        actions
        i i+ip
    fintantque

2.4.4.2. 重复次数未知

Java 中有许多适用于这种情况的结构。

while 循环


    while(condition){
          actions;
        } 

只要条件成立,循环就会继续。该循环可能永远不会被执行。

  • 条件用圆括号括起来。
  • 每个操作以分号结尾。
  • 只有当存在多个操作时才需要大括号。
  • 大括号后不加分号。

相应的算法结构是“直到”结构:

tantque condition
        actions
fintantque

重复直到(do while)结构

语法如下:


    do{
       instructions;
    }while(condition); 

循环将持续进行,直到条件变为假,或者只要条件为真。在此情况下,循环至少执行一次。

  • 条件用圆括号括起来。
  • 每个操作以分号结尾。
  • 只有当存在多个操作时才需要大括号。
  • 大括号后不跟分号。

相应的算法结构是“重复……直到”结构:

répéter
    actions
jusqu'à 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 中的不同指令用逗号分隔。

相应的算法结构如下:

instructions_départ
tantque condition
        actions
        instructions_fin_boucle
fintantque

示例

以下程序均计算前 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
}// 类

我们来看一个例子。假设以下是一条写入指令:

System.out.println("java");

该指令会在屏幕上输出 java。在这条简单的指令中包含了许多内容:

  • System 是一个类,其全名为 java.lang.System
  • out 是该类的属性,其类型为 java.io.PrintStream(另一个类)
  • println 是类 java.io.PrintStream 的一个方法。

我们不会无谓地让这个解释变得复杂,因为它出现得太早,需要理解尚未涉及的“类”这一概念。可以将类视为一种资源。在此,编译器需要访问 java.lang.Systemjava.io.PrintStream 这两个类。 Java中的数百个类被分发在称为“包”(package)的归档中。程序开头处的import语句用于告知编译器该程序需要哪些外部类(即将在源文件中使用但未在该源文件中定义的类)。 因此,在本例中,我们的程序需要 java.lang.Systemjava.io.PrintStream 这两个类。我们通过 import 语句来声明这一点。可以在程序开头写:

import java.lang.System;
import java.io.PrintStream;

由于 Java 程序通常会使用数十个外部类,如果逐一编写所有必要的 import 函数,将会非常繁琐。 这些类已被归类到包中,因此可以导入整个包。例如,要导入 java.langjava.io 这两个包,应写为:

import java.lang.*;
import java.io.*;

java.lang 包包含所有 Java 基础类,并由编译器自动导入。因此最终只需编写:

import java.io.*;

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

也可以写成:

catch (Exception ex){
    System.err.println ("L'erreur suivante s'est produite : "+ex);
    ...
}//捕获

这里有一个字符串 + 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
}//类

部分执行结果:

dos>java console1
Nom : dupont
âge : 23
Vous vous appelez dupont et vous avez 23 ans
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

E:\data\serge\JAVA\ESSAIS\intro1>dir
10/06/2002  08:42                  228 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

E:\data\serge\JAVA\ESSAIS\intro1>"e:\program files\jdk14\bin\java" coucou
coucou

需要注意的是,在上述执行请求中,并未明确指定要执行的文件 coucou.class 的后缀 .class。该后缀是隐含的。 如果 JDK 中的 bin 目录位于 DOS 机器的 PATH 中, 则无需提供 javac.exejava.exe 可执行文件的完整路径。此时只需写入

javac coucou.java
java coucou

2.8. 主程序的参数

主函数 main 接受一个字符串数组作为参数:String[]。该数组包含用于启动应用程序的命令行参数。因此,如果使用以下命令启动程序 P:

        java P arg0 arg1 … argn

且 main 函数声明如下:

public static void main(String[] arg);

则将得到 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]);
  }
}

所得结果如下:

dos>java param1 a b c
Nombre d'arguments=3
arg[0]=a
arg[1]=b
arg[2]=c

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);
  }
}

所得结果如下:

Paramètre formel S=maman
Paramètre effectif S=papa
Paramètre formel a=30
Paramètre effectif age=20

实际参数“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,则会找到该行

    24740        0.15        2072.5

此时税额 I 等于 0.15*R - 2072.5*nbParts。 如果 QF 使得关系 QF<=field1 从未成立,则使用最后一行中的系数。此处:

    0                0.65        49062

由此得出的税额 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