第五章.对象的行为

一、方法调用栈
所有的方法调用都维护在一个称为调用栈的结构中。当前正在执行的方法位于调用栈的顶部。当前方法执行完成后,就从调用栈的顶部移除,控制流程返回栈中的前一个方法。当一个新方法被调用后,这个新的方法就放在调用栈的顶部。
在Java程序中,第一个被调用的方法就是main(),该方法是JVM调用的。因此,main()方法总是在调用栈的底部。
假如main()方法调用了turnOn()方法,然后turnOn()方法又调用了setVolume()方法,最后setVolume()方法又调用println()方法。因为println()方法是在调用栈的顶部,那么控制流程现在就在println()方法中。setVolumn()方法等待println()完成,turnOn()方法等待setVolume()完成,沿着调用栈向下依此类推,如图5.1所示。
在这里插入图片描述
图5.1方法调用栈
如果一个Java程序是多线程应用程序,那么它可以有多个调用栈。
二.调用方法
一个方法被调用时,该方法就被放在调用栈的顶部,直到方法执行完成。当一个方法正在执行时,会发生三种情况:
 方法返回一个值。在这种情况下,一个基本数据类型或引用类型被传回给方法的调用者。
 方法不返回一个值。在这种情况下,返回值被声明为void。
 方法抛出一个异常给方法的调用者。
在三种情况下,控制流程都会跳转给方法的调用者。
注意事项:因为Java是一个严格的面向对象编程语言,所以Java中的方法只能出现在类中。在某些编程语言中,方法可以以全局的形式出现,可以在任何时候调用。在Java中,没有声明为静态(static)的方法只能在类的实例中调用。
如果想编写一个不需要实例化一个类、可以被任何时候任何人调用全局的方法,我们需要使用静态方法。
三.方法签名
通过查看方法的签名,我们可以了解到调用一个方法时所需知道的一切。方法的签名包括方法名、参数列表、返回值的数据类型等信息。
注意,方法的签名不包含方法体内的任何语句。签名只是方法的声明部分。
我们按照在声明方法时出现的顺序,列出方法签名中每个部分如下:
 访问修饰符。访问修饰符的可能值包括public、private、protected或默认访问修饰符(即没有访问修饰符)。public访问修饰符允许从任何地方调用该方法。private访问修饰符意味着除了在类内部以外,没有人可以调用它。protected以及默认访问修饰符分别适用于继承和包,我们将在后面章节中详细讨论。
 可选修饰符。方法签名的下一个部分是可选的修饰符,包括static、final、abstract、native以及synchronized。native方法用于编写一个Java访问映射到用不同编程语言编写的方法,本书不做讨论。其它修饰符我们将在后续章节中详细讲解。类的方法可以不使用这些可选修饰符,也可以使用多个修饰符。
注意事项:
访问修饰符和可选修饰符的次序是随意的。例如,我们也可以按照这种格式声明main()方法:static public void main(String [] args)。
但是,我们经常会看到访问修饰符出现在可选修饰符前面,因为这是Java程序员更喜欢的一种习惯。
 返回值。方法签名必须包括返回值的类型。如果方法不需要返回一个值,就使用void。否则,就要指定返回值的数据类型。返回值的类型可以是八种基本数据类型之一或者一个引用类型。这意味着我们可以从方法中返回任何类型的数据,因为Java中的变量要么是八种基本数据类型之一,要么是对一个对象的引用。
 方法名。方法名必须出现在返回值之后。方法名可以是任何有效的Java标识符。Java命名惯例要求方法是混合大小写的驼峰法则,即方法名的第一个单词的第一个字母为小写,其它单词的第一个字母为大写。例如,main、toString、getDay、setPreferredSize。
 形式参数列表。在方法名的后面必须是用一对括号括起来的形式参数列表。方法被调用时,数据可以通过该方法的调用者传递进来。这个传递进来的数据被复制到形式参数中。一个形式参数由一个数据类型和一个标识符组成。例如,下面的方法签名声明了两个形式参数,一个是int类型,一个是float类型:
public float divide(int x, float f)
 抛出的异常列表。方法可以抛出一个异常给方法的调用者。当方法中出现一个方法本身不能或者不想处理的问题时,就抛出一个异常。如果方法抛出一个受检查的异常,该异常必须在方法签名中使用throws关键字声明。在throws关键字后可以声明多个用逗号分隔的异常。例如,下面的方法签名声明该方法可以抛出两个可能的异常:
