本章目标:
阅读完本章后,大家应能根据需要定义并使用一个用户类,并能够根据需求说明这个用户类的特性。另外,还应该能够使用继承的方法创建子类,实现类的最大化使用。
8.1 定义并使用一个新类
传授新知
在前面几章中,我们遇到了不少用Java语言编写的程序,它们可以分为两类:
1) Java应用程序;
2) Java小应用程序。
它们都有一个共同的特点,那就是整个程序中只包含一个类。不同在于,这个唯一的类的定义不尽相同。
在Java应用程序中,形如:public class testBranch1
而在Java小应用程序中,则形如:public class VariableScope extends Applet
其实,在这些程序中都使用到了其它类。在小应用程序中,很明显,所有的类都是从Applet类中继承来的。在应用程序中,也使用了其它的类,不过不是十分明显,大家想想,System.out.println是从哪来的呢?它是从其它类中继承过来的,它包含在java.lang包中,无须指明,编译器能够自动处理。
Java语言的开发工具包(JDK)中就包含了许许多多的已开发定义的类,我们可以通过使用它们迅速地构建自己的程序。如果你能够熟练地使用它们就能够使用Java语言写出自己所需要的程序。而如果你想要成为Java语言的高手,就一定要学会自己定义类,以供今后的程序使用。下面我们一起来看一下下面这个例子。
实例说明
1.首先,我们使用文字编辑软件输入下面这个源程序。
源程序:Birthday.javapublic class Birthday
{
public String year;
public String month;
public String day;
public Birthday()
{
year=”0000”;
month=”00”;
day=”00”;
}
public Birthday(String y,String m,String d)
{
year=y;
month=m;
day=d;
}
public String getBirthday()
{
String fullbirthday=month+”/”+”/”+day+”/”+year;
return fullbirthday;
}
}
2.编译这个程序,如果顺利完成,将在当前目录下生成一个名为Birthday.class的文件;c:javastudy> javac Birthday.java
3.然后输入以下命令,运行这个程序:c:javastudy> java Birthday
执行这个程序,将输出一些信息,如下图所示:
图8-1 执行Birthday类的输出
Java语言责怪我们没有定义main方法。
4.接下来,我们再用文字编辑软件输入以下源程序。
源程序:useBirthday.javapublic class useBirthday
{
public static void main(String argv[])
{
Birthday birthday1=new Birthday();
Birthday birthday2=new Birthday("1949","10","01");
System.out.println(birthday1.getBirthday());
System.out.println(birthday2.getBirthday());
}
}
3.使用javac编译后,执行以下命令,运行这个程序:c:javastudy> java useBirthday
执行这个程序,将输出一些信息,如下图所示:
图8-2 执行useBirthday类的输出
传授新知
我们先来看一下第二个程序useBirthday.java,这是个Java应用程序,main方法中共有四条语句。前两条,看上去象是定义变量:Birthday birthday1=new Birthday();
Birthday birthday2=new Birthday("1949","10","01");
但是,我们从来没有向大家介绍过Birthday这种变量类型呀!是的,在Java语言中并没有Birthday这种变量类型。
可是这条语句在编译过程中并没有报错呀!这是因为,Java语言编译器在useBirtday.java程序所在目录中发现了包含Birthday类定义的Birthday类:Birthday.class。
也就是说,这两条语句定义了2个属于Birthday类的对象:birthday1和birthday2。从Birthday.java程序中,我们可以看到Birthday类包含三个字符型成员变量:public String year;
public String month;
public String day;
一些提示:
我们将Birthday称为类,而将birthday1和 birthday2称为对象。请大家一定要明明白白地知道其中原委。类是一类事物,而对象则是一个个体。
成员变量是一个形象的术语,表示这些变量属于这个对象,属于这个类。
同时,分别使用new Birthday()和new Birthday(“1949”,”10”,”01”)为它们赋初值。
1)Birthday birthday1=new Birthday();
在这一句中,调用的是Birthday类中的Birthday()方法,我们从Birthday.java中可以看到这个方法实现:public Birthday()
{
year=”0000”;
month=”00”;
day=”00”;
}
也就是说,它将对象birthday1的三个成员变量year,month和day分别赋予了初值“0000”,“00”和“00”。
2) Birthday birthday2=new Birthday("1949","10","01");
在这一条语句中,虽然方法名同是Birthday,但它带上了参数,所以它调用的是Birthday类中的Birthday(String y,String m,String d)方法。这个方法的实现是:public Birthday(String y,String m,String d)
{
year=y;
month=m;
day=d;
}
也就是将其所带的参数值,分别赋值给三个成员变量。在本例中,“1949”赋值给year,“10”赋值给month,“01”赋值给day。
这个程序中另两条语句则十分相似:System.out.println(birthday1.getBirthday());
System.out.println(birthday2.getBirthday());
很明显,这两条语句的用途是打印出birthday1和birthday2两个对象的一些信息。什么信息呢?我们可以发现这两条语句中都使用了Birthday类中的getBirthday方法。
一些提示:
大家注意观察在上面的语句是如何调用getBirthday方法的:birthday1.getBirthday()
前面是对象名,后面是方法名,中间用“.”连接。请记住这个“.”,它经常被使用。形象的说,它就表示“的”。即birthday1的getBirthday方法。同样,我们可以使用这样在方法来访问它的成员变量:birthday1.year,即对象birthday1的成员变量year。
我们就来看一下这个方法做了什么:public String getBirthday()
{
String fullbirthday=month+”/”+”/”+day+”/”+year;
return fullbirthday;
}
大家还记得“+”在字符串操作中的用途吧!对,我们在第6章中就跟大家说过,它是用来进行字符串合并的。我们通过month+”/”+”/”day+”/”+year语句,把birthday1和birthday2两个对象的三个成员变量组成了一个“月/日/年”的常用日期表示方法。
然后,getBirthday方法将这个用“月/日/年”表示法表示的生日日期返回(使用return方法)给System.out.println,这样,我们就得到了如图8-2的输出:00/00/0000
10/01/1949
通过上面的实例与讲解,我们可以得出创建一个新类的方法:
1) 构思所需类的成员变量和成员方法;
2) 用以下格式来编写类:类修饰符 class 类名
{
成员变量定义;
……
成员方法定义;
……
}
3) 使用javac编译这个类;
4) 然后我们就可以在其它类中使用这个类。
注意:
当你编译使用自定义类(如Birthday)的程序(如useBirthday.java)时,这个类(Birthday.class)必须与程序(useBirthday.java)位于相同的目录中,或者在系统变量CLASSPATH定义的目录中。否则编译时将找不到这个类,以致程序无法编译成功。
一些提示:
当你需要同时编译几个Java文件时,你可以使用一条命令来完成。例如:javac Birthday.java useBirthday.java
javac程序将一起编译所有这些文件。我们知道编译useBirthday.java时,需要用到Birthday.class(就是由Birthday.java编译生成的)。那么大家可能会以为,我们一定要将Birthday.java放在前面,以确保能够先编译生成Birthday.class。
其实并不需要,Java编译器能够智能地处理,你完全可以将useBirthday.java放在Birthday.java前面。
我们在Birthday类定义中,发现有两个与类Birthday同名的成员方法:public Birthday()
public Birthday(String y,String m,String d)
这种与类同名的成员方法称为构造器。每当使用new操作符创建属于这个类的对象时,就会执行构造器方法。
一个类可以有多个构造器,在使用new操作符时,执行哪个构造器则取决于它所还的参数。如:
1)Birthday birthday1=new Birthday();
在这条语句中,由于调用Birthday方法时,并未有任何参数,所以将调用Birthday类的构造器Birthday()。
2) Birthday birthday2=new Birthday("1949","10","01");
而在这条语句中,调用Birthday方法所带的参数与Birthday(String y,String m,String d)相吻合,所以将调用Birthdya类的构造器Birthday(String y,String m,String d)。因些,将完成以下赋值工作:
1)year=y; à year=“1949”
2)month=m; à month=“10”
3)day=d; à day=“01”
注意:
如果调用时的参数与所有构造器不吻合,将使Java编译器在编译程序时报错,使得无法编译成功。
自测练习
1) 如果我们定义一个名为car的类,那么存储这个类的源文件名可以是:________。
a. car.class b.任意取名 c.class.java
2) 一个类中,只能拥有一个构造器方法。_________
a. 不对 b.对
3) 一个构造器方法的方法名,___________________。
a.与类同相同 b.可以根据需要取名 c.可以有多个不同名的构造器
4) 在______________________时,将调用构造器方法。
a.定义一个属于该类的对象 b.调用new操作符
c.打印属于该类的对象信息
5) 当__________________时,编译使用自定义类的程序时将出错。
a. 自定义类放在系统环境变量PATH(CLASSPATH未指定该目录)指定的目录中
b.与程序在同一个目录下 c.放在系统环境变量CALSSPATH指定的目录中
6) 修改useBirthday.java程序中的语句:Birthday birthday1=new Birthday();
改为使用构造器Birthday(String y,String m,String d),使其结果相同。
____________________________________________________________________
7) 编写一个userName类,包含两个成员变量:firstname,lastname;并仿照Birthday类构建两个构造器方法,一个是带参数,一个是不带参数(将它们初始化为firstname,lastname);最后构建一个fullName方法,用来返回全名。
____________________________________________________________________
____________________________________________________________________
____________________________________________________________________
____________________________________________________________________
____________________________________________________________________
____________________________________________________________________
____________________________________________________________________
____________________________________________________________________
____________________________________________________________________
____________________________________________________________________
练习答案
1)c 在Java语言中,要求存放类的文件必须与类同名,甚至连大小写都要完全一样。而car.class是编译后生成的,存放源文件应该是car.java。
2)a 不对,在Java的类中,可以定义多个构造器方法。
3)a 在Java语言中,要求构造器方法名与类名相同。
4)b 调用new操作符时,才真正创建这个对象,这时调用构造器方法来构造它。这也是构造器名字的来历。
5)a Java编译器会在当前目录和CLASSPATH指定的目录中寻找相应的类,而不会在PATH路径中查找。
6) 这个很简单,因为不带参数的构造器方法为其赋予了值“0000”、“00”和“00”,因此,我们可以使用带参的构造器来实现相同的工作:Birthday birthday2=new Birthday(“0000”,”00”,”00”);
7) 这里所要的新类很简单,它与我们前面的实例Birthday类十分类似,我们只需要在前面的定义中做一些修改就可以得到了。
以下就是一个实例:
源程序:Birthday.javapublic class userName
{
public String firstname;
public String lastname;
public userName()
{
firstname=”firstname”;
lastname=”lastname”;
}
public userName(String first,String last)
{
firstname=first;
lastname=last;
}
public String fullName()
{
String fullname=firstname+” ”+lastname;
return fullname;
}
}
8.2 说明类的特性
8.2.1 private特性
实例说明
1.首先,我们输入以下源程序,构建一个People类:
源程序:People.javapublic class People
{
public String name;
public String sex;
private String age;
}
2.接着使用javac编译这个类:c:javastudy> javac People.java
3.接下来,我们构建一个使用People类的程序usePeople.java
源程序:usePeople.javapublic class usePeople
{
public static void main(String argv[])
{
People people1=new People();
people1.name=”Kate”;
people1.sex=”female”;
people1.age=”23”;
System.out.println(“Name:”+people1.name);
System.out.println(“Sex:”+people1.sex);
System.out.println(“Age:”+people1.age);
}
}
3. 执行以下命令,编译usePeople.java:c:javastudy> javac usePeople.java
这时,编译器将报告错误,编译失败,如下图所示:
图8-3 编译错误输出
传授新知
在上面这个例子中,我们遇到了一个错误:Variable age in class People not accessible from class usePeople:
people1.age=”23”; 1 error
这个错误信息的中文信息就是:“在语句people1.age=”23”中有一个错误,usePeople类无权访问People类中的变量age。”
为什么会出现这个错误呢?难道是语句写错了!但是Java编译器并未对相类似的另两条语句报错呀。
我们一起来看一下,在People这个类的定义:public String name;
public String sex;
private String age;
我们发现,没有报错的两条语句所访问的变量name与sex,在People类中定义时使用了public;而报错的这条语句所访问的变量age在定义时使用的是private。对,区别就在这里。
英语单词public的意思是:公用的,而private的意思则是:个人的、私有的。噢!People类中的成员变量age与另两个变量name和sex不同,它是私有的。所以usePeople无权访问age这个成员变量。因此,引起了出错。
在Java语言中,我们可以将类的某些数据、方法声明为private,这样这个数据或方法则仅能够被这个类使用,而不允许其它类使用。
这样做有什么好处呢?这样可以实现信息隐藏。我们可以将一个类的所有变量都定义为私有的,然后构造一些公用的方法来访问和处理数据。这样就可以有效地实现数据的封装,利用程序设计。
下面我们来看一个这样的例子:
实例说明
1.首先,我们输入以下源程序,构建一个新的People类:
源程序:People.javapublic class People
{
private String name;
private String sex;
private String age;
public People(String n,String s,String a)
{
name=n;
sex=s;
age=a;
}
public void infoPrint()
{
System.out.println("Name:"+name);
System.out.println("Sex:"+sex);
System.out.println("Age:"+age);
}
}
2.接着使用javac编译这个类:c:javastudy> javac People.java
3.接下来,我们构建一个使用People类的程序usePeople.java
源程序:usePeople.javapublic class usePeople
{
public static void main(String argv[])
{
People people1=new People("Kate","female","23");
people1.infoPrint();
}
}
3. 使用javac编译后,执行以下命令运行程序:c:javastudy> java usePeople
这时,我们如愿以偿地输出了对象people1的信息:
图8-4 usePeople程序的输出
在这个实例中,People将数据都封装起来,usePeople则使用People类提供的两个公用的(public)方法来设置成员变量的值,以及打印这些成员变量的值。
8.2.2 static特性
实例说明
1.首先,我们构建一个类myBook:
源程序:myBook.javapublic class myBook
{
public String bookName;
public static String ownerName=”XuFen”;
public myBook(String bn)
{
bookName=bn;
}
public void infoPrint()
{
System.out.println("Book Name:"+bookName);
System.out.println("Owner:"+ownerName);
}
}
2.接着使用javac编译这个类:c:javastudy> javac myBook.java
3.接下来,我们构建一个使用myBook类的程序usemyBook.java
源程序:usemyBook.javapublic class usemyBook
{
public static void main(String argv[])
{
myBook mybook1=new myBook("C Progamming");
myBook mybook2=new myBook("Java Programming");
mybook1.infoPrint();
mybook2.infoPrint();
System.out.println("--------------------------------------");
mybook1.bookName="C Programming";
mybook1.ownerName=”XuFeng”;
mybook1.infoPrint();
mybook2.infoPrint();
}
}
3. 使用javac编译后,执行以下命令运行程序:c:javastudy> java usemyBook
程序将产生如下图所示的输出:
图8-5 usemyBook程序的输出
传授新知
我们一起来看一下这个程序,以及它所产生的输出:
1)myBook mybook1=new myBook("C Progamming");
myBook mybook2=new myBook("Java Programming");
mybook1.infoPrint();
mybook2.infoPrint();
在这一段程序中,我们定义了两个myBook类的对象mybook1和mybook2,并且调用它的构造器方法,将其成员变量bookName的值分别设置为“C Progamming”和“Java Programming”。
接下来,我们调用了myBook类的成员方法infoPrint打印它们的信息,程序输出了:Book name: C Progamming
Owner: XuFen
Book name: Java Programming
Ower: XuFen
我们并没有为ownerName赋值呀,为什么会输出呢?这是因为我们在myBook类的定义中就为其赋值为“XuFen”了。所有每一个myBook类的对象都有这个成员变量了。
由于,我们发现了mybook1的书名与拥用者名字都错了,所以我们又重新修改,再打印出新的信息:
2)mybook1.bookName="C Programming";
mybook1.ownerName=”XuFeng”;
mybook1.infoPrint();
mybook2.infoPrint();
首先,我们修改了成员变量bookName的值,加上了少掉的字母“r”,接着修改了成员变量ownerName的值,加上了少掉的字母“g”。
接着,我们再看一下输出:Book name: C Programming
Owner: XuFeng
Book name: Java Programming
Owner: XuFeng
这时,我们发现mybook1的bookName与ownerName的值就修改过来了。但我们惊奇地发现,mybook2的ownerName也从“XuFen”变成了“XuFeng”。这是怎么回事呢?我们并没有对它做过修改呀!
这时因为,我们定义ownerName时,使用了一个特殊的特性说明符static:public static String ownerName=”XuFen”;
英文单词static的意思是:静态。也就是说,我们让成员变量ownerName成为静态的变量。这里静态的意思不是指它的值是不变的。而是它的存储位置是不变的。如下图所示:
图8-6 static变量示意图
从上图中,我们可以很清晰地知道,myBook类的两个对象mybook1和mybook2虽然都包含自己的成员变量ownerName,但它们都是指向同一个存储位置的。所以实际上只有一个。
你现在明白为什么修改了mybook1的成员变量ownerName的值,mybook2的成员变量ownerName的值也跟着改变了吗?
一些提示:
同样的,static也可以用来修饰方法,即静态方法,静态方法只能访问静态数据,而不能访问非静态的变量和方法,否则将引起编译器出错。
自测练习
1) 定义私有变量、方法的关键字是__________。
a.pulic b.static c.private
2) 定义静态变量、方法的关键字是__________。
a.pulic b.static c.private
3) 以下几个方法,可以被外部类访问的是___________。
a. pulic void infoPrint() b.void public infoPrint()
c.private void infoPrint() d.void private infoPrint()
4) public方法不能够访问其它类的private数据。___________
a.对 b.不对
5) 外部类不能访问静态(static)变量。___________
a.对 b.不对
6) 外部类不能访问_________变量。
a.static b.private c.public
7) 私有变量允许________访问。
a.任何类 b.特权类 c.定义这个变量的类
8) 现有一个如下所示的类:public class People
{
private String name;
private String sex;
private String age;
}
请构建一个方法getName,使得其它类能够访问它的成员变量name:
____________________________________________________________________
____________________________________________________________________
____________________________________________________________________
练习答案
1)c Java语言中使用关键字private定义私有变量、方法。
2)b Java语言中使用关键字static定义私有变量、方法。
3)a c和d使用了private关键字,肯定不能被外部类访问,所以答案就在a和b之间。而b将void放在了public之前,不符合Java语言的语法规则。
4)a private定义的数据不能够被外部类访问。
5)b 静态变量是可以被外部类访问的。
6)b 仅有私有变量是不能够被外部类访问的。
7)c 私有变量只允许定义它的类访问,在Java中没有什么特权类。
8)这个方法很简单:public int getName()
{
return name;
}
8.3 创建子类
传授新知
至此,我们已经学会了如何创建自己需要的自定义类,已经学会了创造!
正如创建Java的类一样,人们在认知了大自然后,也开始创建属于自己的“类”,如“电灯”、“电视”、“电脑”、“电话”、“飞机”等……。这些伟大的发明使得历史的车轮迅速地向前滚去。
然而,人类之所以得以这样迅速的发展,不仅得益于杰出科学家们的不断创新,还得益于继承,使得每一项新的发明与创造都是“站在巨人头上的进步”。人们在创造新事物的同时,还总结了一些用于创造的基础配件,如“集成电路”、“模板”,新的发明,就从组装、改进、复用它们而得到。
软件危机后,人们意识到在软件工程领域也采用这种思想,必将使软件得于更大的发展。这就是软件复用。而在面向对象方法学中,创建子类就是用以实现这种思想的有效工具。在这一小节中,我们将学习如何继承一个类,创建出更有特色的子类。
举个例子,我们在程序中定义了一个类:“交通工具”,而这时我们需要一个“客机”类,我们就可以通过继承“交通工具”类共性(公用变量和方法),然后新增一些“客机”类独有的属性和方法,就能够很轻松地构建出新的类来。如下图所示:
图8-7 继承示意图
下面,我们一起来看一个创建子类的例子。假设我们已有一个交通工具类vehicle:
源程序:vehicle.javapublic class vehicle
{
public int passenger;
public float carfare;
public vehicle()
{
passenger=0;
}
public vehicle(int p)
{
passenger=p;
}
public getgrossEarnings()
{
float grossEarnings= passenger*carfare;
return grossEarnings;
}
}
在这个类中,有两个成员变量:
1) passenger:定义为公用的,整型数,用于存放乘客总数;
2) carfare:定义为公用的,浮点数,用于存放票价。
同时,我们构造了两个构造器方法:
1) 如果没带参数,则将成员变量passenger赋值为0;
2) 如果带上了一个int型参数,则将成员变量passenger赋值为这个数。
另外,在程序中还有一个有用的方法:getgrossEarnings(),它用来统计这次的总收入(当然是乘客数乘以票价)。
分析完这个类后,我们现在需要构建一个“客机”类,完成的功能与“交通工具”类相同。这时,我们就可以使用创建子类的方法来构建这个“客机”类:plane。
源程序:plane.javapublic class plane extends vehicle
{
public int baggage;
public float bPrice;
public plane()
{
super();
baggage=0;
}
public vehicle(int p,int b)
{
super(p);
baggage=b;
}
public getgrossEarnings()
{
float grossEarnings= passenger*carfare+baggage*bPrice;
return grossEarnings;
}
}
下面,我们来分析一下这段程序:
1)public class plane extends vehicle
第1句,是用来定义类的。它与其它类的定义不同的地方是后面加上了extends vehicle,其中extends的意思是扩展,也就是plane类是从vehicle类中扩展的,也就是说plane是vehicle的子类。
大家应该还记得,我们在编写Java小应用程序的时候,定义类的时候,后面都有一个extends Applet,说明所有的小应用程序是从Applet类中扩展的。其中Applet是Java语言为小应用程序提供的一个类。
2)public int baggage;
public float bPrice;
由于在客机上,行李也是计费的,因此,我们需要加上两个变量baggage和bPrice,这样,plane类就有了四个成员变量:
§ passenger:从超类vehicle中继承,用于存放乘客总数;
§ carfare:从超类vehicle中继承,用于存放票价;
§ baggage:新的成员变量,整型,用于存放计费行李总公斤数;
§ bPrice:新的成员变量,浮点数,用于存放每公斤行李的运费。
3)public plane()
{
super();
baggage=0;
}
这一段程序构建了一个构造器方法。在这个方法中使用了一个第一次遇到的语句:super();
这个语句是用来调用超类的构造器,也就是调用了vehicle类的构造器vehicle()。
同样,我们以相同的方法定义了另一个构造器plane(int p,int b)。
4)public getgrossEarnings()
{
float grossEarnings= passenger*carfare+baggage*bPrice;
return grossEarnings;
}
由于行李也要计费了,所以总收入的计算方法也就不一样了,因此,在plane类中重新定义了方法getgrossEarnings,这叫做“方法overlay”。实际上就是指在子类中,重新定义一个新的同名方法,替换掉超类中定义的同名方法。
自测练习
1) 创建子类时,我们使用了一个新的关键字,是___________。
a. subclass b.modifyclass c.extends d.inherit
2) 子类继承时,___________不被继承。
a.静态变量 b私有变量 c.公用变量
3) 子类继承时,___________不被继承。
a.私有方法 b公用方法 c.静态方法
4) 如果你想要在子类中引用超类的构造器方法,应该使用___________。
a.不可以引用 b.super() c.直接使用超类的构造器方法名
5) 子类中的“方法overlay”是指:_______________。
a. 在子类中,将超类里定义的某个方法去掉,使其不生效
b.在子类中,重新定义超类里定义过的某个方法,使其更适合于子类。
练习答案
1)c 在Java语言中,用extends来表示继承一个类。
2)b 用private定义的私有变量将不会被继承到子类中去。
3)a 用private定义的私有方法将不会被继承到子类中去。
4)b 在Java语言中定义了一个super()的方法,为子类提供了使用超类 的构造器方法的手段。
5)b 方法overlay就是对其重新定义,使其更适合子类。