您的位置: 网站首页 > 程序开发 > Java程序设计 > 第3章 对象和类 > 【3.2 对象】

3.2 对象

 

3.2     

对象是面向对象方法的核心,可以从以下几方面理解这一重要概念。

1.对象具有很强的表达能力和指述功能

人们要进行研究的任何事和物统称为对象。

·    对象可以是看得见、摸得着的有形的实体,如计算机、飞机、汽车等。

·    对象可以表示人或组织所起的作用,如治病、教学、生产等。

·    对象可以表示事件,如各种事故、演出、战斗、开会等。

·    对象可以表示规则,如各种专家规则、约束条件等。

总而言之,对象既能表示简单的结构化的数据,又能表示非结构化的、抽象的、复杂的事件和规则。

从系统分析和系统设计的角度来看,对象是构成系统的最基本的单位,也是描述客观事物的独立的实体。

2.描述对象的两个要素:属性和方法

属性是用于描述对象静态特性(结构特性)的一个数据项,如描述一个人可用姓名、性别、身份证号等属性。

方法(也称服务)是用于描述对象动态特性(行为特性)的一个操作系列,如每个人都具有工作、学习等行为特性。

3.对象体现了封装性能:实现了数据与操作的结合

对象就是一组属性和相关的方法的集合。这是面向对象方法与结构化方法的重大区别之一。

对象把它全部的属性和全部的方法结合在一起,形成一个独立的基本单位,如图3-1所示。也就是说,对象将数据和施加于数据上的操作打包成一个不可分割的最基本的模块,使得仅有该类的有限个方法才可以操纵、改变这些数据。这样,当数据出错时,只需检查其有限的方法就可以。这是面向对象方法的封装性的一个重要表现。

3-1  对象的封装性

4.对象具有唯一标识(以下简称OID

当创建一个对象时,可由系统或用户授予该对象的OID。对象一旦获取了OID,就在它的整个生命周期中一直使用,作为唯一的标识。对象清除后,OID可由系统回收,再分配给其他新对象。

5.具体的对象必须参与一个或一个以上的类

在实际应用中,具体的对象应当参与类并作为该类的一个实例。在具体应用时,类实例与对象是同义词。

6.消息(Message

对象之间是通过发送与接收消息来建立联系的。

对象通过它对外提供的服务(方法)在系统中发挥它的作用。当系统中其他对象要求这个对象执行某个服务时,面向对象方法就把向这个对象发送的服务请求称为消息。通过消息进行对象之间的通信,如图3-2所示。这也是面向对象方法的一个原则:消息通信为对象之间提供了唯一合法的动态联系途径,从而确保对象的封装性得到很好的实现。对象之间既能够互相配合,又能够各自独立、互不干扰。

3-2  对象之间的消息发送与接收

7.对象具有高度的抽象性(Abstraction

在面向对象的方法中,对象可以具有最高的概括和抽象能力,即它可作为面向对象编程语言的类的最高层的父类——根,例如JavaC++语言都把最高层类定为抽象的对象(Object)。

对象也有创建(Create)→使用(Using)→清除(Clear up)的生命周期(Life Cycle of Object)。

3.2.1  对象的创建

创建—个对象实质上是要做3件事:对象的声明、对象的实例化和对象的初始化。

1.对象的声明

其语法格式为:

类名 对象名

例如,String name就是声明了系统已有的字符串类String的新对象name

声明语句中给出的类名必须是Java编译器能够找到的。上面的对象声明之所以能够直接String作为类名,是因为Java的编译器和解释器在启动时自动引入java.1ang包,相当于在程序前加入了一句导入信息。

import java.lang.*

因此,若类不在java.1ang包中,声明对象的类名就必须用类的全名或加入类的导入信息import语句。

注意:1)对象名必须符合Java语言对标识符的规定,即必须是同一串Unicode组成的合法Java标识符。

2)对象的声明并不会实例化对象,也就是不会给对象分配内存,对象的

实例化必须通过操作符new实现。

2.对象的实例化与初始化

new操作符实例化一个对象,并按照对象的类型给它分配内存。通常new操作符要与构造方法配合使用,即new操作符创建对象,构造方法对它进行初始化。例如:

new String("Hello Jaw world!")

String("Hello Jaw world!")就是对系统类String的构造方法的调用。类的构造方法是—类特殊的类方法,只有它才可以和类名相同,它没有返回值也不必用void来注明。一个类的构造方法通常都多于一个,其中默认的构造方法是无任何参数的构造方法。

3.举例:对象创建的完整结构

以系统已有的字体Font类为例,若要创建它的新对象font,可用以下语句:

Font font=new Font("Times Roman",Font.BOLD,36);

其中,新字体对象font调用构造方法Font()初始化时,采用的参数有:字体类型为Times Roman,字型为BOLD(粗体),字的大小为36

3.2.2  对象的使用

1.引用对象的变量

要使用对象的变量,只需在对象名之后加入“.”运算符,然后将变量      名置于运算符之后即可。其语法格式为:

对象名.变量名

例如一个Car类的对象CarRed,若想访问这个对象的变量名CarName时,只要输入:

CarRed.CarName

就可以引用CarRed对象的重要变量CarName

若有多层的结构,例如要知道CarRed对象中的发动机对象CarEng的型号EngType变量时,就应当采用多个“.”运算符来取得,具体引用如下。

CarRed.CarEng.EngType 

注意:1)只有在类定义中声明为public的变量才可以被任意访问。

2)凡是静态的(static)数据成员,都可直接用类进行访问,例如我们常

用的System.out中,out就是DataOutputStream类的静态数据成员,因

此就可以通过类System来调用它。

2.调用对象的方法

调用对象的方法与引用对象的变量相似,也是通过运算符“.”来实现,唯一区别在于:调用方法时可能需要传入参数及返回数据。

使用方法的格式如下:

对象名.方法名[(参数表)]

在对象名后,以“.”运算符来调用后面的方法,以小括号放置需要传入的参数,这些参数可以是数值、字符串或变量类型。例如:

Boat.setEngine("DOHC",16,Dis);

在这个实例中,Boat对象调用setEngine()方法;括号内传入的第一个参数是字符串"DOHC",第二个参数是整数16,最后一个是代表排气量的变量Dis,表示在具体执行时,会将排气量的具体值传入。

注意:1)调用方法时传入的参数必须符合类中所定义的数据类型,否则会出错。
2)当方法有返回值时,可利用与返回值相同的变量将返回值保存起来。
3)当方法有void修饰词时,方法无返回值。
4)当方法的括号内无参数时,就不必传入参数。

3.2.3  对象的撤销与清理

面向对象语言需要对所有对象进行跟踪,以便在对象不再使用时,将它从内存中清除。管理内存是一件既枯燥又易出错的工作。Java平台允许创建任意个对象(当然只要不受到系统资源的限制),而且当对象不再使用时自动清除它,这个过程就是所谓的“垃圾收集”。当对象不再有引用的时候,对象就会被作为垃圾收集的对象而清除。变量的引用通常在变量超出作用域的时候被清除。也可以通过设置变量为NULL来清除对象引用。这里要注意的是,程序中同一个对象可以有多个引用,对象的所有引用必须在对象被作为垃圾收集对象清除之前清除。

1.垃圾收集器

Java有一个垃圾收集器,它周期性地将不再被引用的对象从内存中清除。这个垃圾收集器是自动执行的,它扫描对象的动态存储区并将引用的对象作标记,将没有引用的对象作为垃圾收集起来,定期地释放那些不再需要的对象所使用的内存空间。

我们也可以在任何时候在Java程序中通过调用System类的gc( )方法来运行垃圾收集程序。例如,在产生大量垃圾代码之后或者在需要大量内存代码之前运行垃圾收集器。

2.撤销方法finalize

在一个对象被垃圾收集器收集之前,对象也可以调用自己的finalize( )方法,将自身从内存中清除,这个过程就是所谓的最后处理(即Finalize)。绝大多数程序员并不关心这个finalize( )方法的实现;但也有少数情况,程序员不得不执行finalize( )方法来释放资源

Finalize( )方法是Object类的一个成员函数,Object类处在Java平台类分级结构的顶部,而且所有类都是它的子类。子类重载了finalize( )方法来完成对象的最后处理工作。在有特殊需要时,可用下列格式声明重载的finalize( )方法,进行特殊的善后工作。

protected void finalize()throws Thrownable

如果编写了自己的finalize( )方法,它将自动覆盖超类中的finalize( )方法。它的工作过程一般是在自己的处理工作完成之后再调用超类的finalize( )方法。对于任何类,finalize( )方法都可以完成包括关闭已打开的文件,确保不遗留任何保存过的信息等功能。

3.3 

类也是面向对象方法的一个非常重要的概念。把众多的事物归纳、划分成类是认识世界时经常采用的方法。分类所依据的原则是抽象,也就是忽略事物的非本质特征,只注意那些与当前事物有关的本质特征,从而找出事物的共性并将具有共同性质的事物划分为同一个类。例如,人、树、山等都属于高度抽象后的类。

在面向对象方法中,也采用了类的概念,并给出下面的定义,类是具有相同语义特性的对象的集合,所谓相同的语义特性,其含义如下:

·    同一类中的对象有相同的属性(也称为成员变量,它描述该类的静态特性或结构特性)。

·    同一类中的对象有相同的方法(也称服务,它描述该类的行为特性或动态特性)。

·    同一类中的对象遵守相同的语义规则(共同的约束或规则)。

3.3.1  类的定义

类是对具有相同属性和方法的一组相似对象的抽象,或者说类是对象的模板。类的重要作用是客观定义了一种新的数据类型,它封装了一类对象的属性和方法,是这一类对象的原型,如图3-3所示。在面向对象程序设计中,可以用类创建对象,并可以重复使用类创建多个对象。一旦改变了类的定义,由此类所创建的对象也就具有了新的特征。类是一种逻辑结构,而对象是真正存在的物理实体。

3-3  面向对象的类

Java语言中,一切事物都用类来描述,类是Java的核心,也是Java的基本单元。对象是某个类的实例,其变量表示属性,方法表示功能,Java正是通过类和对象的概念来组织和构建程序的。

类是Java程序的基本单元,Java编译器无法处理比类更小的程序代码。当开始编写Java程序时,也就是要开始建立一个类。

类的声明格式如下:

[修饰符] class <类名> [extends 父类名] [implements 接口名] {

类主体

}

其中,class是定义类的关键字,<类名>是所定义的类的名字,extends表示该类继承了它的父类,父类名指明父类的名称,implements表示类所实现的接口,若实现多个接口,则接口名之间用逗号隔开。

修饰符分为访问控制符和类型说明符两部分,分别用来说明类的访问权限以及该类是否为抽象类或最终类。

类的访问控制符主要包括publicfriendly(默认修饰符)。public表示该类可以被任何类访问,并被称为公共类,当某一个类被声明为public时,此源程序的文件名必须与public所修饰的类名相同;当没有public修饰符时,即是默认类(friendly),表示该类只能被同一个包中的类所访问。

类的类型说明符包括finalabstract。用final修饰的类被称为最终类,表明该类不能派生子类;用abstract修饰的类被称为抽象类,抽象类不能定义对象,它通常被设计成一些具有类似成员变量和方法的子类的父类。

访问控制符和类型说明符可以一起使用,访问控制符在前,类型说明符在后。例如public final class Student就声明了一个公共最终类Student

3.3.2  类的结构

类是Java的抽象数据类型。它既实现了抽象性,也实现了数据与操作的封装。在Java编程中,类往往由类说明和类实现两部分组成。其中,类说明是类的外部接口,对用户是公开的;而类实现则是具体的实现代码,它对用户是隐藏的。类的结构如图3-4所示。

