本文介绍面向对象设计的继承性与多态性,并运用在聊天系统的设计中。
声明:本系列博文为"爱校码.中国"原创博文,版权所有,未经授权,不得转载或抄袭,若发现将依法追责。
在聊天系统的程序设计中,基于面向对象的思想,不必每次都从头开始定义一个新的类,而是将新的类作为一个或若干个现有类的扩展或派生。而且抽象出聊天系统中客户端类和服务器端都会使用到的一些公共操作,这些操作在设计时采用抽象类的抽象方法,而在客户端类和服务器端类中分别继承该抽象类,并分别重写具有相同方法名的方法。根据客户端和服务器端的情况,该方法的实现内容则不同。这样,在对象引用该方法时,到底执行客户端的方法还用服务器端的方法,则是由运行时的情况确定的。
继承是将相关类组成的一种层次结构,一个类的直接祖先称为父类,而由父类派生出的类称为子类。子类允许继承父类的非私有的属性和方法。UML表示的继承关系如图所示:继承关系用一条带封闭三角形空箭头的实线来表示,封闭三角形空箭头在父类一边,末端在子类一边。
Java定义类的继承的格式如下:
[类的修饰符] class 子类名 extends 父类名{
}
关键词extends
表示继承关系,等同于UML中的带封闭三角形空箭头的实线这样的图素符号。在类的继承结构中,位于顶层的类称为基类或根类,Java中的自定义类以及或所有的系统类都是通过直接或间接地继承java.lang.Object
类,因此,Object类是java的根类。在自定义类时,没有明确使用关键词extends制定其父类,则默认其父类为Object
类。
Java不支持多重继承,即一个子类只能从一个父类派生。对于需要使用多重继承的程序,可以使用Java的接口来完成,Java接口的详细讨论在下一任务中描述,而用接口提供了Java中多重继承的性质。
继承允许子类派生并并保留父类中的所有变量和方法以及创建自己的变量和方法。如果父类的访问控制符为private,则子类不能继承父类中的成员变量和方法。子类继承父类,通过隐藏父类的成员变量和重写父类的方法,可以把父类的状态和行为改变为自身的状态和行为。
清单 4-3:
1: class MyParent{
2: String name;
3: int age;
4: public String getName(){
5: return name;
6: }
7: public void setName(String strName){
8: name=strName;
9: }
10: public int getAge(){
11: return age;
12: }
13: public void setAge(int iAge){
14: age=iAge;
15: }
16: }
17: public class MyChild extends MyParent{
18: int age; //隐藏了父类的变量age
19: public int getAge(){ //重写了父类的方法getAge()
20: return age;
21: }
22: public void setAge(int iAge){ //重写了父类的方法setAge()
23: age=iAge;
24: }
25: public static void main(String args[]){
26: int myAge;
27: MyChild mychildObj=new MyChild();
28: mychildObj.setAge(20);
29: myAge=mychildObj.getAge();
30: System.out.println("年龄:"+Integer.toString(myAge));
31: }
32: }
代码行18
的整型变量age
隐藏了父类的整型变量age
,代码行19
和代码行22
的方法getAge()
与setAge()
重写了父类的方法getAge()
与setAge()
。
子类不需要重新定义就能够拥有父类的非私有属性变量,而子类实例化为对象时,分配了新的存储空间保存从父类继承的属性变量,其初值为父类属性的初值,这时,父类的属性与子类的属性彼此独立,它们的属性值不会相互影响。如果父类定义的属性数据类型发生了变化,则子类的继承的相应属性数据类型的定义也会发生相同的变化。
同样,子类能够继承父类的非私有方法,而如果需要重新实现父类的方法,则重写父类的方法,要求方法名、参数列表和返回类型相同,访问控制相同或更弱,抛出异常相同或更少,所不同的只是方法体的实现。
当子类隐藏了父类的属性或重写了父类的方法式,为了明确访问的是父类的属性还是子类的属性或者访问的是父类的方法还是子类的方法。采用关键词super
指代父类,其含义是被限定的属性和方法将使用父类的定义声明,而不是子类的定义声明。方式如下:
super.变量;
super.方法;
super
的含义是在this
的实例中调用父类的变量和方法,不能用于静态的上下文中。
在【清单4-3】中23到24行,将其改为:
这时访问的是父类的方法getAge()
, 得到的是父类的属性age
。
22: public void setAge(int iAge){ //重写了父类的方法setAge()
23: super.age=iAge;
24: }
这时访问的是父类的属性age,将19到21行改为:
19: public int getAge(){ //重写了父类的方法getAge()
20: return super.getAge();
21: }
这时访问的是父类的方法getAge()
, 得到的是父类的属性age
。
每个子类的构造方法必须明确或隐含调用它的直接父类的构造方法,隐含调用只是针对无参数列表的父类的构造方法。如果父类没有提供无参数列表的构造方法,则它的直接子类必须显示调用其构造方法。方式如下:
super([paramlist]);
以上代码必须位于子类构造方法的第一行,参数列表必须与父类的构造方法匹配。
继承具有层次结构,子类继承了父类的属性和方法,有以下特点:
子类实例化为对象时,通过子类的构造方法进行,其调用过程如图所示:
以下说明子类构造方法调用过程的代码实现:
清单 4-4
1: /**
2: * 这个程序演示子类构造器构造方法的用法.
3: * @version 1.0
4: * @author zcj
5: */
6: class Author {
7: String name; // 存储作者的姓名.
8: /**
9: * 父类构造方法.
10: * @param name 传递至构造方法的参数 */
11: Author(String name) {
12: this.name = name;
13: System.out.println("从 Author 类中输出");
14: System.out.println("姓名为 " + this.name);
15: }
16: }
17: /**
18: * 这个类是父类的子类.
19: */
20: class SubAuthor extends Author {
21: String type; //存储作者的类型.
22: /**
23: * 子类构造方法.
24: * @ param name 传递至构造方法的参数
25: * @ param type 传递至构造方法的参数
26: */
27: SubAuthor(String name, String type) {
28: super(name);
29: this.type = type;
30: System.out.println("从SubAuthor 类中输出");
31: System.out.println("类型为 " + this.type);
32: }
33: }
34: /**
35: * 这个类测试父类构造方法/子类构造方法.
36: */
37: public class AuthorDemo {
38: /**
39: * main 方法.
40: * @param args 传递至 main 方法的参数
41: */
42: public static void main(String [] args) {
43: SubAuthor subAuthor = new SubAuthor("王晓明" , "散文");
44: }
45: }
代码行6-16行实现了父类的定义,11-15行进行父类构造方法定义;代码行20-33行实现了子类的定义,27-32进行子类构造方法定义,其中28行通过super实现在子类构造方法中调用父类构造方法,并且该行必须位于子类构造方法的第一行;代码行12与代码行29中的this分别代表父类对象和子类对象。
创建子类的一个对象时,它在其中包含了父类的一个“子对象”。 这个子对象就像根据父类本身创建了它的一个对象。从外部看,父类的子对象已封装到子类的对象里了。如图所示:
Java语言中的多态性
体现在静态多态性
和动态多态性
两个方面。静态多态性体现的是方法的重载
,是同一个类内部的多个方法间的关系,它们的方法名相同,参数列表不同。在编译阶段,编译器根据方法的参数列表的类型、个数和次序决定调用的是那个方法。这里并不将方法返回的类型作为判断重载的标准,具有其它相同标志但返回值类型不同的方法是不允许的;动态多态性体现的是方法的重写
,是子类与父类的两个方法间的关系,它们的方法名、参数列表和返回类型完全相同,只是方法体的实现不同。在运行阶段,凡是使用父类对象的地方,都可以用子类对象作为父类对象使用,根据子类的对象的引用调用子类或父类的方法。方法的重载与重写的说明如图所示,然后进一步通过【清单4-5】和【清单4-6】的代码说明其实现。
方法重载:
方法重写:
清单 4-5:方法重载显示时间,体现静态多态性
1: public class Time {
2: int hour;
3: int minute;
4: int second;
5: Time(){
6: hour=0;
7: minute=0;
8: second=0;
9: }
10: Time(int hr, int min, int sec){
11: hour=hr;
12: minute=min;
13: second=sec;
14: }
15: void setTime(int hr,int min,int sec){
16: hour=hr;
17: minute=min;
18: second=sec;
19: }
20: void setTime(int min){
21: minute=min;
22: }
23: void setTime(int min,int sec){
24: minute=min;
25: second=sec;
26: }
27: void display(){
28: System.out.println(hour+":"+minute+":"+second);
29: }
30: public static void main(String[] args) {
31: Time time=new Time(); //Time 对象
32: time.setTime(12,30,50); //设置hour,minute,second
33: time.display(); //按 hh:mm:ss格式显示
34: time.setTime(35); //设置过12:00的35分钟
35: time.display();
36: time.setTime(42, 31); //设置minute与second
37: time.display();
38: }
39: }
以上程序的显示结果为:
12:30:50
12:35:50
12:42:31
学习者已经看到,Time
类有两个构造方法,三个setTime
方法,它们的不同表现在参数列表的数量和次序。静态多态性指的是一个实体同时以不同的物理形式存在,从前面程序的方法重载,可看到,静态多态通过一个方法的不同存在形式来展示。
静态多态性对程序运行来说更有效率,而动态多态性更具灵活性,它体现的是在运行时期,而不是在编译时期。
清单 4-6:方法重写,体现动态多态性
1: import java.io.*;
2: class Person{
3: protected String name;
4: protected String dateOfBirth;
5: protected String city;
6: protected String phoneNo;
7: InputStreamReader keyreader;
8: BufferedReader bfreader;
9: public Person(){
10: keyreader=new InputStreamReader(System.in);
11: bfreader=new BufferedReader(keyreader);
12: }
13: public void accept(){
14: try{
15: System.out.println("姓名 : ");
16: name=bfreader.readLine();
17: System.out.println("出生年月 : ");
18: dateOfBirth=bfreader.readLine();
19: System.out.println("所在城市 : ");
20: city=bfreader.readLine();
21: System.out.println("电话号码 : ");
22: phoneNo=bfreader.readLine();
23: }
24: catch(IOException ex){
25: System.out.println("读入数据错误:"+ex.toString());
26: }
27: }
28: public void display(){
29: System.out.println( "姓名 : " + name);
30: System.out.println( "出生年月 : " + dateOfBirth);
31: System.out.println( "所在城市 : " + city);
32: System.out.println( "电话号码 : " + phoneNo);
33: }
34: }
35:
36: class Customer extends Person{
37: private String billingAddress;
38: private float amountOutstanding;
39: public void accept(){
40: System.out.println( "输入消费者情况 :");
41: super.accept();
42: try{
43: System.out.println("消费者详细地址 : ");
44: billingAddress=bfreader.readLine();
45: System.out.println("付款总数 : ");
46: amountOutstanding=Float.valueOf(bfreader.readLine()).floatValue();
47: }
48: catch(IOException ex){
49: System.out.println("读入数据错误:"+ex.toString());
50: }
51: }
52: public void display(){
53: System.out.println("输入消费者详细情况 ");
54: super.display();
55: System.out.println("详细地址 : " + billingAddress);
56: System.out.println("消费量: " + amountOutstanding );
57: }
58: }
59:
60: class Dealer extends Person{
61: private String shopAddress;
62: private int numSold;
63: public void accept(){
64: System.out.println("输入经销商情况 :");
65: super.accept();
66: try{
67: System.out.println( "商店地址 : ");
68: shopAddress=bfreader.readLine();
69: System.out.println( "出售数量 : ");
70: numSold=Integer.valueOf(bfreader.readLine()).intValue();
71: }
72: catch(IOException ex){
73: System.out.println("读入数据错误:"+ex.toString());
74: }
75: }
76: public void display(){
77: System.out.println( "经销商详细情况 ");
78: super.display();
79: System.out.println( "商店地址 : " + shopAddress);
80: System.out.println( "已出售数量 : " + numSold );
81: }
82: }
83:
84: public class AcceptInformation {
85: public static void main(String[] args) {
86: Person person;
87: person= new Customer();
88: person.accept();
89: person.display();
90: person = new Dealer();
91: person.accept();
92: person.display();
93: }
94: }
父类Person
的方法accept()
与display()
,子类Customer
和Dealer
分别对其进行了重写,输入和显示各自的特定信息。
在聊天系统中,定义一个抽象类DushiConnection
,这个类包含了常见的数据字段和开放所需要的数据流的方法,使其作为父类,声明扩展子类DushiClientConnection
用于客户端的连接,声明扩展子类DushiServerConnection
用于服务器端的连接。父类和子类的简化声明如下:
public abstract class DushiConnection extends Thread {
...
}
public class DushiClientConnection extends DushiConnection{
...
}
public class DushiServerConnection extends DushiConnection{
...
}
在DushiConnection
抽象类中,由于其继承了Thread
类,在重写run()
方法时,将其声明为抽象方法
。如图所示的UML中,抽象方法显示为斜体
。然后在子类DushiServerConnection
和DushiClientConnection
中重写了run()方法。在这里的UML中的带封闭三角形空箭头的实线这样的图素符号等价于Java语言中extends
关键词。
清单 4-7:DushiConnection.java
package cn.ischoolcode.chat;
import java.net.*;
import java.util.*;
import java.io.*;
/**
* 一个抽象类,用于从客户机或服务器的连接。作为
* DushiServerConnection与DushiClientConnection类的超类(父类) ,
* 这个类包含了常见的数据属性和对所需要的数据流开放的方法。
*
* @author zcj
*/
public abstract class DushiConnection extends Thread {
protected double protocolVersion = 2.2; // By default
protected ThreadGroup threadGroup = null;
protected Socket socket;
protected DataInputStream iStream;
protected DataOutputStream oStream;
protected boolean online = false;
protected Hashtable fileTransfers = new Hashtable();
protected Hashtable imageTransfers = new Hashtable();
protected long lastSendTime = System.currentTimeMillis();
protected long lastReceiveTime = lastSendTime;
protected WatchDog watchDog = null;
public DushiConnection(ThreadGroup tg, String name, Socket s)
throws IOException {
super(tg, name);
threadGroup = tg;
socket = s;
// Set up the streams
iStream = new DataInputStream(socket.getInputStream());
oStream = new DataOutputStream(socket.getOutputStream());
online = true;
}
public abstract void run(); // 抽象方法
private void finishSend() {
lastSendTime = System.currentTimeMillis();
yield();
}
/**
* 向另一端发送所需协议的消息.
*/
protected void sendProtocol(double version) {
if (!online)
return;
try {
synchronized (oStream) {
oStream.writeShort(DushiCommand.SETPROTO);
oStream.writeDouble(version);
}
finishSend();
} catch (IOException e) {
lostConnection();
}
}
/**
* 向另一端的连接发送ping命令,让它知道我们仍然处于
* 连接状态。
*/
protected void sendPing() {
if (!online)
return;
try {
synchronized (oStream) {
oStream.writeShort(DushiCommand.PING);
oStream.flush();
}
finishSend();
} catch (IOException e) {
lostConnection();
}
}
/**
* 向另一端发送连接通知,让它知道用户
* 正在连接。
*/
protected void sendConnect(String userName) {
if (!online)
return;
try {
synchronized (oStream) {
oStream.writeShort(DushiCommand.CONNECT);
oStream.writeUTF(userName);
}
finishSend();
} catch (IOException e) {
lostConnection();
}
}
/**
* 发送有关用户的信息。
*/
protected void sendUserInfo(int id, String name, String password,
boolean encrypted, String email, String additional, String language) {
if (!online)
return;
try {
synchronized (oStream) {
oStream.writeShort(DushiCommand.USERINFO);
oStream.writeInt(id);
oStream.writeUTF(name);
oStream.writeUTF(password);
oStream.writeBoolean(encrypted);
if (protocolVersion >= 2.2)
oStream.writeUTF(email);
oStream.writeUTF(additional);
if (protocolVersion >= 2.1)
oStream.writeUTF(language);
}
finishSend();
} catch (IOException e) {
lostConnection();
}
}
/**
* 该方法将导致来自服务器的消息出现在用户屏幕
* 上的对话框窗口中。只能通过服务器连接发送;
* 服务器将不接受它以另一种方式进行。
*/
protected void sendServerMessage(String data) {
if (!online)
return;
try {
synchronized (oStream) {
oStream.writeShort(DushiCommand.SERVERMESS);
oStream.writeUTF(data);
}
finishSend();
} catch (IOException e) {
lostConnection();
}
}
/**
* 发送一个用户(可能是发送者本人)正在断开连接的广播。
* 连接的广播。
*/
protected void sendDisconnect(int userId, String mess) {
if (!online)
return;
try {
synchronized (oStream) {
oStream.writeShort(DushiCommand.DISCONNECT);
oStream.writeInt(userId);
oStream.writeUTF(mess);
}
finishSend();
} catch (IOException e) {
lostConnection();
}
}
/**
* 发送“房间列表”请求。如果房间向量为空,那么这是
* 来自客户端的请求,要求服务器发送列表。
* 否则,就是服务器发送列表。
*/
protected void sendRoomList(Vector rooms) {
DushiChatRoom room = null;
if (!online)
return;
try {
synchronized (oStream) {
oStream.writeShort(DushiCommand.ROOMLIST);
if (rooms == null) {
//寻问列表,而不是提供列表
oStream.writeShort(0);
} else {
oStream.writeShort(rooms.size());
for (int count1 = 0; count1 < rooms.size(); count1++) {
room = (DushiChatRoom) rooms.elementAt(count1);
oStream.writeUTF(room.name);
oStream.writeUTF(room.creatorName);
oStream.writeBoolean(room.priv);
// 是否邀请用户?
if (room.priv)
oStream.writeBoolean(room.invitedUsers
.contains(this));
else
oStream.writeBoolean(true);
oStream.writeInt(room.users.size());
for (int count2 = 0; count2 < room.users.size(); count2++) {
DushiServerConnection tmpConn = (DushiServerConnection) room.users
.elementAt(count2);
oStream.writeUTF(tmpConn.user.name);
}
}
}
}
finishSend();
} catch (IOException e) {
lostConnection();
}
}
/**
* 从一个用户向另一个用户发送聊天室邀请。
*/
protected void sendInvite(int fromId, String roomName, int toId) {
if (!online)
return;
try {
synchronized (oStream) {
oStream.writeShort(DushiCommand.INVITE);
oStream.writeInt(fromId);
oStream.writeUTF(roomName);
oStream.writeInt(toId);
}
finishSend();
} catch (IOException e) {
lostConnection();
}
}
/**
* 发送用户正在从一个聊天室移动到另一个聊天室内的通知。
*/
protected void sendEnterRoom(int fromId, String roomName, boolean priv,
String password, boolean encrypted) {
if (!online)
return;
try {
synchronized (oStream) {
oStream.writeShort(DushiCommand.ENTERROOM);
oStream.writeInt(fromId);
oStream.writeUTF(roomName);
oStream.writeBoolean(priv);
oStream.writeUTF(password);
oStream.writeBoolean(encrypted);
}
finishSend();
} catch (IOException e) {
lostConnection();
}
}
/**
* 发送用户正在从聊天室启动的通知。
*/
protected void sendBootUser(int fromId, String roomName, int toId) {
if (!online)
return;
try {
synchronized (oStream) {
oStream.writeShort(DushiCommand.BOOTUSER);
oStream.writeInt(fromId);
oStream.writeUTF(roomName);
oStream.writeInt(toId);
}
finishSend();
} catch (IOException e) {
lostConnection();
}
}
/**
* 发送用户已被禁止进入聊天室的通知。
*/
protected void sendBanUser(int fromId, String roomName, int toId) {
if (!online)
return;
try {
synchronized (oStream) {
oStream.writeShort(DushiCommand.BANUSER);
oStream.writeInt(fromId);
oStream.writeUTF(roomName);
oStream.writeInt(toId);
}
finishSend();
} catch (IOException e) {
lostConnection();
}
}
/**
* 发送通知,表示以前被禁止进入聊天室的用户
* 现在可以使用了。
*/
protected void sendAllowUser(int fromId, String roomName, int toId) {
if (!online)
return;
try {
synchronized (oStream) {
oStream.writeShort(DushiCommand.ALLOWUSER);
oStream.writeInt(fromId);
oStream.writeUTF(roomName);
oStream.writeInt(toId);
}
finishSend();
} catch (IOException e) {
lostConnection();
}
}
/**
* 向请求的收件人发送用户正在进行某些活动
* (如打字或绘图)的通知。
*/
protected void sendActivity(int fromId, short activity, int[] recipients) {
if (!online)
return;
try {
synchronized (oStream) {
oStream.writeShort(DushiCommand.ACTIVITY);
oStream.writeInt(fromId);
oStream.writeShort(activity);
oStream.writeInt(recipients.length);
for (int count = 0; count < recipients.length; count++)
oStream.writeInt(recipients[count]);
}
finishSend();
} catch (IOException e) {
lostConnection();
}
}
/**
* 向请求的收件人发送聊天文本。
*/
protected void sendChatText(int fromId, boolean priv, String data,
int[] recipients) {
if (!online)
return;
try {
synchronized (oStream) {
oStream.writeShort(DushiCommand.CHATTEXT);
oStream.writeInt(fromId);
oStream.writeBoolean(priv);
oStream.writeShort(0); // No colour
oStream.writeUTF(data);
oStream.writeInt(recipients.length);
for (int count = 0; count < recipients.length; count++)
oStream.writeInt(recipients[count]);
}
finishSend();
} catch (IOException e) {
lostConnection();
}
}
/**
* 从服务器发送聊天文本,以显示在收件人的
* 聊天窗口中。
*/
protected void sendServerText(String data) {
if (!online)
return;
try {
synchronized (oStream) {
oStream.writeShort(DushiCommand.CHATTEXT);
oStream.writeInt(0); // From user "nobody"
oStream.writeBoolean(false); // Not private
oStream.writeShort(0); // No colour
oStream.writeUTF(data);
oStream.writeInt(0); // Empty recipient list
}
finishSend();
} catch (IOException e) {
lostConnection();
}
}
/**
* 将绘制线发送给请求的收件人。
*/
protected void sendLine(int fromId, short colour, short startX,
short startY, short endX, short endY, short thick, int[] recipients) {
if (!online)
return;
try {
synchronized (oStream) {
oStream.writeShort(DushiCommand.LINE);
oStream.writeInt(fromId);
oStream.writeShort(colour);
oStream.writeShort(startX);
oStream.writeShort(startY);
oStream.writeShort(endX);
oStream.writeShort(endY);
oStream.writeShort(thick);
oStream.writeInt(recipients.length);
for (int count = 0; count < recipients.length; count++)
oStream.writeInt(recipients[count]);
}
finishSend();
} catch (IOException e) {
lostConnection();
}
}
/**
* 将绘图多边形发送给请求的收件人。
*/
protected void sendPoly(short kind, int fromId, short colour, short x,
short y, short width, short height, short thick, boolean fill,
int[] recipients) {
if (!online)
return;
try {
synchronized (oStream) {
oStream.writeShort(kind);
oStream.writeInt(fromId);
oStream.writeShort(colour);
oStream.writeShort(x);
oStream.writeShort(y);
oStream.writeShort(width);
oStream.writeShort(height);
oStream.writeShort(thick);
oStream.writeBoolean(fill);
oStream.writeInt(recipients.length);
for (int count = 0; count < recipients.length; count++)
oStream.writeInt(recipients[count]);
}
finishSend();
} catch (IOException e) {
lostConnection();
}
}
/**
* 将绘图文本(即画布上的图形文本)发送给请求的
* 收件人。
*/
protected void sendDrawText(int fromId, short colour, short x, short y,
short type, short attribs, short size, String text, int[] recipients) {
if (!online)
return;
try {
synchronized (oStream) {
oStream.writeShort(DushiCommand.DRAWTEXT);
oStream.writeInt(fromId);
oStream.writeShort(colour);
oStream.writeShort(x);
oStream.writeShort(y);
oStream.writeShort(type);
oStream.writeShort(attribs);
oStream.writeShort(size);
oStream.writeUTF(text);
oStream.writeInt(recipients.length);
for (int count = 0; count < recipients.length; count++)
oStream.writeInt(recipients[count]);
}
finishSend();
} catch (IOException e) {
lostConnection();
}
}
/**
* 将图形图像发送给请求的收件人,以便在绘图
* 画布上显示。
*/
protected void sendImage(int fromId, String imageName, short xCoord,
short yCoord, int totalSize, int totalBlocks, int blockSize,
byte[] data, int[] recipients) {
// 将另一个用户粘贴的图像发送给我们的客户
if (!online)
return;
try {
synchronized (oStream) {
oStream.writeShort(DushiCommand.IMAGE);
oStream.writeInt(fromId);
if (protocolVersion >= 2.2)
oStream.writeUTF(imageName);
oStream.writeShort(xCoord);
oStream.writeShort(yCoord);
if (protocolVersion >= 2.2) {
// 图像数据以单独的IMAGEBLOCK格式发送
// 命令。在这里,我们只说其中有多少
// 将要发送。
oStream.writeInt(totalSize);
oStream.writeInt(totalBlocks);
oStream.writeInt(blockSize);
} else {
// 发送实际图像数据。
oStream.writeInt(data.length);
oStream.write(data, 0, data.length);
}
oStream.writeInt(recipients.length);
for (int count = 0; count < recipients.length; count++)
oStream.writeInt(recipients[count]);
}
finishSend();
} catch (IOException e) {
lostConnection();
}
}
/**
* 向请求的收件人发送用户已清除绘图画布的
* 通知。
*/
protected void sendClearCanv(int fromId, int[] recipients) {
if (!online)
return;
try {
synchronized (oStream) {
oStream.writeShort(DushiCommand.CLEARCANV);
oStream.writeInt(fromId);
oStream.writeInt(recipients.length);
for (int count = 0; count < recipients.length; count++)
oStream.writeInt(recipients[count]);
}
finishSend();
} catch (IOException e) {
lostConnection();
}
}
/**
* 向请求的收件人发送寻呼请求。
*/
protected void sendPageUser(int fromId, int[] recipients) {
if (!online)
return;
try {
synchronized (oStream) {
oStream.writeShort(DushiCommand.PAGEUSER);
oStream.writeInt(fromId);
oStream.writeInt(recipients.length);
for (int count = 0; count < recipients.length; count++)
oStream.writeInt(recipients[count]);
}
finishSend();
} catch (IOException e) {
lostConnection();
}
}
/**
* 向请求的收件人发送即时消息请求。
*/
protected void sendInstantMess(int fromId, int toId, String message) {
if (!online)
return;
try {
synchronized (oStream) {
oStream.writeShort(DushiCommand.INSTANTMESS);
oStream.writeInt(fromId);
oStream.writeInt(toId); // Our id
oStream.writeUTF(message);
}
finishSend();
} catch (IOException e) {
lostConnection();
}
}
/**
* 将所有存储的消息发送给请求的收件人。
* 这只会由服务器发送到客户端。
*/
protected void sendStoredMess(Vector messages) {
if (!online)
return;
try {
synchronized (oStream) {
oStream.writeShort(DushiCommand.STOREDMESS);
oStream.writeShort(messages.size());
for (int count = 0; count < messages.size(); count++) {
DushiMessage message = (DushiMessage) messages
.elementAt(count);
oStream.writeUTF(message.from);
oStream.writeUTF(message.text);
}
}
finishSend();
} catch (IOException e) {
lostConnection();
}
}
/**
* 发送要为请求的用户名保存的存储消息。
*/
protected void sendLeaveMess(int fromId, String toName, String message) {
if (!online)
return;
try {
synchronized (oStream) {
oStream.writeShort(DushiCommand.LEAVEMESS);
oStream.writeInt(fromId);
oStream.writeUTF(toName);
oStream.writeUTF(message);
}
finishSend();
} catch (IOException e) {
lostConnection();
}
}
/**
* 从客户端向服务器发送请求以检索存储的
* 消息。
*/
protected void sendReadMess(int fromId) {
if (!online)
return;
try {
synchronized (oStream) {
oStream.writeShort(DushiCommand.READMESS);
oStream.writeInt(fromId);
}
finishSend();
} catch (IOException e) {
lostConnection();
}
}
/**
* 向请求的收件人发送错误通知。
*/
protected void sendError(int fromId, short code, int[] recipients) {
if (!online)
return;
try {
synchronized (oStream) {
oStream.writeShort(DushiCommand.ERROR);
oStream.writeInt(fromId);
oStream.writeShort(code);
oStream.writeInt(recipients.length);
for (int count = 0; count < recipients.length; count++)
oStream.writeInt(recipients[count]);
}
finishSend();
} catch (IOException e) {
lostConnection();
}
}
/**
* 使用提供的参数和数据从客户端向服务器
* 发送“发送电子邮件”请求。
*/
protected void sendSendEmail(int fromId, String mxHost, String fromAddress,
String toAddress, String subject, String text) {
if (!online)
return;
try {
synchronized (oStream) {
oStream.writeShort(DushiCommand.SENDEMAIL);
oStream.writeInt(fromId);
oStream.writeUTF(mxHost);
oStream.writeUTF(fromAddress);
oStream.writeUTF(toAddress);
oStream.writeUTF(subject);
oStream.writeUTF(text);
}
finishSend();
} catch (IOException e) {
lostConnection();
}
}
/*
* 发送连接的一端想要发送文件的通知。
* 当用户希望将文件传输到收件人列表时,
* 客户端将其发送到服务器,服务器将其
* 转发给这些收件人以接受或拒绝。
*/
protected void sendFile(int fromId, String fileName, long totalSize,
int totalBlocks, int blockSize, int expireMins, int[] recipients) {
if (!online)
return;
try {
synchronized (oStream) {
oStream.writeShort(DushiCommand.FILE);
oStream.writeInt(fromId);
oStream.writeUTF(fileName);
oStream.writeLong(totalSize);
oStream.writeInt(totalBlocks);
oStream.writeInt(blockSize);
oStream.writeInt(expireMins);
oStream.writeInt(recipients.length);
for (int count = 0; count < recipients.length; count++)
oStream.writeInt(recipients[count]);
}
finishSend();
} catch (IOException e) {
lostConnection();
}
}
/**
* 发送有关客户端是否接受文件传输邀请
* 的通知。
*/
protected void sendFileAccept(int fromId, int toId, String fileName,
boolean accept) {
if (!online)
return;
try {
synchronized (oStream) {
oStream.writeShort(DushiCommand.FILEACCEPT);
oStream.writeInt(fromId);
oStream.writeInt(toId);
oStream.writeUTF(fileName);
oStream.writeBoolean(accept);
}
finishSend();
} catch (IOException e) {
lostConnection();
}
}
/**
* 发送文件传输的文件块数据包。
*/
protected void sendFileBlock(int fromId, String fileName, int blockNumber,
byte[] data) {
if (!online)
return;
try {
synchronized (oStream) {
oStream.writeShort(DushiCommand.FILEBLOCK);
oStream.writeInt(fromId);
oStream.writeUTF(fileName);
oStream.writeInt(blockNumber);
oStream.writeInt(data.length);
oStream.write(data, 0, data.length);
}
finishSend();
} catch (IOException e) {
lostConnection();
}
}
/**
* 发送文件传输的取消通知。
*/
protected void sendFileCancel(int fromId, String fileName) {
if (!online)
return;
try {
synchronized (oStream) {
oStream.writeShort(DushiCommand.FILECANCEL);
oStream.writeInt(fromId);
oStream.writeUTF(fileName);
}
finishSend();
} catch (IOException e) {
lostConnection();
}
}
/**
* 发送图像传输的文件块数据包。
*/
protected void sendImageBlock(int fromId, String imageName,
int blockNumber, byte[] data) {
if (!online)
return;
try {
synchronized (oStream) {
oStream.writeShort(DushiCommand.IMAGEBLOCK);
oStream.writeInt(fromId);
oStream.writeUTF(imageName);
oStream.writeInt(blockNumber);
oStream.writeInt(data.length);
oStream.write(data, 0, data.length);
}
finishSend();
} catch (IOException e) {
lostConnection();
}
}
protected abstract void lostConnection(); // 抽象方法
protected synchronized void shutdown() {
online = false;
// 关闭数据流
try {
//不要同步这样做,因为阅读器会在那里阻塞,等待数据。
iStream.close();
} catch (IOException e) {
}
try {
synchronized (oStream) {
oStream.flush();
oStream.close();
}
} catch (IOException e) {
}
// 关闭套接字
try {
socket.close();
} catch (IOException e) {
}
// 如果有任何正在进行的图像传输,请停止它们
Enumeration e = imageTransfers.elements();
while (e.hasMoreElements()) {
DushiImageTransfer t = (DushiImageTransfer) e.nextElement();
t.stop = true;
imageTransfers.remove(t.imageName);
}
// 强制垃圾收集,因为某些连接似乎还保持
System.gc();
return;
}
/**
* 用于监视连接状态的看门狗线程。我们的连接将启动此看门狗,
* 以确保我们与另一端的连接保持联系,如果有一段时间没有任
* 何活动,则发送ping。
*/
class WatchDog extends Thread {
private static final long pingEvery = 2000; // 2 seconds.
private static final long lostAfter = 15000; // 15 seconds.
private DushiConnection connection;
public WatchDog(DushiConnection c) {
super(c.threadGroup, "Dushi Chat connection watchdog");
connection = c;
start();
}
public void run() {
while (online) {
long currTime = System.currentTimeMillis();
if (currTime >= (connection.lastSendTime + pingEvery)) {
// 发送ping以保持连接有效。
sendPing();
}
if (currTime > (connection.lastReceiveTime + lostAfter)) {
// 我们似乎失去了联系。
lostConnection();
}
try {
sleep(10);
} catch (InterruptedException e) {
}
}
}
}
}
清单 4-8: DushiClientConnection.java
清单 4-9:DushiServerConnection.java
在子任务1中,已进行了连接类的继承设计。在此子任务中,另外再进行聊天系统的文件传输设计,同样,首先定义一个抽象类DushiFileTransfer,这个类包含了常见的数据项和用于读取和写入文件的方法,使其作为父类,声明扩展子类DushiClientFileTransfer用于客户端的文件传输,声明扩展子类DushiServerFileTransfer用于服务器端的文件传输。继承关系如图所示:
在图中所示的DushiFileTransfer抽象类为斜体,当然斜体的run()方法为抽象方法,在子类的方法栏中都进行了重写,这也是继承抽象类所必须的。
父类和子类的简化声明如下:
public abstract class DushiFileTransfer extends Thread {
...
}
public class DushiClientFileTransfer extends DushiFileTransfer{
...
}
public class DushiServerFileTransfer extends DushiFileTransfer {
...
}
清单 4-10:DushiFileTransfer.java
清单 4-11:DushiClientFileTransfer.java
清单 4-12:DushiServerFileTransfer .java
当在其它类的对象中调用连接对象或文件传输对象时,可以设计如下代码:
DushiConnection connection = null;
DushiFileTransfer transfer = null;
先进行连接类型和文件传输类型的引用变量声明,然后:
connection = new DushiServerConnection();
transfer = new DushiServerFileTransfer();
或者:
connection = new DushiClientConnection();
transfer = new DushiClientFileTransfer();
接着,执行下列代码:
connection.start();
transfer.start();
这时将分别启动服务器端的连接对象和文件传输对象的线程,并运行run()方法;或者启动客户端的连接对象和文件传输对象的线程,并运行run()方法。到底是运行服务器端对象还是客户端对象,这是由运行时多态性决定的。
static关键字声明与类相关的成员(属性、方法和嵌套类/内部类)而非类的实例,虽然在前面的知识点中也有描述,在此将其抽取出来,作进一步的讲述。
⑴ 类属性
有时需要有一个在类的所有实例中共享的变量,可以使用该变量作为实例间通信的基础,或者已跟踪已创建的实例数。为了实现这一共享效果,利用static关键字来标记变量,将其称为类变量或者类属性,以区分于未被共享的成员变量。样例代码如下:
1:public class Count {
2: private int serialNumber;
3: public static int counter = 0;
4: public Count(){
5: counter++;
6: serialNumber = counter;
7: }
8:}
以上代码为创建的每个对象分配了一个唯一的序列号,从1开始向上递增,变量counter在所有实例间共享,当一个对象的构造方法增加counter的值时,下一个创建的对象就可接收到这个增加的值。static变量在某些方面与c
语言中的全局变量类似,但Java语言中没有全局变量,而static变量是一个可以被类的任何实例访问的变量。若static变量没有被标记为private,则可从类外访问它,通过类名直接引用,不需要实例化为对象。
⑵ 类方法
在没有特定对象的实例时,有时需要访问程序代码。使用static关键字标记的方法可以实现这样的功能,称之为类方法。样例代码如下:
1:public class Count2 {
2: private int serialNumber;
3: public static int counter = 0;
4: public Count2(){
5: counter++;
6: serialNumber = counter;
7: }
8: public static int getTotalCount(){
9: return counter;
10: }
11: }
以上代码中实现的static方法,不需要将类实例化为对象就可以通过类名调用,因此没有this
值,除了方法的参数、方法内的局部变量以及static变量外,static方法不能访问任何变量。这是因为非static属性绑定到一个实例对象上,只有通过实例引用才能访问。
使用static方法注意事项:
⑶ 静态初始化
一个类可以在静态块中(而不是标准方法体内)包含代码。静态块代码仅在加载类时执行一次。如果类中包含多个静态块,将按照在类中出现的顺序执行它们。样例代码如下:
1:public class Count3 {
2: public static int counter;
3: static{
4: counter = Integer.getInteger("myCount").intValue();
5: }
6: public static void main(String args[]){
7: System.out.println("计数是:"+ Count3.counter);
8: }
9 }
以上代码的第4行使用Integer类的静态方法getInteger(),它返回代表系统属性值的Integer对象,再通过对象的intValue()方法返回一个int类型的值。在命令行上使用-D选项设置系统属性。执行结果如下:
java –D myCount=35 Count3
计数是:35
在此将对final类、final方法和final 变量作进一步描述。
⑴ final类
Java语言允许对类使用关键字final,这样,类就不能被子类化。例如,java.lang.String类就是一个final类。对其不能有继承,这是出于安全的考虑,需要确保方法引用一个字符串时,它肯定是一个String类的字符串,而不是String修改后可能已经改变的子类的字符串。
⑵ final方法
标记为final的方法不能被重写。出于安全的考虑,如果方法的实现不应改变,而且对对象的一致性状态很重要。声明为final的方法可被编译器最优化。
⑶ final变量
如果变量被标记为final,则该变量不能被重新赋值。如果将一个引用类型的变量(任意类类型)标记为final,则该引用不能更改,不能再引用任何其他对象。不过,可以改变对象的内容,因为只有引用本身是final。 空的final变量是指声明时没有初始化的final变量。初始化被延迟。一个空的final实例变量应该在构造方法中赋值,但它只能设置一次。空final局部变量可以随时在方法体内设置,也只能被设置一次。
在编程中使用一组有限的符号名称来表示属性值,例如,要表示扑克牌的信息Suit,可以创建一组符号:SPADES、HEARTS、CLUBS和DIAMONDS。将这样的一组符号称为枚举类型。这里需要注意的是:不要将术语枚举类型与java.util.Enumeration类相混淆,它们完全不相关。
样例代码如下:
1:package cards;
2: public enum Suit {
3: SPADES ("黑桃"),
4: HEARTS ("红心"),
5: CLUBS ("梅花"),
6: DIAMONDS ("方块");
7: private final String name;
8: private Suit(String name){
9: this.name=name;
10: }
11: public String getName(){
12: return name;
13: }
14: }
以上代码显示了一套扑克牌的枚举类型。可以将Suit类型作为带有一组值的类,并在类型定义中列出这些值的符号名称。在以下样例代码中将使用Suit类型的属性数据。
1: package cards;
2:public class PlayingCard {
3: private Suit suit;
4: private int rank;
5: public PlayingCard(Suit suit,int rank){
6: this.suit = suit;
7: this.rank = rank;
8: }
9: public Suit getSuit(){
10: return suit;
11: }
12: public int getRank(){
13: return rank;
14: }
15: public static void main(String args[]){
16: PlayingCard card = new PlayingCard(Suit.SPADES,2);
17: System.out.println("这张牌是"+card.getSuit().getName()+card.rank);
18: }
19:}
如果必须访问类的静态成员,则有必要限制引用以及产生它们的类,对于枚举类型也是一样。在上个知识点的PlayingCard类中代码的第16行,显示使用点符号Suit.SPADES访问Suit类型的SPADES值,就是这种情况。
静态导入功能可以不受限制地访问静态成员,而不必使用类名限制它们。样例代码如下:
1: package test;
2: import static cards.Suit.*;
3: import cards.PlayingCard;
4: public class TestPlayingCard {
5: public static void main(String args[]) {
6: PlayingCard card = new PlayingCard(SPADES, 2);
7: System.out.println("这张牌是"+card.getSuit().getName()+card.getRank());
8: }
9: }
以上代码第2行使用静态导入,告诉编译器,当编译程序时,在符号表中要包括枚举类型值(或该枚举类型的任何静态成员)。因此,第6行使用SPADES而不用加Suit. 命名空间前缀。
博文最后更新时间: