第六章类设计
面向对象是一种强力的软件开发方法,它将数据和操作视为一个相互依赖、不可分割的整体。由于面向对象的方法更符合人们的思维习惯,同时有助于控制软件的复杂性、提高效率,从而得到了最广泛的认可,也已经成为目前最为流行的一种软件开发方法。在各个不同版本的中,对面向对象理念的解释也都是大同小异。其中以是Coad、Yourdon的:“面向对象=对象+类+继承+消息通讯”最为著名。也被人们所熟知。
6.1 类与对象
对象的初始化通常由类的构造器来完成。我们可以简单的把构造器理解为一种特殊的成员方法,在每次创建对象时自动被调用。使用构造器要注意以下几个问题:1.构造器必须与类名相同;
2.构造器不能添加返回类型;
3.构造器只能用范围限定修饰符来修饰;
4.构造器一般是公有的;
5.构造器通常仅仅进行对象的初始化工作;
6.构造器是被系统自动调用的,不能像调用其他普通方法那样显式的调用构造器。
下面先看一个circle类的例子
例6.1 :
public class test
{
public static void main(String args[])
{
circle c = new circle();
c.output();
}
}
class circle
{
double r;//半径
double pai;//圆周率
public circle()
{
r = 3.5;
pai = 3.14;
}
public void output()
{
System.out.print("圆的面积为:\t");
System.out.println(pai*pai*r);
}
当主方法使用new关键字来创建circle对象时,就会自动调用构造器。其实除了可以用构造器进行初始化外,还能通过“语句块”的方式来实现。下面就通过“语句块”的方式来改写circle类,代码如下:
例6.2 :
class circle
{
double r;//半径
double pai;//圆周率
{
r = 3.5;
pai = 3.14;
}
public void output()
{
System.out.print("圆的面积为:\t");
System.out.println(pai*pai*r);
}
}
这看起来很难理解。然而我们只需要知道JA V A中能支持这种用法就够了。其实普通的“语句块”是很少用到的。用的更多的是static“语句块”来给类中的静态属性初始化。从上面的代码中,我们很容易的发现:很显然把圆周率(pai)定义为静态的更为合理一些。
例6.3 :
class circle
{
double r;//半径
static double pai;//圆周率
{//普通(非静态)语句块
r = 3.5;
}
static //静态语句块
{
pai = 3.14;
}
public void output()
{
System.out.print("圆的面积为:\t");
System.out.println(pai*pai*r);
}
}
其实JAVA引入“语句块”的概念也是无奈之举:因为众所周知,JAVA是不支持静态构造器的。所以引入“语句块”就是为了解决静态属性初始化的问题。从上面的例子我们能够非常清楚的看出“语句块”就是用一对{}括起来的属性赋值语句,并且本身不能接受任何参数。所以JAVA中的“语句块”跟C#中的静态构造方法比起来就落了下乘。然而有总剩余无,我们所能做的也仅仅是期望在新的JDK版本中能有新的突破。
通过上面的例子,我们了解了“语句块”的概念。那么也需要知道构造器、非静态“语句块”、静态“语句块”在类实例化时的调用顺序。大多数的读者,往往并不太关心这种非常细节的技术问题。试想一下在非静态“语句块”中和构造器中都对同一个属性赋了值,那么最终是哪一个值呢?这种细节的东西如果没掌握住,那么在调试程序的时候会浪费大量的时间,并且往往找不到问题的所在。构造器与非静态“语句块”每实例化一个对象就调用一次。而静态“语句块”则是不管实例化多少个对象,都只在实例化第一个对象时才被调用一次。而调用的先后顺序是:静态语句块- 非静态语句块—>构造器。
下面给出一个可以运行的程序,读者可以编译运行它来加深理解。
例6.4 :
public class transferSequence
{
{
System.out.println("**我是非静态的代码块,创建多少个对象我就被调用多少次");
}
static
{
System.out.println("--我是静态的代码块,无论创建多少个对象我都被调唯一的调用次");
}
public transferSequence()
{
System.out.println("我是类的构造器");
}
}
class test_transferSequence
{
public static void main(String args[])
{
transferSequence temp1 = new transferSequence();
transferSequence temp2 = new transferSequence();
transferSequence temp3 = new transferSequence();
}
}
保存为:transferSequence.java
编译:javac transferSequence.java
运行:java test_transferSequence
结果:
--我是静态的代码块,无论创建多少个对象我都被调唯一的调用次
**我是非静态的代码块,创建多少个对象我就被调用多少次
我是类的构造器
**我是非静态的代码块,创建多少个对象我就被调用多少次
我是类的构造器
**我是非静态的代码块,创建多少个对象我就被调用多少次
我是类的构造器
6.2 类中的高级应用
在类的设计过程中,有几个重要的修饰符号。下面我们依次介绍它们的用法:
6.2.1 static关键字
JAVA中,类声明属性、方法、内部类时,可以使用static关键字作为访问修饰限制符。Static标记的属性或方法有整个类(类的所有实例)共享。如果访问权限允许,我们就可以通过“类名.方法(属性)名”引用。因此静态成员也可以称为类成员。
下面的程序将有助于理解:
例6.5:
public class person
{
public static int total = 0;
public person()
{
total ++;
System.out.println("人数为:\t" + total);
}
}
class test_person
{
public static void main(String args[])
{
person.total = 20;
System.out.println("人数为:\t" + total);
person temp = new person();
}
}
需要注意的是:静态方法只能访问当前类中的静态字段。而非静态方法却能直接使用静态字段。此外还要特别注意的是:局部变量是不能声明为静态的。细心的读者可能会发现局部变量不但不能用static来声明,而且连访问修饰限制符都不能用。
6.2.2 this关键字
在谈this关键字之前,还需要先了解一下局部变量的概念以及它的生存周期、优先级。我们通常所说的“局部变量”,在大多数情况下都指的是方法中定义的字段。是跟类体中直接定义的字段相比,才有的“局部变量”的概念。而且两者之间的区别也是很微妙的。
先看下面的示例程序:
例6.6 :
1 public class test_partVariable
2 {
3 public static void main(String args[])
4 {
5 partVariable temp = new partVariable();
6 temp.output();
7 }
8 }
9 class partVariable
10 {
11 String a;
12 public partVariable()
13 {
14 a = “Kevin”;
15 }
16 public void output()
17 {
18 String a = “waston”;//局部变量
19 System.out.println(a);
20 }
21 }
运行后,发现结果是”waston”(如果是初学者,可能会很困惑)。其实原因很简单,就是局部变量的特性搞的鬼。在揭示这个特性之前,我们还需要知道变量作用域的概念。所谓的作用域,指得就是变量的生存区间(即:所在的那对大括号)。还是以上面的程序为例,第11行的变量a,它的作用域是整个partVariable类.而第14行的a,它的作用域仅仅是output()这个方法。
前面所提到的“特性”就是:在变量的生存区间内,作用域范围越小的变量优先级越高。所以在19行输出语句输出的a值就是第18行定义的,值为”waston”。那么怎么才能在output()中输出第11行的变量a呢?这就需要用到this关键字。
下面用this关键字改写上面的代码
例6.7 :
1 public class test_partVariable
2 {
3 public static void main(String args[])
4 {
5 partVariable temp = new partVariable();
6 temp.output();
7 }
8 }
9 class partVariable
10 {
11 String a;
12 public partVariable()
13 {
14 a = “Kevin”;
15 }
16 public void output()
17 {
18 String a = “waston”;//局部变量
19 System.out.println(“将输出18行的值 a = ” + a);
System.out.println(“将输出11行的值 a = ” + this.a);
20 }
21 }
this关键字只能在类的方法内部使用,表示对“当前方法所在对象”的引用。所以this 的用法和其他普通对象一样。编译下面的示例程序,将会发现另一个问题。
例6.8 :
public class test
{
public static void main(String args[])
{
partVariable.print();
}
}
class partVariable
{
static String str = "********";
public void output()
{
String str = "&&&&&&&&&";//局部变量
System.out.println(this.str);
}
}
通过编译上面的代码会发现,前面提到的“问题”指的是:在静态方法中是不支持this 关键字的。这也是很符合定义this关键字的初衷的。因为在前面我们提到了:this是属于
对象的,而静态方法是属于类所有。
6.2.3 Get、set方法和JavaBean技术
虽然JAVA也有所谓的get和set方法,然而它的形式意义远大于它的实际意义。因为get和set方法仅仅是在编程的时候的一种格式约定,就像代码缩进一样,它并不是由语言本身所提供的。
众所周知:为了实现类的良好的封装性,(在不考虑继承的前提下)我们往往喜欢把属性定义为private类型,然后再提供一个public方法来获取或更改它。然而对于这样的方法,在方法名前面加上set或get。这是一个良好的习惯,因为这样会让代码变得更加清晰有序。
请看下面的程序实例:
例6.9 :
class student
{
private String name;//姓名
private String sex;//性别
private int age;//年龄
public void setName(String name)
{ https://www.doczj.com/doc/8e6361279.html, = name; }
public String getName()
{ return name; }
public void setSex(String sex)
{ this.sex = sex; }
public String getSex()
{ return sex; }
public void setAge(int age)
{ this.age = age; }
public int getAge()
{ return age; }
}
很多程序员对这种业内“约定”的东西不感兴趣。因为按照业内“约定”做,会给自己增加许多的负担,却往往不能得到立竿见影的好处,所以很多人就慢慢的放弃了。可惜,正是这种“短视”的做法,才使得自己在今后的职业生涯中为此付出了很大的代价。
说到了get和set方法就不得不说JavaBean。与get和set方法一样,JavaBean技术称为约定更合适。关于JavaBean是这样定义:在类中只定义私有的属性、一个空的构造器,以及与每个私有属性对应的get和set方法
6.3 封装类
封装类概念的引入,源于JAVA中的八种基本数据类型。JAVA中引入这八种数据类型也属无奈之举,因为这样不但能简单、有效的进行常规数据处理,更主要的是是为了迎合人们根深蒂固的习惯。
这种借助于非面向对象技术的做法也会带来很大的不便。比如将String类型的数据转换为基本数据类型要麻烦很多。这是为了解决这个问题,JAVA引入了封装类的概念。JAVA 的JDK中针对各种基本数据类型分别定义了相应的引用类型,并称之为封装类(Wrapper Classes)。下面列出的是八种基本数据类型与其封装类的对应表。
基本数据类型对应的封装类
char character
byte Byte
short Short
int Integer
long Long
float Float
double Double
boolean Boolean
这八个封装类都提供了相同的方法,为了使用方便,下面就列出一些最主要的方法以供读者参考(更详细的信息请参照官方资料)。
为了读者能看的更直观,下面将列出八种封装类所提供的最主要的方法
构造器:
public Boolean(String s);
public Byte(String s);
public Short(String s);
public Integer(String s);
public Long(String s);
public Float(String s);
public Double(String s);
public Boolean(boolean value);
public Byte(byte value);
public Short(short value);
public Character(char value);
public Integer(int value);
public Long(long value);
public Float(float value);
public Double(double value);
方法:
//用于返回对象的值
public boolean booleanValue();
public byte byteValue();
public short shortValue();
public int intValue();
public long longValue();
public char charValue();
public float floatValue();
public double doubleValue();
//用来进行String类型到基本类型的转换
public boolean parseBoolean(String s);
public byte parseByte(String s);
public short parseShort(String s);
public int parseInt(String s);
public long parseLong(String s);
public float parseFloat(String s);
public double parseDouble(String s);
需要注意的是:Character没有public Character(String s)构造器,并且也没有提供从String类型到char类型的转换方法。或许是因为JAVA中提供了toCharArray()方法的缘故吧。这个方法将在字符串一章有详细的介绍。
下面通过一个事例来加深理解:
例6.10 :
public class test_exchangeType
{
public static void main(String args[])
{
int temp1 = 10;
String temp2 = "20";
Integer i1,i2;
i1 = new Integer(temp1);
i2 = new Integer(temp2);
System.out.println(i1.intValue());
System.out.println(i2.intValue());
System.out.println(i1.parseInt(temp2));
}
}
6.4 继承
从现实的角度来考虑,继承是再正常不过的,比如:孩子继承父母的优点(或缺点)。而所谓的多态也比较常见,比如:早晨闹钟响了,父母就要上班了,而孩子却要上学。类似于这种对于同一事件,不同的执行体执行不同的动作,就可以称为多态。不得不承认,方法重载是一个不错的理念。读者通过后面的学习会对此有更深刻的认识。而在本章的开篇要留下一个关于方法重载问题:这样的一个理念,有没有它的局限性?或者说我们有没有办法把它变得更加完美?这个答案将在本章的小结中给出。类的继承是面向对象语言的一个基本特
性。继承机制可以大幅度的提高软件模块的可重用性和可扩展性,从而提高软件的开发效率。
6.4.1 基类和派生类
在介绍这个概念之前,先看一个小的程序片段。
例6.11 :
class student
{
String name;
String college;
char sex;
... ...
}
class teacher
{
String name;
char sex;
String title;
... ...
}
从这两个类的代码段中不难发现,这两个类有太多共性的地方。很显然把这两个类共性的东西拿出来,然后在通过某种方式来调用共性的地方是最佳的选择。而所谓的“某种方式”就是我们常说的“继承机制”。
在JA V A中,一个子类类只能继承一个父类。也就是人们所熟知的“JA VA语言是单继承”。另外JA V A的继承不同于C++的另一点是:C++中类的继承是通过“:”来标识的,而JA V A中是通过关键字“extends”来实现的。下面就用继承的机制来改写上面的程序片段,使其变得更加合理。并给出一个测试类,使其能够运行。
例6.12 :
public class person
{
String name;
int age;
}
class student extends person
{
String college;
public void printInfo()
{
System.out.println("学生的基本信息");
System.out.println(name + "\n" + age + "\n" + college);
}
}
class teacher extends person
{
String title;
public void printInfo()
{
System.out.println("教师的基本信息");
System.out.println(name + "\n" + age + "\n" + title);
}
}
class test
{
public static void main(String args[])
{
student s = new student();
teacher t = new teacher();
https://www.doczj.com/doc/8e6361279.html, = "kevin";
s.age = 25;
s.college = "Software College";
https://www.doczj.com/doc/8e6361279.html, = "waston";
t.age = 40;
t.title = "professor";
s.printInfo();
t.printInfo();
}
}
保存为:person.java
编译:javac person.java
运行:java test
结果如下:
学生的基本信息
kevin
25
Software College
教师的基本信息
waston
40
Professor
在这个示例代码中,person类可以被称谓“父类”、“超类”、“基类”,而student和teacher 类被成为“子类”、“派生类”。
6.4.2 super关键字
在前一章我们介绍过,this关键字可以用来访问当前对象,而本小节所介绍的super关键字是用来访问当前对象的基类对象,进而调用基类对象的成员。与this关键字一样,super
也不使用于static方法、属性。
前面我们提到了“继承”的概念,很显然上面的代码,在student和teacher类中还是存在着共性的东西。下面就借助super关键字来使得上面的代码变得完美些。
例6.13 :
public class person
{
String name;
int age;
public void printInfo()
{
System.out.println(name + "\n" + age);
}
}
class student extends person
{
String college;
public void printInfo()
{
System.out.println("学生的基本信息");
super.printInfo();
System.out.println(college);
}
}
class teacher extends person
{
String title;
public void printInfo()
{
System.out.println("教师的基本信息");
super.printInfo();
System.out.println(title);
}
}
class test
{
public static void main(String args[])
{
person p;
student s = new student();
teacher t = new teacher();
https://www.doczj.com/doc/8e6361279.html, = "kevin";
s.age = 25;
s.college = "Software College";
https://www.doczj.com/doc/8e6361279.html, = "waston";
t.age = 40;
t.title = "professor";
p = s;
p.printInfo();
p = t;
p.printInfo();
}
}
6.4.3 继承中的构造器
如果要创建一个派生类的实例,那么在执行其本身的构造器之前,会隐式的调用其基类的构造器也就是说JA V A语言要创建一个派生类的对象,虚拟机会首先自动的调用顶级副类的构造器,然后依次调用各级派生类的构造器。
为了加深理解,请看下面的示例代码。
例6.14 :
class A
{
public A()
{
System.out.println("我是A的构造器");
}
}
class B extends A
{
public B()
{
System.out.println("我是B的构造器");
}
}
class C extends B
{
public C()
{
System.out.println("我是C的构造器");
}
}
public class constructordemo
{
public static void main(String args[])
{
C c = new C();
}
}
保存为: constructordemo.java
编译:javac constructordemo.java
运行:java constructordemo
运行结果:
我是A的构造器
我是B的构造器
我是C的构造器
有一点很奇怪,在JA V A中构造器也是可以被调用的。下面给出示例代码
例6.15 :
public class test
{
public test()
{
System.out.println("**********");
}
public test(int a)
{
this();//调用public test()
}
public static void main(String args[])
{
test t = new test(1);
}
}
JA V A程序员为了这个特性争吵了很久:有些人认为这样做能够简化代码,也有些人认为这样做势必要增加模块间的耦合……。事实上,这个争吵并没有最终的赢家。然而在JDK1.5中依然保留了这种用法,所以从这点来说或许前者胜利了。其实这种争吵根本就没有意义,因为一旦在程序中这样用了,那么基本上就能够简化代码,并且一定增加了模块间的耦合性。所以本着“正交性”的原则,还是不提倡这种用法。此外还需要注意的一点是JA V A中构造器没有访问修饰符限制的时,默认是public。
6.4.4 JA V A中包的概念与环境变量的设置
包的概念源于WINDOWS系统中的文件夹的启发。当在开发大型项目时,能够有类似于文件夹的机制来解决数以百计的类文件之间的命名冲突的问题,无疑是一件非常美妙的事情。当然包概念的引入使得这种想法得以实现。
与包有关的两个关键字是:package和import。前者是用来声明生成包,后者用声明该类文件将引入包。并且package一定要先于import语句。请看下面的代码。
例6.16 :
package kevin;
import java.io.*;
public class packageExample
{
public static void main(String args[])
{
InputStreamReader isr;
BufferedReader br;
System.out.println("请随便输入一些字符");
try
{
isr = new InputStreamReader(System.in);
br = new BufferedReader(isr);
String str = br.readLine();
System.out.println("您输入的是:\t" + str);
}
catch(IOException e)
{
e.printStackTrace();
}
}
}
保存为: packageExample.java
编译:javac -d .packageExample.java
运行:java kevin.packageExample
结果:
请随便输入一些字符
kevin
您输入的是:kevin
不难发现上面的编译和运行命令和以前的有些不一样。-d是做包的命令,“.”表示包的生成路径为当前目录,当然也可以定位到任意存在的路径下(后面将有一个示例进行讲解)。,包的名字是由package关键字后面名字决定,在本例中是"kevin"。运行命令也于以前不同。JA V A中规定,如果一个类文件中含有package命令,那么要运行这个程序,就必须在运行的时候在类文件的前面加上package中定义的包名。并且在类文件中,package一定要写在import的前面,否则是不能通过编译的。有兴趣的读者可以试一试。
在介绍如何引入包前,需要了解一下环境变量的概念。
简单的说,所谓的环境变量就是告诉系统,去哪里找当前命令需要用到的资源。在这里只介绍如何制作环境变量的批处理文件(所谓的批处理文件就是扩展名问bat的文件。在系统中有着特殊的作用,关于它的知识请参照相关资料,这里就不赘述了)。
JA V A的环境变量要设置path和classpath,下面是批处理文件内部的内容:
set path=D:\Program Files\java\j2sdk1.5.0\bin;
set classpath=.;D:\Program Files\java\j2sdk1.5.0\lib
当然根据JDK的安装路径以及版本的不同,这个批处理文件也是不尽相同的。在光盘中有一个关于如何配置环境变量的录象。
下面看一个关于引入自定义包的示例
例6.17 :
package kevin.waston;
public class demo
{
public void output()
{
System.out.println("我是kevin包中一个类的方法");
}
}
保存为demo.java
import kevin.waston.*;
public class testdemo
{
public static void main(String args[])
{
demo p = new demo();
p.output();
}
}
保存为testdemo.java
环境变量:
set path=D:\Program Files\java\j2sdk1.5.0\bin;
set classpath=.;C:\;D:\Program Files\java\j2sdk1.5.0\lib
编译demo.java: javac -d c:\ demo.java
编译testdemo.java javac -sourcepath src testdemo.java
运行testdemo java testdemo
结果:
我是kevin包中一个类的方法
"-d"命令表示要求系统生成包,存放的路径为"-d"后面跟随的路径,并且将生成的.class 文件存放到系统生成的包中。-sourcepath src表示从环境变量设置的路径中寻找需要的文件(注:虽然在环境变量中已经设置的路径,但是如果不在编译时加入-sourcepath src命令,则系统依然不去classpath中寻找。也就是说如果不引入自定义包,则在环境变量中可以不设置classpath。如果要深入的了解javac命令,请参照相关的专业资料。但是读者需要注意的是:javac是一个很复杂的命令,绝不是被大多数人所描述的那样,仅仅是用来编译.java 源文件)那样简单的javac命令。
6.4.6 范围限定修饰符
这四个关键字的复杂混乱的用法,给初学者带来了极大的难度。但是经过十多年的代码积累,SUN已经没有魄力和能力改变这种混乱的局面。所以作为程序员,只能接受它并且试着去习惯这种混乱的用法。
控制级别
同一个类同一个包不同包的子类任何场合
可否访问
private 可以
default 可以可以
protected 可以可以可以
public 可以可以可以可以
对于这个表就不进行更为详细的解释,希望读者能花些时间对各种情况进行一下验证,以加深理解。因为以后的程序开发都是以此为基础的。
6.5 多态
“多态性”的含义指的是:同一事物在不同的条件下可以表现出不同的形态。这一点在对象之间进行通信时非常有用。例如一个对象发送消息到其他对象,它并不一定要知道接收的对象属于哪一个类。接收到消息后,再根据不同类型的对象做出不同的解释,执行不同的操作,进而得到想要的结果。
6.5.1方法重载与重写
在同一个类中,方法名相同而参数列表不同的方法称为重载方法。方法重载的意义并不大,我们甚至可以理解为:之所以要引入方法重载的理念,仅仅是因为取一个见名知意的方法名很困难。当然方法重载也带来了一个好处:我们知需要记住一个方法名,然后根据需要的参数不同来调用不同的方法。
下面将给出一个方法重载的小示例。
例6.18 :
class overloadeg
{
public void print()
{
System.out.println("调用参数类表为空的print方法");
}
public void print(String name)
{
System.out.println("调用了参数列表为一个String变量的print方法");
}
public static void main(String args[])
{
overloadeg eg = new overloadeg();
eg.print("kevin waston");
}
}
保存为: overloadeg.java
编译:javac overloadeg.java
运行:java overloadeg
结果:
调用参数类表为空的print方法
调用了参数列表为一个String变量的print方法
方法重写不同于重载(当然有的书种将两者统成为重载),前者是类之间继承中才存在的概念,而方法重载则是类中的概念。为了便于理解可以这样看待两者:方法重写存在于继承之中,而方法重载只存在于单个类的内部。
JA V A中是这样判定两个方法完全相同的:只要方法的名字相同,并且参数列表完全一样。这和以前学过的一些语言不太一样,读者应该多加注意。当然也不要在“参数列表完全一样”上玩文字游戏,下面的代码将不能通过编译。
例6.19 :
public class demo
{
public void print(int a,int b)
{}
public void print(int c,int d)
{}
public staitc void main(String args[])
{
demo d = new demo();
}
}
JA V A中的方法重写是不需要特别声明的,也就是说只要子类中的方法和父类中的方法完全一样,就是方法重写。下面的代码将有助于理解。
例6.20 :
public class test
{
public static void main(String args[])
{
A a = new B();
a.print();
}
}
class A
{
{
System.out.println("A中的方法");
}
}
class B extends A
{
public void print()
{
System.out.println("B中的方法");
}
}
保存为: test.java
编译: javac test
运行:java test
结果:
B中的方法
作为JA V A类的重要组成部分,构造器也可以进行重载。此外,如果类中没有定义构造器,则系统会默认的生成一个空的构造器。但是一旦在类中显示的定义构造器后,系统就不会在自动地生成空的构造器了。构造器的重载形式也与普通方法一样,比如例6.21 :
class student
{
int age;
public void print()
{}
public print(int age)
{
this.age = age;
}
}
前面提到了,JA V A中的方法重写是不需要任何特殊声明的。然而这个语法规则却直接导致了一个BUG的产生,比如子类需要重写父类中的一个方法,但是很遗憾,在父类中忘记定义这个方法了。所以程序中就产生了一些奇怪的问题。除非去翻看源代码,否则这个BUG将被永远的埋藏起来。值得庆幸的是,在JA V A1.5中通过“注释”的机制解决了这个问题。
final标识的方法是不能被重写的。但是很显然一个见名知意的方法名并不是很好取,所以JA V A的这种规定无疑让我们不得不在子类中重新给方法重新取个名字。虽然这不是一个大问题,但还是期待在JDK的下一个版本中能通过某种方式解决这个问题另外读者思考一下,静态方法能否被重写?
6.5.2抽象类和抽象方法
通过前面的学习,我们知道了父类中的非final方法允许派生类进行重写,并载调用时动态的决定是执行父类的方法代码,还是执行子类的方法代码。此外,JA V A还可以为父类定义抽象方法,它强制性的要求所有的非抽象派生类必须重写该方法。
抽象方法使用abstract关键字进行定义,并且不能给处方法的实现(注:abstract关键字一定要在返回类型的前面,另外抽象方法不能是static方法)。比如:
public abstract void print();
含有抽象方法的类必须是抽象类,也需要用abstract关键字加以标识,比如:
例6.22 :
abstract class demo
{
public abstract void print();
}
但是需要注意的一点是:含有抽象方法的类一定是抽象类,而抽象类可以不含有抽象方法。抽象类表达的是抽象的概念,并且不允许创建抽象类的实例。其作用是为派生类提供一个公共的界面。下面将用抽象类改写本章person.java这个程序。
例6.23 :
public abstract class person
{
String name;
int age;
public abstract void printInfo();
}
class student extends person
{
String college;
public void printInfo()
{
System.out.println("学生的基本信息");
System.out.println(name + "\n" + age + '\t' + college);
}
}
class teacher extends person
{
String title;
public void printInfo()
{
System.out.println("教师的基本信息");
System.out.println(name + "\n" + age + '\t' + title);
}