3-4  类的结构

3.3.3  类的成员变量

Java类包括两种类型的成员变量:实例成员变量和类成员变量,简称为实例变量和类变量。用static关键字声明的变量是类变量,没有使用static关键字声明的变量是实例变量。

声明一个成员变量就是声明该成员变量的名字及其数据类型,同时指定其他的一些特性。声明成员变量的格式为:

[修饰符] <变量类型> <变量名>

1.成员变量的修饰符

对一个成员变量,可以限定它的访问权限,也可以用static限定它为类变量,或者用以下的修饰符限定。

可访问控制符有3种:公共访问控制符public、私有访问控制符private、保护访问控制符protected

·    public公共访问控制符。用public修饰的域称为公共域。如果公共域属于一个公共类,则可以被其他所有类所引用。由于public修饰符会降低运行的安全性和数据的封装性,因此一般应减少public域的使用。

·    private私有访问控制符。用private修饰的成员变量(域)只能被该类自身所访问,而不能被其他任何类(包括子类)所引用。

·    protected保护访问控制符。用protected修饰的成员变量可以被3种类所引用,即该类自身;与它在同一个包中的其他类;在其他包中的该类的子类。使用修饰符protected的主要作用是允许其他包中它的子类来访问父类的特定属性。

对于上述访问权限控制符,将会在以后的章节里介绍。下面介绍非访问权限控制符

非访问控制符有4种:静态域修饰符static、最终域修饰符final、易失(共享)域修饰符volatile、暂时性域修饰符transient

1static:静态域修饰符。

static修饰的成员变量仅属于类的变量,而不属于任何一个具体的对象;静态成员变量的值是保存在类的内存区域的公共存储单元,而不是保存在某一个对象的内存区域。任何一个类的对象访问它时,取到的都是相同的数据;任何一个类的对象修改它时,也都是对同一个内存单元进行操作。

在生成每个类的实例对象时,Java运行系统为每个对象的实例变量分配一块内存,然后可以通过该对象来访问这些实例变量。不同对象的实例变量是不同的。

而对于类变量来说,在生成类的第一个实例对象时,Java运行系统对这个对象的每个类变量分配一块内存,以后再生成该类的实例对象时,这些实例对象将共享同一个类变量。每个实例对象对类变量的改变都会直接影响到其他实例对象。类变量可以通过类名直接访问,也可以通过实例对象来访问。

例如:

class StaticVar{

    static int counter = 100;

}

程序中声明了static变量counter,并赋值为100,则在程序中就可以通过StaticVar类访问这个变量;而且所有StaticVar类的对象的counter值都是一样的,无论在哪个对象里改变了counter的值,所有对象的counter值都跟着改变。

2final:最终域修饰符,用来声明一个常量。

最终域修饰符final是用来定义符号常量的。一个类的域(成员变量)如果被修饰符final说明,则它的取值在程序的整个执行过程中都是不变的。

例如:

class FinalVar{

    final int CONSTANT = 50;

}

程序中声明了常量CONSTANT并赋值为50。对于用final限定的常量,在程序中不能改变它的值。通常常量名用大写字母。

3transient:暂时性域修饰符,用来声明一个暂时性变量。

例如:

class TransientVar{

    transient TransientV;

}

在默认情况下,类中所有变量都是对象永久状态的一部分,当对象被存档时,这些变量必须同时被保存。暂时性域修饰符transient用来定义一个暂时性变量。其特点是:用修饰符transient限定的暂时性变量,将指定Java虚拟机认定该暂时性变量不属于永久状态,以实现不同对象的存档功能;否则,类中所有变量都是对象永久状态的一部分,存储对象时必须同时保存这些变量。

4volatile:易失(共享)域修饰符,声明一个共享变量。

例如:

class VolatileVar{

    volatile int volatileV;

}

