第3章 带有类的语言:Java面向对象程序设计
3.1 设计一个Java类
3.2 方法的魔方
Java中的方法与大部分过程化语言中的函数相似,一个理想的方法是设计用来执行一项单一的任务。Java方法包括
q 可见性(public private protected)
q 可能有的任何修饰符(static final)
q 返回类型(8种基本类型之一,一个对象或void)
q 方法名(通常是一个行为动词)
q 一个参数列表(0个或多个传给方法的参数)
q 方法主体(0个或多个被花括号包起来的语句)
C++注解:把Java对象作为引用(对,Java里面的对象名均是引用,是地址,是指针,可笑的是,很多书里说:Java没有指针,其实Java里要引用对象实例,都是用的指针。所以个人觉得,所谓没有指针,应该是说,Java不支持指针的操作,例如指针的加减运算)没有什么好处,也就是说,在Java中没有显式的指针类型。因此,在C++中点运算符(.)和指针访问运算符(->)之间没有明显的差别。所有的Java对象通过点运算符引用数据。应该用C++指针的方式来理解方法参数中的对象,如果一个对象直接在被调用的方法中改变,那么在整个程序过程中这个改变都会起作用。记住这个规则只适用于Java中的可变对象,不适用于Java中的原始类型和像String这样的不可变对象。
3.3 关于方法的更多话题
3.3.1 构造函数方法
构造函数方法,或者构造函数,是为特定对象分配内存时调用的方法。一般说来,我们把没有参数的构造函数称为默认构造函数。
我们把用同一类型对象做参数的构造函数称作复制构造函数。当程序员不想只是把多个对象指向同一个内存地址,而想创建一个对象的拷贝时,有时会用到这种类型的方法。例如,对Card类的复制构造函数就会如下所示:
public Card(Card other){
value=other.value;//完成所有属性的复制(这里假设Card类只有一个属性value)。
}
注意:记住,使用复制构造函数与使用赋值操作符不同。因此,下面的语句不等价:
Card c2=new Card(c1);//c1和c2不是一个对象,但各属性值均相同
Card c2=c1;//c1和c2是同一个对象。
简单地把一个Card对象等价地赋给另外一个Card对象会使它们指向相同的物理对象。这样当一个对象的状态改变时,另外一个也改变。使用复制构造函数,可以创建与原始对象独立的拷贝。程序员可以任意修改其中一个,不会影响到另外一个。
Java中通常不是定义复制构造函数,而是实现clone()方法。要了解更多关于如何写clone()方法的信息,可以查阅第4章
3.3.2 访问方法
C++注解:正如所看到的,关键字new为新建的对象分配内存。C++用户肯定也使用过delete关键字来清除和销毁已分配的对象。Java不支持与delete等价的关键字,但是,在系统已经用尽所有可用内存时,Java虚拟机会进行所谓的垃圾收集。垃圾收集释放不再被引用或活动的对象的内存。因此,对于Java程序员来说,没有必要手工回收不用的内存。
3.3.3 类方法
static方法,或者叫类方法。是属于整个类而不是类的任何特定实例的方法。
3.3.4 “其他”方法
即不能归结于其他类别的方法。
3.4 继承
3.5 抽象类
抽象类通常也称为类工厂,这是因为我们能够很快地“制成”填充到不完整的父类方法中的子类。下面是与抽象类相关的其他的属性,它们很重要:
q 抽象类不能被直接实例化,这是因为它们的定义不是完整的,但是可以创建抽象对象的数组,只要每个成员是它的派生类之一的实例。
q 一个包含抽象方法的任意成员的类自己也必须声明为抽象的。抽象类可以包含带有已定义主体的方法,但是,只有不完整的方法才应当加abstract关键字的前缀。
q 从一个抽象类派生的类如果要实例化,就必须为每个在它的父抽象类中声明的方法定义方法体,否则,这个类必须声明为abstract,只有它的定义这些方法的子类才能被实例化。
3.6 类修饰符
C++注解:不要混淆C++中的const关键字和Java中的final关键字。C++被指责的一点就是,允许const以很多不同的方式使用,导致产生了不必要的副作用和含糊不清的代码。Java的开发者采用了final关键字来指示常量或不可变的值和定义。Java中也存在关键字const,但是它是为未来预留的保留字,目前没有任何含义。
3.7 接口
接口不是类?!(本人觉得这个提法有些道理,不过好象与我的理解不太一样,上网查了查,没找到明确说明接口不是类的文章,却找到了两篇谈类继承的,一篇叫<为什么继承是有害的>,另一篇叫<澄清Java语言中接口与继承的本质>深有启发。而且对于“继承主要是实现抽象”“抽象就是抽去像的部分”让人拍案叫绝。不过看完这两篇文章之后,自己还是觉得其实接口应该就是C++中的虚基类吧?所以,个人还是认为接口应该还是属于类,纯抽象类)
3.8 快捷地创建类
当我们需要完成某个工作的时候,我们通常都会创建一个类,然后实例化它来完成这项工作,可是有一些特殊的情况下,比如,这个类其实我们只会使用一次,它的代码根本不具备复用性,这种情况下,我们会使用匿名内部类的方式来处理。
要实现一个匿名内部类,所要做的就是尽快详细说明类的主体。也就是说,在用到的时候定义它。
考虑这个例子
import java.util.*;
class NameSortTest
{
public static void main(String[] args){
//创建一个字符串数组
String[] strArray={"Ward,Bill","Osbourne,Ozzy","Butlet,Geezer","Iommi,Tony"};
//打印未排序的字符串数组
System.out.println("排序前的字符串:");
for(int i=0;i<strArray.length;i++){
System.out.println(i+":"+strArray[i]);
}
Arrays.sort(strArray,new Comparator(){
//注意,这里就是大名鼎鼎的匿名类的内部了
public int compare(Object a,Object b){
//这个方法是用来制定规则,如何完成两个字符串之间怎么排序的
String s1=(String)a;
String s2=(String)b;
//从字符串中解析first name
s1=s1.substring(s1.indexOf(",")+1);
s2=s2.substring(s2.indexOf(",")+1);
//去掉开头和结尾的空格
s1=s1.trim();
s2=s2.trim();
//使用String的compareTo来比较
return s2.compareTo(s1);
}
});
System.out.println("\n排序后的字符串:");
for(int i=0;i<strArray.length;i++){
System.out.println(i+":"+strArray[i]);
}
}
}
以上代码可以说是关于匿名内部类的经典演示,在这些,我们需要对一个字符串数组进行排序,这个字符串数组里的每一个元素都是很规律的,名和姓,中间有逗号,排序其实很简单,但是我们希望的并非按开头字母顺序排列,而是希望按姓的字母(即逗号之后的字符串)排序。
这里的解决方法就是利用了Java的一个Arrays类里的一个方法sort(Object[],Comparator),这里的第二个参数就是排序规则制定的对象,我们使用了匿名内部类的形式,很好的实现我们期望的功能。
实际上,我们所作的是定义了一个没有名称的(即匿名的)类,它在另外一个类中实现了Comarator接口,这在只需要使用一次一个类的实例时非常有用。
3.9 包
所有的Java包都按照类型有机地分组,java.io包含有与输入输出相关的类,java.applet包含有与applet开发相关的类,java.util包提供了一般的工具类。
可遵循这种模式来建立自己的包。
什么是包?包其实就是一个文件夹,用它来组织自己的代码,所以包的概念就如同C++和C#中的命名空间一样,是对代码的一种组织形式,让自己的代码有组织。
要创建一个包,只需做两件事。首先,需要在源文件列表的顶端包含进package语句,在import语句的前面输入。
创建一个包的第二步就是把已编译的源文件复制到与包名匹配的目录下(个人觉得这种做法似乎会有一些副作用,目前还不敢完全肯定,不过以后我会专门用一篇文章来说明它的副作用的)。
3.10 总结
本章所学的是关于面向对象程序设计的一般概念。面向对象程序设计的思想很多,所以要把这些只看作一个简介。在以后的学习中会随时弥补这些差距。
把基类结构、继承以及抽象类的原理都记在脑海中。如果你还不是一个经验丰富的C++或Java程序员,掌握面向对象程序设计的细节也就不重要了,关键是要熟悉。本章中涉及的概念会在本书余下的部分中得到加强。
3.11 练习
1根据如何给对象分配内存来描述下面代码片段的不同:
//片段A
Card c1=new Card(21);
Card c2=c1;
//片段B
Card c1=new Card(21);
Card c2=new Card(c1);
2通过扩展Vector2d类来定义Vector3d类,一个向量包含3个维数。如果有需要可以把Vector2d类作为模板使用。对于奖励分值,实现计算两个Vector3d对象的叉和点乘积的操作(默然:我好象没有写Vector2d的代码,这题可以不做了吧?)。
3使用Java2文档,研究StringTokenizer类。这个类从哪里继承它的功能?它有一个父基类吗?默认的分隔字符是什么?写一个解析来自逗号分隔表元素的小测试程序。
4解释下面的代码不可能是一个假设的抽象Product类的有效实例的原因:
Product x=new Product(“Acme Inc.”,431);