本文介绍分支的流程控制与循环的流程控制程序语句结构,并编写案例,进一步应用在聊天系统的设计中。并对异常流程、流程框图以及for循环的简写形式进行了扩展。
                 爱校码本文介绍分支的流程控制与循环的流程控制程序语句结构,并编写案例,进一步应用在聊天系统的设计中。并对异常流程、流程框图以及for循环的简写形式进行了扩展。
声明:本系列博文为"爱校码.中国"原创博文,版权所有,未经授权,不得转载或抄袭,若发现将依法追责。
顺序执行的程序虽然能解决计算、输出等问题,但不能做判断再选择。分支结构的程序设计方法的关键在于构造合适的分支条件和分析程序流程,根据不同的程序流程选择适当的分支语句。聊天系统中具有这种选择判断的分支流程,需要学习者实践这样的流程控制。
复杂的程序有时需要根据条件判断决定去执行一个语句块,而跳过另一些语句块,完成相应的逻辑功能,分为双向分支和多向分支。
if-else结构分支通过布尔语句实现了程序流程的双向分支,其基本格式为:
if (condition) {
    statement1;
}
else{
    statement2;
}
else子句是可选的,condition是任何返回值为boolean类型(true或false)的表达式,statement1和statement2是括在大括号内的单个语句或数个语句的复合语句。当condition的返回值为true时,执行statement1;。当condition的返回值为false时,执行statement2;。if-else结构的执行流程如图所示。图中的statement3是紧跟if-else结构的语句。

清单 3-1, 单个if-else结构分支程序:
 1: /**
 2: * @author zcj
 3: * 单个if-else结构
 4: */
 5: public class SingleCondition {
 6:     /**
 7:    * @param args   
 8:    */
 9:     public static void main(String[] args){
 10:        //声明整型的温度变量并初始化
 11:        int temperature = 30;
 12:        //if-else结构判断
 13:        if(temperature < 35){
 14:            System.out.println("坚持工作!");
 15:         }else{
 16:             System.out.println("太热了,回家休息!");
 17:         }
 18:    }
 19: }
有时对于复杂的判断,单个if-else还不够,需要多个if-else的连接,根据每个条件的双向分支选择其一执行,从第一个if语句到最后一个if语句依次执行,如果某个 if语句内的条件表达式的返回值为true,则执行其相关联的statement语句,其格式为:
if (condition1) {
    statement1;
}
else if (condition2) {
    statement2;
}
…
else if (conditionM) {
    statementM; 
}
清单3-2, 多个if-else的连接结构的程序:
1:/**
2: * @author zcj
3: * 嵌套if-else结构
4: */
5: public class MultiCondition {
6:     /**
7:      * @param args   
8:      */
9:      public static void main(String[] args){     
10:         int month = 8; //声明一个表示月份的整型变量,并初始化一个值
11:         String season; //声明一个表示季节的字符串变量
12:         //多重条件判断
13:         if(month==12 || month==1 || month==2){
14:                season = "冬季";
15:         }else if(month==3 || month==4 || month==5){
16:             season = "春季";
17:         }else if(month==6 || month==7 || month==8){
18:             season = "夏季";
19:         }else if(month==9 || month==10 || month==11){
20:                season = "秋季";
21:         }else{
22:              season = "不确定季节";
23:         }
24:         System.out.println("现在的季节是:"+season);
25:    }
26: }
switch语句根据表达式的返回值的不同结果,跳转到相匹配对应的代码部分执行,实现了程序流程的多向分支,其基本格式为:
switch(expression) {
    case value1:
         statement1;
         break;
    case value2:
        statement2;
        break;
    …
    case valueN:
       statementN;
       break;
    default:
       statementM;
}
expression可以是返回任何原始数据类型的数据,而case后的values与expression的返回值必须是相兼容数据类型,而且每个values是唯一的常量。如果expression的返回值与某个case 后的value值比较后相匹配,则执行其后的statementN语句,最后,执行break语句,使执行流程跳转到switch语句的结尾。如果表达式的返回值与任何case值不匹配,则执行default后的statementM,如果switch中没有default语句,则不执行任何代码。switch结构的执行流程如图所示。