易失(共享)域修饰符volatile是用来说明这个成员变量可能被几个线程所控制和修改。也就是说在程序运行过程中,这个成员变量有可能被其他的程序影响或改变它的取值。因此,在使用中要注意这种成员变量取值的变化。通常volatile用来修饰接受外部输入的域。由多个并发线程共享的变量可以用volatile来修饰,使得各个线程对该变量的访问能保持一致。

2.实例变量和类变量

1)实例变量。

声明类的成员变量时,前面不带static关键字的成员变量都是实例变量。例如,声明Myclass类中的实例变量aFloataInt时,可以使用如下形式。

class MyClass {

    float aFloat ;

    int  aInt;

}

声明了实例变量之后,每次创建类的一个新对象的时候,系统就会为该对象创建实例变量的副本,即该对象的每个实例变量都有自己的存储空间,然后就可以通过对象名访问这些实例变量。

2)类变量。

声明类的成员变量时,如果用static关键字进行修饰,则该变量是一个类变量。

类变量跟实例变量的区别是:不管为类创建了多少对象,系统仅在第一次调用类的时候为类变量分配内存,所有对象共享该类的类变量。因此,可以通过类本身或者某个对象来访问类变量。

【例3-1本例定义一个AnIntegerNamedX类,将它的成员变量x指定为一个类变量。

class AnIntegerNamedX {

    static int x;            //使用了static关键字

    public int x() {

       return x;

    }

    public void setX(int newX) {

       x = newX;

    }

}

下面再编写一个类,它包含了两种不同AnIntegerNamedX类型的对象,并且将x设置为不同的值,然后显示出来。

public class Compare_test{

    public static void main(String args[]){

        AnIntegerNamedX myX = new AnIntegerNamedX();

        AnIntegerNamedX anotherX = new AnIntegerNamedX();

        myX.setX(1);

        anotherX.x = 2;

        System.out.println("myX.x = " + myX.x());

        System.out.println("anotherX.x = " + anotherX.x());

    }

}

这里使用了以下两种方法访问x

·    使用setX来设置myXx数值。

·    直接赋值给anotherX.x

再运行Compare_test,其输出结果为:

myX.x = 2

anotherX.x = 2

从两个对象中输出x,其结果相同。这是因为x是一个类变量,类变量只存储唯一一个版本,它被该类的所有对象所共享,包括myXanotherX。当在任意一个对象中调用setX的时候,也就改变了该类所有对象所共享的x的值。

3.变量初始化

变量的初始化采用如下方式。

class Foo{

    int bar = 1;

    ……

}

对于类变量,当类被初始化的时候,变量被计算和赋值,这个过程只有一次;对于实例变量,则当每次创建这个类的实例的时候,变量都要被计算和赋值。

对于类变量初始化,其初始化表达式有如下诸多限制:不能引用这个类变量自身以及出现在其后面的类变量;不能引用实例变量;不能使用thissuper关键字。如果违反了上述任何一条,都将产生编译错误。

对于实例变量初始化,其初始化表达式的限制要少一些。它同样不能引用这个实例变量自身以及出现在其后面的实例变量。在实例变量的初始化表达式中还可以使用thissuper关键字。例如:

class Foo{

    int a = b*2;         //错误,不可引用后面的实例变量

    int b = 1;

    int c = b*2;         //可以,引用前面的实例变量

    static int d = b*2;//错误,类变量不能应用实例变量

3.3.4  类的方法

方法描述了类的行为。本小节将集中详细讲述关于方法的知识。

1.方法声明

Java的类中,方法声明的格式如下。

<修饰符> <返回值类型> <方法名>[参数列表][throws< exception>]

示例如下。

[Modifier] returnType methodName[paraList][throws exceptionList] {

        methodBody

}

其中,Modifier是方法的修饰符,有可访问控制符和非访问控制符两类;returnType指明方法的返回类型;methodName为方法名;paraList是方法的参数列表;exceptionList是异常列表。

方法名是用来调用此方法的名字,它可以是Java中的任意标识符。按照命名的约定,方法名应该是有意义的动词或动词短语,它的第一个字母一般要小写,其他有意义的单词的首字母要大写,其余字母小写。方法名可以和域名相同,因为它们处在不同的上下文中;但是一个类中不能有名字相同的两个方法,否则会产生编译错误。和域名不同,方法名不会被局部变量名隐藏。

对于一个方法,如果在声明中所指定的返回类型不为void,则在方法体中必须包含return语句,返回指定类型的值。返回值的数据类型必须和声明中的返回类型一致,或者完全相同,或者是它的一个子类;当返回类型是接口时,返回的数据类型必须实现该接口。

参数列表是可选的。参数列表的形式如下。

type name[,type name[,]]

省略号表示可以有多个参数。如果在一个参数列表中有两个参数名相同,就会产生编译错误。在调用方法的时候,会在方法调用堆栈中根据参数列表中的类型新建一些参数变量,然后将方法调用表达式中实际参数表达式的值逐一赋给这些参数变量。在方法体内,可以使用参数的名字来引用这些在调用时创建的参数变量。

throws子句列出了在方法执行中可能导致的异常。

方法体可以是一个实现了这个方法的代码块,也可以是一个简单的分号(;)。当且仅当方法的修饰符中有abstractnative时,方法体才是一个分号。

2.方法的参数

Java中经常出现的一个争论是:Java参数是传值还是传应用。

事实上,参数是传值的。通常,当程序员讨论传值和传引用时,它们是指语言的参数传递机制,C++同时支持这两种机制,因此,以前使用过C++的程序员开始好像不能确定Java是如何传递参数的。Java语言为了使事情变得简单,所以只支持参数传值的机制。

Java中的变量有两种类型:引用类型和原始类型。当它们被作为参数传递给方法时,它们都是传值的。这是一个非常重要的差别,下面的例3-2将说明这一点。首先,我们有必要定义一下传值和传引用。

传值意味着当参数被传递给一个方法或者函数时,方法或者函数接收到的是原始值的副本。因此,如果方法或者函数修改了参数,受影响的只是副本,原始值保持不变。

关于Java中参数传递的混乱,是因为很多Java程序员是从C++程序员转变过来的。C++有引用和非引用类型的变量,并且是分别通过传引用和传值来使用的;Java有原始和引用类型的变量,那么按照逻辑,Java对于原始类型使用传值,而对引用类型使用传引用。

C++Java中,当函数的参数不是引用时,参数传递的是值的副本(传值),但是对于引用类型就不同了。在C++中,当参数是引用类型,参数传递的是引用或者内存地址(传引用);而在Java中,传递一个引用类型的参数的结果只是传递引用的副本(传值)而非引用自身。

这是一个非常重要的区别。Java不考虑参数的类型,一律传递参数的副本。下面的例3-2程序说明了这一点,如果Java是传引用,那么程序中的swap( )方法将交换它们的参数。因为是传值,所以这个方法不是像期望的那样正常工作。

【例3-2程序示例。

class Swap{

    public static void main(String args[]) {

        Integer a, b;

        int i,j;

        a = new Integer(10);

        b = new Integer(50);

        i = 5;

        j = 9;

       

        System.out.println("Before Swap, a is " + a);

        System.out.println("Before Swap, b is " + b);

       

        swap(a, b);

        System.out.println("After Swap a is " + a);

        System.out.println("After Swap b is " + b);

        System.out.println("Before Swap i is " + i);

        System.out.println("Before Swap j is " + j);

       

        swap(i,j);

        System.out.println("After Swap i is " + i);

        System.out.println("After Swap j is " + j);

    }

    public static void swap(Integer ia, Integer ib){

        Integer temp = ia;

        ia = ib;

        ib = temp;

    }

   