public void readFromFile() throws IOException, SecurityException
如果一个方法不需要使用throws关键字声明任何异常,那么签名的这部分就可以不写。
一些方法签名的示例:
public int getDay()
private void setName(String f, String g)
int calibrate(double radius, int multiplier, boolean metric)
public void addListener(Listener a) throws TooManyListenersException
public Employee findEmployee(int number) throws SQLException
四. 形式参数和实际参数
一个方法的签名包括一个形式参数(Parameter)的列表,形式参数列表用于声明传递到方法中的数据的类型。传递给形式参数的数据称为实际参数(Arguement)。当方法被调用时,实际参数必须传递给形式参数列表中的每个形式参数。
五.按值调用
当实际参数传递给形式参数时,实际参数的数据被复制给形式参数。在编程中,在方法调用之间复制数据的过程称为按值调用。
注意事项:在Java中,我们不需要指定要传递的实际参数使用按值调用,因为它是自动发生的,实际上也是唯一的选择。其它编程语言中可能会使用按引用调用或按指针调用,这种情况下实际参数不是复制给形式参数。在Java中,不能用按引用调用或按指针调用。不管传递给方法的实际参数是什么类型,相关的形式参数都会得到该数据的一份拷贝,这就是按值调用的工作原理。
当使用按值调用时,在方法中改变形式参数并不会改变实际参数。
在Java中,一个很重要的特点是:变量要么是八种基础类型之一,要么是引用类型。如果实际参数是基础类型,那么它最大是64位(double或者long)。如果我们想传给方法的数据是一个很大的对象,那么请记住:不是对象被传入,而是对象的引用被传入。这个引用在大多数情况下是32位,并且不会超过64位。结果是引用被复制,而不是大量数据被复制。所以,在Java中,通过按值调用传递复制的最大数据是64位。在今天的计算世界中,复制64位的数据几乎不用考虑性能和开销。
假如我们确实要改变传递给方法的实际参数,只能通过其它的方法。我们已经知道,如果形式参数是一个对象的引用,那么,方法就可以用这个引用来做任何它想要对象做的事情(当然,这个取决于对象的成员变量和方法的访问修饰符)。方法可以改变被指向的对象的数据,调用对象中的方法。按值调用导致的唯一限制是方法不能改变引用的指向。
注意事项:OOP初学者常常容易混淆的一个问题,是在编写一个没有main()方法的类时。一个没有main()方法的Java类不是一个程序。例如,我们不能执行Radio类,因为它是收音机的一个简单的描述。如果我们试图用"java Radio"运行,我们会从JVM得到一个错误消息,指出找不到main()方法。这意味着Radio类只能被其它需要它的类所使用。在一个大型Java程序中,可能有很多很多类,但是肯定只有一个类有main()方法。
六.方法重载
Java中允许方法被重载。当一个类有两到多个同名但是有不同参数列表的方法时,就是方法重载。多个方法有相同的名字看起来是不必要的,但是方法重载在Java及其他编程语言中使用却很频繁。如果不能使用方法重载,那么每一个println()方法就必须有一个唯一的名称,我们可以重载一个方法,只要方法的形式参数列表对编译器来说是截然不同的,从而使编译器能够区分我们想调用的方法是哪一个。如果方法的形式参数个数是不同的,那么重载就肯定是有效的。如果我们仅仅是改变参数的名称,那么重载就是无效的。上面的两个方法有相同的名称和相同数目的形式参数,并且形式参数出现的顺序相同。编译器不能区分两个方法,因而会产生一个编译器错误。改变返回值也不影响重载是否有效。但是,改变形式参数的顺序与改变形式参数列表的效果一样。
七.构造器
构造器是类中的一个特殊的方法,该方法在对象实例化时被调用。构造器的用途是当对象实例化时,初始化对象中的成员变量。
当对象使用new关键字实例化时,JVM为对象分配内存,并初始化。因此,对象的成员变量值被设置为初始值(参见表4-1)。如果没有构造器,我们就得自己初始化所有的成员变量,以确保对象的数据是有意义的。在对象实例化时,构造器给我们提供了构造对象的机会,以确保对象的成员变量都含有有意义的数据。
构造器与方法不同之处在于构造器必须满足如下两个属性:
 构造器的名称必须与类名相同。
 构造器不能声明返回值,也不能返回void。