将清单3-2改写为使用switch语句。
清单3-3, switch结构程序:
1:  /**
2:  * @author zcj
3:  * switch结构
4:  */
5:  public class SwitchSeason {
6:      /**
7:      * @param args   
8:      */
9:      public static void main(String[] args) {
10:         int month = 8; //声明一个表示月份的整型变量,并初始化一个值
11:         String season; //声明一个表示季节的字符串变量
12:        //switch语句
13:         switch(month){
14:             case 3:    //继续执行下一个case语句
15:             case 4:    //继续执行下一个case语句
16:             case 5:
17:                   season = "春季";
18:                   break;
19:             case 6:    //继续执行下一个case语句
20:             case 7:    //继续执行下一个case语句
21:             case 8:
22:                     season = "夏季";
23:                     break;
24:             case 9:   //继续执行下一个case语句
25:             case 10:  //继续执行下一个case语句
26:             case 11:
27:                     season = "秋季";
28:                         break;  
29:             case 12:  //继续执行下一个case语句
30:             case 1:   //继续执行下一个case语句
31:             case 2:
32:                     season = "冬季";
33:                     break;
34:             default:
35:                     season = "不确定季节";
36:         }
37:         System.out.println("现在的季节是:"+season);
38:     }
39: }
由于项目代码的完整性,有时if-else流程控制语句与switch流程控制语句的运用会交叉进行,为了不刻意分割程序代码,在此将子任务1的内容与子任务2的内容放在一起进行介绍,在解释程序行代码时给予说明。程序代码在子任务2中展示。
清单3-4, 聊天系统Client.java:
1: package cn.ischoolcode.scene3;
2: import java.net.*;
3: import java.io.*;
4: import java.util.*;
5: import cn.ischoolcode.chatcui.ChatMessage;
6: /*
7:  * 2023 爱校码.中国
8:  * 版权所有
9:  */
10: /**
11:  * 控制台界面运行的客户端程序
12:  *  @ version 1.0
13:  *  @ author zcj 
14: */
15: public class Client  {
16:     // 用于 I/O
17:     private ObjectInputStream sInput;   // 从套接字 读
18:     private ObjectOutputStream sOutput; // 写到套接字
19:     private Socket socket;  
20:     // 服务器地址,端口号和用户名
21:     private String server, username;
22:     private int port;
23:     /**
24:     *  控制台模式调用的构造方法
25:     *  @param server: 服务器地址
26:     *  @param port: 端口号
27:     *  @param username: 用户名
28:     */
29:     public Client(String server, int port, String username) {
30:         this.server = server;
31:         this.port = port;
32:         this.username = username;
33:     }
34:     /*
35:      * 开始对话
36:      */
37:     public boolean start() {
38:         // 试图连接到服务器
39:         try {
40:             socket = new Socket(server, port);
41:         } 
42:         // 出现异常
43:         catch(Exception ec) {
44:             display("连接到服务器时出现错误:" + ec);
45:             return false;
46:         }
47:         String msg = "连接已接受 " + socket.getInetAddress() + ":" + socket.getPort();
48:         display(msg);
49:         /* 创建输入输出两个对象流*/
50:         try
51:         {
52:             sInput  = new ObjectInputStream(socket.getInputStream());
53:             sOutput = new ObjectOutputStream(socket.getOutputStream());
54:         }
55:         catch (IOException eIO) {
56:             display("Exception creating new Input/output Streams: " + eIO);
57:             return false;
58:         }
59:         // 创建侦听自服务器端的线程 
60:         new ListenFromServer().start();
61:         // 发送用户名到服务器端,这是仅为一个字符串发送。
62:         // 所有其它的信息将是 ChatMessage类型的对象
63:         try
64:         {
65:             sOutput.writeObject(username);
66:         }
67:         catch (IOException eIO) {
68:             display("登录时出现异常 : " + eIO);
69:             disconnect();
70:             return false;
71:         }
72:         // 告知调用者,工作成功
73:         return true;
74:     }
75:     /*
76:      * 发送一个信息到控制台
77:      */
78:     private void display(String msg) {       
79:         System.out.println(msg);    // 控制台模式输出打印         
80:     }
81:     /*
82:      * 传递一条信息到服务器端
83:      */
84:     void sendMessage(ChatMessage msg) {
85:         try {
86:             sOutput.writeObject(msg);
87:         }
88:         catch(IOException e) {
89:             display("Exception writing to server: " + e);
90:         }
91:     }
92:     /*
93:      * 断开连接
94:      * 关闭输入/输出流 
95:      */
96:     private void disconnect() {
97:         try { 
98:             if(sInput != null) sInput.close();
99:         }
100:        catch(Exception e) {}  
101:        try {
102:            if(sOutput != null) sOutput.close();
103:        }
104:        catch(Exception e) {}  
105:        try{
106:             if(socket != null) socket.close();
107:         }
108:         catch(Exception e) {}              
109:     }
110:    /*
111:     * 在命令行控制台开始运行客户端,使用下列命令
112:     * > java Client
113:     * > java Client 用户名
114:     * > java Client 用户名  端口号
115:     * > java Client 用户名  端口号  服务器地址
116:     * 控制台提示
117:     * 如果端口号没有指定,1500将作为默认端口号
118:     * 如果服务器地址没有指定, "localHost" 将作为默认地址使用
119:     * 如果用户名没有指定, "Anonymous" 将作为默认用户使用
120:     * > java Client 
121:     * 等效于
122:     * > java Client Anonymous 1500 localhost 
123:     *  
124:     * 在控制台模式下,如果出现错误,程序将停止  
125:     */
126:    public static void main(String[] args) {
127:        // 缺省值
128:        int portNumber = 1500;
129:        String serverAddress = "localhost";
130:        String userName = "Anonymous";
131:        // 依据命令行参数的数量进行多分支判断
132:        switch(args.length) {
133:            // > javac Client 用户名  端口号  服务器地址
134:            case 3:
135:                serverAddress = args[2];
136:            // > javac Client 用户名  端口号
137:            case 2:
138:                try {
139:                    portNumber = Integer.parseInt(args[1]);
140:                }
141:                catch(Exception e) {
142:                    System.out.println("无效端口号.");
143:                     System.out.println("用法为: > java Client [用户名]"
144:                                           +"[端口号] [服务器地址]");
145:                    return;
146:                }
147:            // > javac Client 用户名
148:            case 1: 
149:                userName = args[0];
150:            // > java Client
151:            case 0:
152:                break;
153:            // 无效的参数
154:            default:
155:                System.out.println("用法为: > java Client [用户名] [端口号]"
156:                                            +"[服务器地址]");
157:            return;
158:        }
159:        // 创建Client类的对象
160:        Client client = new Client(serverAddress, portNumber, userName);
161:        // 测试如果连接到服务器,程序将继续进行
162:        // 如果失败,则什么也不做,返回
163:        if(!client.start())
164:            return;
165:        // 等待控制台的用户信息输入
166:        Scanner scan = new Scanner(System.in);
167:        // 永久循环,扫描用户信息
168:        while(true) {
169:            System.out.print("> ");
170:            // 读用户信息
171:            String msg = scan.nextLine();
172:            // 如果信息为LOGOUT,则注销
173:            if(msg.equalsIgnoreCase("LOGOUT")) {
174:                client.sendMessage(new ChatMessage(ChatMessage.LOGOUT, ""));
175:                // 退出循环,断开连接
176:                break;
177:            }
178:            // 发送查询用户列表信息为WhoIsIn
179:            else if(msg.equalsIgnoreCase("WHOISIN")) {
180:                client.sendMessage(new ChatMessage(ChatMessage.WHOISIN, ""));   
181:            }
182:            else {  // 缺省的普通信息
183:                client.sendMessage(new ChatMessage(ChatMessage.MESSAGE, msg));
184:            }
185:        }
186:        // 断开连接操作
187:        client.disconnect();    
188:    }
189:    /*
190:     * 处理等待服务器端信息的类
191:     *  在控制台模式下,简单的通过 System.out.println()显示
192:     */
193:    class ListenFromServer extends Thread {
194:        public void run() {
195:            while(true) {
196:                try {
197:                    String msg = (String) sInput.readObject();
198:                    // 在控制台打印信息,返回提示符
199:                     
200:                        System.out.println(msg);
201:                        System.out.print("> ");                  
202:                }
203:                catch(IOException e) {
204:                    display("服务器已关闭连接: " + e);                   
205:                    break;
206:                }                
207:                catch(ClassNotFoundException e2) {
208:                }
209:            }
210:        }
211:    }
212: }
在清单3-4代码中,代码行173-184实现了子任务1中的if-else流程控制语句。代码行132-158实现了子任务2种中的switch流程控制语句。其它代码行基本都有注释说明。本程序的编译,需要导入(import)情景2任务2的子任务2中的清单2-7的ChatMessage类, 然后,在本情景的任务2中的子任务中介绍的服务端类Server启动装载后,就可以按照代码行111-125注释说明指导执行程序,显示客户端的信息。有些代码行涉及以后的情景讲解的内容,可以先不用理会,到时将会进一步学习。
如果需要一个else子句,则必须使用if-else语句:
if (<布尔表达式>) {
     <语句或语句块>
}else{
     <语句或语句块>
}
如果需要连续进行条件检查,则可连接一系列的if-else-if语句:
if (<布尔表达式>) {
     <语句或语句块>
}else if (<布尔表达式>){
     <语句或语句块>
}
当带else与不带else两种形式的if语句混合嵌套使用时,往往容易产生误解,例如:
if (表达式1)  if (表达式2)  语句1 else 语句2
该语句虽然合法,但其可以理解为:
if (表达式1) { if (表达式2)  语句1 else 语句2}
也可以理解为:
if (表达式1) { if (表达式2)  语句1 } else 语句2
显然两种不同理解的执行结果就不同,这样造成了逻辑上的二义性。将程序设计语言存在的这样问题,称为垂悬else问题。
Java语言采用“最近匹配原则”来解决垂悬else问题,指定else必须匹配最近那个没有匹配的if,即出现在else前的语句应该是已经匹配的语句。所以,正确的理解应该是:
if (表达式1) { if (表达式2)  语句1 else 语句2}
为了减少程序中的二义性,建议在出现垂悬else问题时尽量使用花括号显示表示else的匹配关系。
在情景2中介绍逻辑运算符时,了解到?:是一种三元运算符,其格式为:
expression ? statement1 : statement2
expression是一个表达式,其中包含布尔条件,当布尔条件为true,则执行statement1返回,否则,就执行statement2返回。样例代码如下:
int totalSize = 7;
int blockSize = 3;
int totalBlocks =((totalSize / blockSize) +
                 (((totalSize % blockSize) > 0)?1:0));