    public static void swap(int li, int lj) {

        int temp = li;

        li = lj;

        lj = temp;

    }

}

本例程序的输出如下

Before Swap, a is 10

Before Swap, b is 50

After Swap a is 10

After Swap b is 50

Before Swap i is 5

Before Swap j is 9

After Swap i is 5

After Swap j is 9

因为swap( )方法接收到的是引用参数的副本(传值),所以对它们的修改不会影响到调用代码。

3.方法重载

方法重载指多个方法可以享有相同的名字,但是这些方法的参数必须不同,或者是参数个数不同,或者是参数类型不同。通过方法重载可以实现多态。

为了说明什么是方法重载,这里以System.out.println( )方法为例。这个方法的作用是把参数的内容打印出来,而且它能够接收charStringintdouble等类型的变量作为参数。示例如下:

System.out.println("Hello");

以及:

int i = 2;

System.out.println(i);

这两个代码片段在编译与执行时都没有任何错误。System.out.println( )方法已经被设计为接收不同类型的变量。这样,调用具有相同名字的方法就实现了对charStringintdouble等类型变量的输出;如果没有方法重载机制,可能就不得不用System.out.printlnChar( )方法来打印字符,用System.out.printlnString( )方法来打印字符串等。

【例3-3程序示例。

class MethodOverloading{

    void set( int x ){

             System.out.println("set(int): " + x);

    }

    void set( int x, int y ){

             System.out.println("set(int,int): " + x + "," +y);

    }

    void set(double d){

             System.out.println("set(double): " + d);

    }

    void set(String s){

             System.out.println("set(String): " + s);

    }

}

public class MethodOverloadingTest{

    public static void main( String args[ ] ){

        MethodOverloading mo = new MethodOverloading( );

        mo.set( 1 );

        mo.set( 2, 3 );

        mo.set( 12.56 );

        mo.set("Message");

    }

}

这个程序实现了4set( )方法,第1个有一个int类型的参数,第2个有两个int类型的参数,第3个有一个double类型的参数,最后1个有一个String类型的参数。分别调用这4set( )方法,输出如下:

set(int): 1

set(int,int): 2,3

set(double): 12.56

set(String): Message

值得注意的是:返回类型不能作为重载的依据。例如在一个类里实现如下两个方法。

void set(String s){

    System.out.println("set(String): " + s);

}

int set(String s){

    System.out.println("set(String): " + s);

}

编译时会得到方法已经定义的错误,如下所示:

MethodOverloadingTest.java:15: set(java.lang.String) is already defined in MdOverloading