当为类添加多个构造器时,应用了方法重载的规则。每个构造器必须有一个唯一的形式参数列表,以与其它构造器相区别。
我们应该注意到,本书迄今为止,当使用关键字new实例化一个对象时,都有一个括号出现。
前面我们一直没有对这些括号进行解释,现在我们可以开始了。当我们看到括号时,它看起来像是一个方法被调用。这正好是我们用new关键字时发生的事情,当然并不是方法被调用,而是类中的构造器之一被调用。
可以调用构造器的唯一时机是对象被实例化时。构造器与方法类似,但是它不是方法。二者的行为区别很大,我们将在《继承》一章中讲解。
我们应该可以发现,在本节之前的所有类都没有添加构造器。那么,我们是不是应该给每个类添加一个构造器呢?答案是:每个类都必须有一个构造器。但是,从以前的示例我们已经知道,不添加构造器好像不会发生什么问题。这是因为,如果我们没有给类添加构造器,编译器会自动为我们添加一个默认构造器。那么,编译器怎么知道我们想让默认构造器干什么呢?编译器并不知道。实际上,编译器自动为我们添加的默认构造器的形式参数列表是空的,并且什么都不执行。这似乎有点可笑,有一个构造器但是什么都不做,岂不是浪费代码么?这是因为每个类都必须有一个构造器。当我们实例化类时,new运算符必须调用构造器。编译器要为类添加默认构造器纯粹是为了方便。在几乎所有我们可以找到的开发条件下,我们都会为所有类添加至少一个构造器。在编写自己的构造器之前,我们首先来快速了解一下默认构造器。
八.默认构造器
如果我们编写了一个类,但是在类中没有添加构造器,编译器会给类添加一个默认的构造器。这个默认的构造器用public访问修饰符限定,没有形式参数,而且不做任何事情。
默认的构造器仍然遵循构造器的规则:构造器的名称与类名相同,没有返回值。同时,默认的构造器不包含任何语句。
如果我们自己在类中添加了一到多个构造器,那么编译器就不会为类添加默认构造器。
因为Television类中仅有一个构造器,所以实例化一个新的Television对象的唯一方法是传递一个整型参数做为构造器的实际参数。
这条语句试图调用一个无形式参数的构造器,但是Television类中没有无形式参数的构造器,所以会导致一个编译器错误。正如Java API中很多类所展示的一样,写一个没有无形式参数的构造器是很常见的。
如果我们给类添加了一个构造器,编译器就不会为类添加默认构造器。
九.构造器的使用
当一个对象使用关键字new实例化时,构造器就被调用了。一个类可以有多个构造器,这种情况下,要调用哪个构造器就取决于new运算符所用的实际参数。
如果一个类有一个构造器,那么就只有一种方法来初始化该类的一个对象。
十.在构造器中使用this
为了避免重复代码,我们可以用所有的构造器调用一个特定的构造器,让这个特定的构造器完成重复代码的工作。在构造器中,可以用this关键字来调用同一个类中的其它构造器。
注意事项:如果一个构造器用this关键字来调用本类中的其它构造器,那么这个this语句必须是本构造器的第一行,否则,会产生一个编译器错误。
在Java中,this关键字有两种不同的用处。构造器内的使用的this关键字,与代表每个对象本身的this引用是不相同的。
 在Java中,方法必须出现在类中。方法的签名描述方法的名称、访问修饰符、返回值、要传递给方法的形式参数以及方法可能会抛出的任何可检查的异常。
 在Java中,实际参数通过按值调用的方法传递给方法,即实际参数的一份拷贝传给方法。
 方法可以被重载,允许类有多个同名的方法,同时形式参数列表是不同的。
 每个类至少有一个构造器,构造器是一个类实例化时被调用的特殊类型的方法。构造器的名称必须与类的名称匹配,并且没有返回值声明。
 如果一个类没有声明构造器,编译器将为该类添加一个默认的构造器。默认的构造器没有形式参数,并且不做任何事情。
 一个构造器可以使用this()语句,来调用同类中的其它构造器。这种情况下,this()语句必须是构造器的第一条语句。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值