以上totalBlocks结果值为3。在参与的取模运算中,取模结果大于零,则问号前的布尔表达式为true,返回值为1,与前面的除法运算结果2相加。
在聊天系统的程序流程控制中,需要用来描述重复执行某段算法的问题,学习者为了减少源程序重复书写的工作量,使用循环结构实现该流程控制。
循环意味着重复执行某一程序代码块,直到满足终止循环的条件。Java有三种循环结构,它们分别是while、do-while和for。
while循环首先判断布尔表达式的返回值,若为true,不断地执行循环语句,直到为false而中断循环。基本的格式如下:
[initialization;]
while (condition) {
   statements;
   [iteration;]
}
initialization是循环的初始状态,用于设置循环变量的初始值。iteration是在循环语句执行后,下次循环开始前执行的迭代因子,用于递增或递减循环变量的值。condition为布尔表达式,只要其返回值为true,statements语句块就不断地执行。while循环的执行流程如图所示:

清单3-5, while循环结构, 说明一个数字递增的循环:
1:  /**
2:  * @author zcj
3:  * 数字递增循环
4:  */
5:   public class NumberIncrease {
6:      public static void main(String[] args) {
7:          // 声明整型的循环变量并初始化
8:          int number = 1;
9:          while(number < 10 ){
10:           System.out.println("数值:"+number);
11:           number++;
12:         }
13:     }
14: }
有时需要将循环的语句块至少执行一次,即使放在循环体之后的布尔表达式的返回值一开始就为false,do-while结构就能满足这种情况。基本格式如下:
[initialization;]
do {
   statements;
   [iteration;]
}while (condition);
do-while循环的执行流程如图所示:

清单3-6, do-while循环结构, 说明一个数字递减的循环:
 1:  /**
 2:  * @author zcj
 3:  * 数字递减循环
 4:  */
 5:   public class NumberDescending {
 6:        /**
 7:         * @param args   
 8:         */
 9:         public static void main(String[] args) {
 10:            // 声明整型的循环变量并初始化
 11:            int number = 10;
 12:            // 循环
 13:            do{
 14:              System.out.println("数值:"+number);
 15:              number--;
 16:            }while(number > 0);         
 17:       }
 18:  }
for表示循环的一种紧凑形式,它将初始化、条件判断和迭代放在一起,基本格式如下:
for (initialization; condition; iteration) {
    statements;
}
for循环通常表示一个整数从小到大的多次循环,或用于数组的遍历。initialization表示用于设置循环计数的整型变量的初始值,仅在开始循环时执行一次,一般用i代表index;condition是一个布尔表达式,其返回值为true或false,用来决定循环的继续执行或终止;iteration表示修改迭代的计数整型变量,其执行是在statements执行之后和condition检查之前进行。for循环的执行流程如图所示:

清单3-7, for循环结构:
1:  /**
2:  * @author zcj
3:  * for循环的数组遍历
4:  */
5: public class ArraySearch {
6:    public static void main(String[] args) {
7:      // 遍历命令行参数的数组
8:      for(int i=0;i<args.length;i++){
9:          System.out.println(args[i]);            
10:     }
11:   }
12: }
有时initialization和 iteration中需要多个语句,使用逗号(,)分隔多个语句。
清单3-8, 多个语句的循环结构:
1:  /**
2:  * @author zcj
3:  * for循环中的逗号使用
4:  */
5: public class Comma {
6:    public static void main(String[] args) {
7:      // for循环的初始化语句和迭代语句通过逗号隔开多个语句
8:      for(int i=1,j=5;i<j;i++,j--){
9:          System.out.println("i="+i);
10:         System.out.println("j="+j);
11:     }
12:   }
13: }
break语句主要用于switch的分支结构和循环结构中,表示终止其后面语句的执行,退出switch或循环结构。
清单3-9, 循环结构中的break语句使用:
1: /**
2:  * @author zcj
3:  * Break用于循环结构中
4:  */
5: public class BreakUse {
6:     public static void main(String[] args) {
7:       // for循环
8:       for(int i=1;i<50;i++){
9:           if(i==20){
10:               break;
11:           }
12:           System.out.println("循环计数="+i);
13:       }
14:       System.out.println("退出循环");
15:    }
16: }
continue语句用于循环中,有时需要跳过循环的剩余的部分代码而开始新一次的循环,控制循环直接到下一次循环的条件表达式,从而决定循环的继续或退出。
清单3-10, 循环结构中的continue语句使用:
1:  /**
2:  * @author zcj 
3:  * Continue用于for循环结构中
4:  */
5: public class ContinueUse {
6:      public static void main(String[] args) {
7:           // 嵌套for循环,输出三角形的0到9的乘法表
8:           lable: for (int i = 0; i < 10; i++) {
9:                  for (int j = 0; j < 10; j++) {
10:                    if (j > i) {
11:                        System.out.println("");
12:                        continue lable;
13:                     }
14:                     System.out.print(i * j + " ");
15:                  }
16:           }
17:      }
18: }
输出结果如图所示:

在本情景的任务1的子任务中,实现了控制台界面聊天系统的客户端,在那里,程序流程有分支结构,也有循环结构。在这里,将实现控制台界面聊天系统的服务器端,其中,while循环与for循环交叉实现,子任务1和子任务2的流程控制代码将整合在一起。程序代码在子任务2中展示。
清单3-11, 聊天系统 Server.java:
1: package cn.ischoolcode.scene3;
2: import java.io.*;
3: import java.net.*;
4: import java.text.SimpleDateFormat;
5: import java.util.*;
6: import cn.ischoolcode.chatcui.ChatMessage;
7: /*
8:  * 2023 爱校码.中国
9:  * 版权所有
10: */
11: /**
12:  *  作为控制台应用程序运行的服务器端程序
13:  *  @version 1.0
14:  *  @author zcj
15:  */
16:  public class Server {
17:     // 为每个连接定义的唯一ID
18:     private static int uniqueId;
19:     // 定义一个保持客户端列表的动态数组
20:     private ArrayList<ClientThread> clientList;  
21:     // 定义显示时间日期的格式引用对象变量
22:     private SimpleDateFormat sdf;
23:      // 侦听连接的端口号
24:     private int port;
25:     // 定义一个判断服务器端程序停止的布尔变量
26:     private boolean keepGoing;  
27:     /*
28:      *  服务器端构造方法,其参数为侦听连接的端口号
29:      */
30:     public Server(int port) {       
31:         this.port = port;                           // 端口号      
32:         sdf = new SimpleDateFormat("HH:mm:ss");     //显示格式HH:mm:ss      
33:         clientList = new ArrayList<ClientThread>(); //客户列表
34:     }   
35:     public void start() {
36:         keepGoing = true;
37:         /* 创建套接字服务器并等待连接请求 */
38:         try 
39:         {
40:             // 服务器套接字
41:             ServerSocket serverSocket = new ServerSocket(port);
42:             // 无限循环来等待连接
43:             while(keepGoing) 
44:             {
45:                 // 格式化信息是说正在等待
46:                 display("服务器正在端口" + port + "上等待客户.");               
47:                 Socket socket = serverSocket.accept(); // 接受连接
48:                 // 判断是否停止
49:                 if(!keepGoing)  
50:                     break;
51:                 ClientThread clientThread = new ClientThread(socket);//产生一个线程
52:                 clientList.add(clientThread);           // 保存线程到客户列表
53:                 clientThread.start();
54:             }
55:             // 被要求停止
56:             try {
57:               serverSocket.close();
58:               for(int i = 0; i < clientList.size(); ++i) {
59:                 ClientThread ct = clientList.get(i);
60:                 try {
61:                     ct.sInput.close();
62:                     ct.sOutput.close();
63:                     ct.socket.close();
64:                 }
65:                 catch(IOException ioex) {
66:                     ioex.printStackTrace();                      
67:                 }
68:               }
69:             }
70:             catch(Exception e) {
71:                display("在关闭服务器与客户线程时出现异常: " + e);
72:             }
73:         }
74:          // 出现某些问题
75:         catch (IOException e) {
76:             String msg = sdf.format(new Date()) + " 异常发生在新的服务器套接字: " + e + "\n";
77:           display(msg);
78:        }
79:     }
80:    /*
81:     * 在命令行控制台上显示对应一个事件(不是一条信息)
82:     */
83:     private void display(String msg) {
84:         String time = sdf.format(new Date()) + " " + msg;        
85:         System.out.println(time);
86:     }
87:     /*
88:      *  广播一条信息到所有的客户端
89:      */
90:     private synchronized void broadcast(String message) {
91:         // 添加 HH:mm:ss 格式和换行符 \n 与信息字符串连接
92:         String time = sdf.format(new Date());
93:         String messageLf = time + " " + message + "\n";
94:         // 在命令行控制台界面显示信息         
95:         System.out.print(messageLf);
96:         // 以反向循环删除每个已断开连接的客户端线程      
97:         for(int i = clientList.size(); --i >= 0;) {
98:             ClientThread clientThread = clientList.get(i);
99:             // 尝试写入到客户端,如果失败,将它从列表中删除
100:            if(!clientThread.writeMsg(messageLf)) {
101:               clientList.remove(i);
102:               display("断开连接的客户端 " + clientThread.username + " 已从列表中删除.");
103:            }
104:        }
105:    }
106:    // 针对一个客户端,使用注销类型的信息完成注销任务
107:    synchronized void remove(int id) {
108:        // 遍历数组列表,直到找到对应的id为止
109:        for(int i = 0; i < clientList.size(); ++i) {
110:            ClientThread ct = clientList.get(i);
111:            // 判断是否找到id
112:            if(ct.id == id) {
113:                clientList.remove(i);
114:                return;
115:            }
116:        }
117:    }   
118:    /*
119:     *  为了运行控制台应用程序,只要打开命令行控制台界面,运行以下命令: 
120:     * > java Server
121:     * > java Server 端口号
122:     * 如果端口号没有指定,则使用1500作为端口号
123:     */ 
124:    public static void main(String[] args) {
125:        // 除非指定端口,否则服务器将在端口 1500上开始启动
126:        int portNumber = 1500;
127:        switch(args.length) {
128:            case 1:
129:                try {
130:                    portNumber = Integer.parseInt(args[0]);
131:                }
132:                catch(Exception e) {
133:                    System.out.println("无效的端口号。");
134:                    System.out.println("用法是: > java Server [端口号]");
135:                    return;
136:                }
137:             case 0:
138:                break;
139:             default:
140:                System.out.println("用法是: > java Server [端口号]");
141:                return;             
142:        }
143:        // 生成服务器对象并且运行该对象的start方法
144:        Server server = new Server(portNumber);
145:        server.start();
146:    }   
147:    class ClientThread extends Thread {
148:        // 定义一个听和说的套接字对象
149:        Socket socket;
150:        ObjectInputStream sInput;
151:        ObjectOutputStream sOutput;
152:        // 本地唯一id 
153:        int id;
154:        // 客户端用户名
155:        String username;
156:        // 将接收的某一类型信息
157:        ChatMessage cm;
158:        // 连接日期
159:        String date;        
160:        ClientThread(Socket socket) {            
161:            id = ++uniqueId;
162:            this.socket = socket;
163:            /* 创建双向数据流 */
164:            System.out.println("本线程试图创建‘输入/输出’对象流");
165:            try
166:            {
167:                sOutput = new ObjectOutputStream(socket.getOutputStream());
168:                sInput  = new ObjectInputStream(socket.getInputStream());
169:                // 读用户名
170:                username = (String) sInput.readObject();
171:                display(username + " 刚连接上.");
172:            }
173:            catch (IOException e) {
174:                display("Exception creating new Input/output Streams: " + e);
175:                return;
176:            }
177:            // 捕获 ClassNotFoundException类型的异常对象         
178:            catch (ClassNotFoundException e) {
179:            }
180:            date = new Date().toString() + "\n";
181:        }       
182:        // 将长久运行
183:        public void run() {
184:            //循环直到收到'注销'类型的信息
185:            boolean keepGoing = true;
186:            while(keepGoing) {
187:                // 读取某种类型的信息对象
188:                try {
189:                    cm = (ChatMessage) sInput.readObject();
190:                }
191:                catch (IOException e) {
192:                    display(username + " 读取流时出现异常: " + e);
193:                    break;              
194:                }
195:                catch(ClassNotFoundException e2) {
196:                    break;
197:                }
198:                // 获取信息对象中消息字符串信息部分
199:                String message = cm.getMessage();
200:                // 对收到的信息对象进行信息类型的多分支判断
201:                switch(cm.getType()) {
202:                    case ChatMessage.MESSAGE:
203:                        broadcast(username + ": " + message);
204:                        break;
205:                    case ChatMessage.LOGOUT:
206:                        display(username + " 通过LOGOUT信息断开连接..");
207:                        keepGoing = false;
208:                        break;
209:                    case ChatMessage.WHOISIN:
210:                        writeMsg("用户列表连接在 " 
211:                                           + sdf.format(new Date()) + "\n");
212:                         //遍历客户端列表中的已连接用户
213:                        for(int i = 0; i < clientList.size(); ++i) {
214:                            ClientThread ct = clientList.get(i);
215:                            writeMsg((i+1) + ") " + ct.username + " 自 " + ct.date);
216:                        }
217:                        break;
218:                }
219:            }
220:            // 从包含已连接客户端的列表中删除自己
221:            remove(id);
222:            close();
223:        }       
224:        // 试图关闭每一个实例
225:        private void close() {
226:            //试图关闭连接
227:            try {
228:                if(sOutput != null) sOutput.close();
229:            }
230:            catch(Exception e) {}
231:            try {
232:                if(sInput != null) sInput.close();
233:            }
234:            catch(Exception e) {};
235:            try {
236:                if(socket != null) socket.close();
237:            }
238:            catch (Exception e) {}
239:        }
240:        /*
241:         * 写一个字符串到客户端的输出流
242:         */
243:        private boolean writeMsg(String msg) {
244:                // 判断如果客户端仍然连接,则发送信息给它
245:            if(!socket.isConnected()) {
246:                close();
247:                return false;
248:            }
249:            // 写信息到流
250:            try {
251:                sOutput.writeObject(msg);
252:            }
253:            // 如果出现异常,不终止,仅仅通知用户
254:            catch(IOException e) {
255:                display("发送信息到用户" + username+"时出现错误");
256:                display(e.toString());
257:            }
258:            return true;
259:        }       
260:    }
261: }
在清单3-11代码中,代码行43-54为while循环流程控制;代码行58-68为for循环流程控制;代码行97-104为for循环流程控制;代码行109-116为for循环流程控制;代码行186-219为while循环流程控制。除了循环控制结构,还实现了异常流程控制,其代码为:代码行38-78行(其中嵌套的异常流程控制代码行为56-72行,还有下层嵌套60-67行);代码行165-179行;代码行188-197行;代码行227-238行;代码行250-257行。
另外,该服务器端的流程控制代码也实现分支结构流程控制,代码行49-50行、代码行100-103行、代码行112-115行、代码行245-248行分别实现了二分支流程控制;代码行127-142行、代码行201-218行分别实现了多分支流程控制。
最后,按照代码行118-123行的注释说明,就可以运行Server程序。然后,再按照本情景任务1中的子任务节中的Client代码行110-125行的注释说明,就可以运行Client程序。此时,在Client的控制台界面可以进行聊天了。
Java中的异常处理也可以导致程序控制结构的变化,在后续情景中将会详细的讨论,在此了解异常的控制结构,正确理解异常处理的含义。
异常是程序执行时出现的非正常状态,Java程序要能够识别并捕获这些异常,然后进一步跟踪处理异常。Java中异常处理采用面向对象的方法,每个异常是一个描述异常状态的对象,每当出现一个异常情况,就创建一个异常对象,然后对这个对象进行处理,报告错误信息,在程序继续执行前或退出前执行释放资源的特定代码。
异常处理的基本格式如下:
try{
    statements;
}
catch(ExceptionType1 ex1){
    hander1;
}
catch(ExceptionType2 ex2){
    hander2;
}
…
catch(ExceptionTypeN exN){
    handerN;
}
finally{
    LastStatements;
}
异常处理的执行流程如图所示:

Java中的异常可以是Java系统类库(Java API)提供定义的异常类型,在后续情景中详细介绍,也可以根据需要自己定义,当然在定义之前需要清楚Java中的异常类别。
Throwable类是Java语言中所有错误或异常的超类。只有当对象是此类(或其子类之一)的实例时,才能通过Java虚拟机或者 Java throw 语句抛出。Throwable类有两个直接的子类,分别为Exception和Error,Exception用于描述捕获的异常情况,它是所有异常类别的父类,而Error表示不希望捕获的错误,因为Error对象的创建通常发生在出现了灾难性的错误。
Exception有一个特殊子类是RuntimeException,它是那些可能在 Java 虚拟机正常运行期间抛出的异常的超类。例如,在算术的除法运算中,如果除数为零,则Java虚拟机创建一个ArithmeticException类型的异常对象,它的类型为RuntimeException的子类,这时停止除法操作并转向异常处理程序。如果没有定义异常处理程序,那么Java虚拟机调用缺省的异常处理程序,通常是打印Exception类型的异常的字符串值和异常出现的堆栈位置,即通过printStackTrace()方法实现。
流程图在软件设计和开发中使用非常广泛,能够直观地表示软件系统的功能和编程逻辑,使得软件项目成员之间的沟通更方便。
⑴ 框图
在介绍框图之前,首先需要了解一个流程活动的划分,将其分为三个阶段,第一阶段称为输入阶段(Input),第二阶段称为处理过程阶段(Process),第三阶段称为输出阶段(Output),有时简称为I-P-O。
框图是一种图形表示,按照一定的步骤来求解问题。每个框图由一组符号组成,每个符号代表一个特定的活动。以下图中列出了框图中使用的符号。