        int set(String s){

            ^

1 error

这是因为编译器并不能根据返回类型的不同来选择应该调用哪一个方法。

4.方法的控制修饰符

方法的控制修饰符也分为两类:可访问控制符和非访问控制符。

可访问控制符有3种:公共访问控制符public、私有访问控制符private、保护访问控制符protected

非访问控制符有5种:抽象方法控制符abstract、静态方法控制符static、最终方法控制符final、本地方法控制符native、同步方法控制符synchronized

·    抽象方法控制符abstract用修饰符abstract修饰的方法称为抽象方法。抽象方法是一种仅有方法头,没有方法体和操作实现的一种方法。

·    静态方法控制符static用修饰符static修饰的方法称为静态方法。静态方法是属于整个类的类方法,而不使用static修饰限定的方法是属于某个具体类对象的方法。由于static方法是属于整个类的,所以它不能操纵和处理属于某个对象的成员变量,而只能处理属于整个类的成员变量,即static方法只能处理static的成员变量。

·    最终方法控制符final用修饰符final修饰的方法称为最终方法。最终方法是其功能和内部语句不能更改的方法,即最终方法不能重载。这样就固定了这个方法所具有的功能和操作,防止当前类的子类对父类关键方法的错误定义,保证了程序的安全性和正确性。所有被private 修饰符限定为私有的方法,以及所有包含在final类(最终类)中的方法,都被认为是最终方法。

·    本地方法控制符native用修饰符native修饰的方法称为本地方法。为了提高程序的运行速度,需要用其他的高级语言编写程序的方法体,那么该方法可定义为本地方法,用修饰符native来修饰。

·    同步方法控制符synchronized该修饰符主要用于多线程共存的程序中的协调和同步。

5.静态方法

static标识的方法称为静态方法,也称类方法。顾名思义,这个方法属于类。一个静态方法可以通过它所属的类名来调用。当然,这并不说明静态方法只能通过类名来调用,事实上,这个类的对象也可以通过“.”运算符来调用静态方法。

【例3-4程序示例。

class StaticSimple{

    static void go(){

        System.out.println("Go");

    }

 }

 public class StaticSimpleTest{

    public static void main(String[] args){

        StaticSimple.go();

        new StaticSimple().go();

    }

 }

输出如下:

Go

Go

一般来说,静态方法常常为应用程序中的其他类提供一些实用工具,在Java的类库中大量的静态方法正是出于此目的而定义的。

值得注意的是:静态方法不能访问非静态的成员变量,或者调用非静态的方法。

【例3-5程序示例。

class StaticSimple{

    int i = 0;

   

    void f(){

        System.out.println("Go…");

    };

    static void go(){

        i++;

        f();

       

    }

 }

 public class StaticSimpleTest{

    public static void main(String[] args){

        StaticSimple.go();

        new StaticSimple().go();

    }

 }

编译时将出现如下的错误信息。

D:>StaticSimpleTest.java:4: non-static variable i cannot be referenced from a static context

                i++;

                ^

StaticSimpleTest.java:9: non-static method f() cannot be referenced from a static context

                f();

                ^

2 error

一个类的main( )方法必须要用static来修饰,这是因为Java运行系统在开始执行一个程序前,并没有生成类的一个实例,它只能通过类名来调用main( )方法作为程序的入口。

3.3.5  定义和使用构造方法

声明一个变量时,可以同时使用赋值语句为它赋初值,而一个对象可能包括若干个成员变量,需要若干个赋值语句,把若干个赋初值的语句组合成一个方法,以便在创建对象时一次性地同时执行,这个方法就是构造方法。创建一个对象时,将调用这个构造方法完成对象的初始化工作。

所有的Java类都有构造方法,构造方法是与类同名的方法。创建对象的语句用new运算符开辟了新建对象的内存空间之后,将调用构造方法初始化这个新建对象。

构造方法的形式为:

[public] 类名(参数表) {}

【例3-6Student类为例,该类定义如下构造方法初始化它的几个成员变量。

Student (String x, String y, String z){//构造方法定义

        姓名=x; 性别=y; 学号=z;

        System.out.println(姓名+性别+学号);

}

定义了构造方法之后,就可以用如下的语句创建并初始化Student类的对象。

Student card1new Student(张三, , 2004034567);

Student card2new Student(李四, , 2003034666);

可见,构造方法定义了几个形式参数,创建对象的语句在调用构造方法时就应该提供几个类型顺序一致的实际参数,指明新建对象各成员变量的初始值。利用这种机制就可以创建不同初始特性的同类对象。

【例3-7前面已定义的Stack类的构造方法为:

public Stack() { items = new Vector(10); }

Java支持对构造方法的重载,这样一个类就可以有多个构造方法。下面是定义在Stack类中的另一个构造方法。这个构造方法是根据它的参数来初始化堆栈的大小的。

public Stack(int initialSize) {

    items = new Vector(initialSize);

}

可以看出,两个构造方法都有相同的名字,但是它们有不同的参数表。编译器会根据参数表中参数的数目以及类型区分这些构造方法,并决定要使用哪个构造方法初始化不同的对象。

下面的代码对于编译器是认识的,它使用了一个整型参数。

new Stack(10);

类似地,对于下面的代码,编译器会选择没有参数的构造方法或者默认的构造方法。

new Stack();

总之,构造方法是类的一种特殊方法,它的特殊性主要体现在如下几个方面:

·    构造方法的方法名与类名相同。

·    构造方法没有返回类型。

·    构造方法的主要作用是完成对象的初始化工作。

·    构造方法不能像一般方法那样用“对象”显式地直接调用,应该用new关键字调用构造方法为新对象初始化。