用框图表示两个数相加:

⑵ 表示条件判断的程序流程:
如果输入两个数,并进行条件判断,将两数中的较大者显示出来,可以用图所示的流程实现。

在图中,通过声明数值变量num1和num2,然后输入变量的,再将较大者显示。
⑶ 表示迭代的程序流程
如果需要将20个数值相加,显示其和,可以用图所示的流程实现。

(4) 聊天系统客户端的程序流程图

(5) 聊天系统服务器端的程序流程图

从JDK1.5起,Java提供了另一种形式的for循环。借助这种形式的for循环,可以用更简单地方式来遍历数组和Collection集合类型的容器对象。关于集合类型的容器对象的内容将在后续情景中介绍。本小节介绍这种循环的使用方式,基本的格式如下:
for (final 循环变量类型 循环变量名称 : 容器对象或者数组){
    <语句或语句块>
}
其中,循环变量类型可以为原始数据类型,也可以为引用数据类型,这个取决于容器对象或数组中的元素的数据类型。final修饰符关键字用于杜绝有意或无意的进行“在循环体内修改循环变量”的操作。样例代码如下:
⑴ 数组
int marks[]={85,60,80,70};
for (final int mark : marks) {
    System.out.println("成绩是:" + mark);
}
⑵ 容器对象
Collection<String> courses = new ArrayList<String>();
courses.add("英语");
courses.add("C语言程序设计");
courses.add("数据库原理及应用");
courses.add("Java语言程序设计");
for (final String course : courses) {
    System.out.println("课程名是:" + course);
}
循环变量的类型可以和被遍历的对象中的元素的类型之间存在着隐式自动转换的关系。自动转换可以在原始数据类型和它们的包装类之间进行,例如用Integer型的循环变量来遍历一个int[]型的数组,或者用byte型的循环变量来遍历一个Collection,是可行的。这里的“元素的类型”,是由被遍历的对象的决定的,如果它是一个Object[]型的数组,那么元素的类型就是Object,即使里面装的都是String类型的数据也是如此。
从JDK1.5起,在实例化Collection类型的对象时,可以限定其元素类型,在上面的样例代码中,使用泛型将Collection类型的对象courses中元素类型限定为String类型。有关泛型的内容,将在后续情景博文的任务中讨论。
博文最后更新时间: