方法是类或对象行为特征的抽象,方法是类或对象最重要的组成部分。 从功能上来看,方法类似于C语言中的函数。但是,Java里方法不能单独存在,所有的方法都必须定义在类里。方法在逻辑上要么属于类,要么属于对象。
一、方法的所属性
不论从定义方法的语法来看,还是从方法的功能来看,都不难发现方法和函数之间的相似性。实际上,方法的确是由传统函数发展而来的,方法与传统的函数有着显著不同:在结构化编程语言里,函数是一等公民,整个软件由一个个的函数组成;在面向对象的编程语言里,类才是一等公民,整个系统由一个个的类组成。 因此,在Java里,方法不能独立存在,方法必须属于类或对象。
Java语言是静态的。一旦也类定义完成后,只要不再重新编译这个类文件,该类和该类的对象所拥有的方法是固定的,永远不会变。
因为Java里的方法不能独立存在,它必须属于一个类或一个对象,因此方法也不能像函数那样被独立执行,执行方法时必须使用类或对象来作为调用者,即所有方法都必须使用“类.方法”或“对象.方法”的形式来调用 。这里产生一个问题:同一个类里不同方法相互调用时,不就可以直接调用吗?这里需要指出:同一个类的一个方法调用另外一个方法时,如果被调方法是普通方法,则默认使用this作为调用者;如果被调用方法是静态方法,则默认使用类作为调用者。 也就是说,表面上看上去某些方法可以被独立指向,但实际上还是使用this或者类来作为调用者。
永远不要把方法当成独立存在的实体,正如现实世界由类和对象组成,而方法只能作为类和对象附属,Java语言里的方法也是一样。Java语言里方法的所属主要体现在如下方面。
①方法不能独立定义,方法只能在类体里定义
②从逻辑意义上来看,方法要么属于该类本身,要么属于该类的一个对象
③永远不能独立执行方法,执行方法必须使用类或对象作为调用者。
使用static修饰的方法属于这个类本身,使用static修饰的方法既可以使用类作为调用者也可以使用对象作为调用者来调用。但值得指出的是,因为使用static修饰的方法还是属于这个类的,因此使用该类的任何对象来调用这个方法时会得到相同的执行结果,这是由于低层依然是使用这些实例所属的类作为调用者。
二、方法的参数传递机制
Java里方法是不能独立存在的,调用方法也必须使用类或对象作为主调者。如果声明方法时包含了形参声明,则调用方法时必须给这些形参指定参数值,调用方法时实际上传给形参的参数值也被称为实参。
Java的实参值如何传入方法的呢?这是由Java方法的参数传递机制来控制的,Java里方法的参数传递方式只有一种,值传递,所谓值传递,这就是将实际参数值的副本(复制品)传入方法内,而参数本身不会受到任何影响。
下面程序演示了方法参数传递的效果。
public class PrimitiveTransferTest
{
public static void swap(int a,int b)
{
//西面三行代码实现a、b变量的值交换
//定义一个临时变量来保存a变量的值
int tmp = a;
//把b的值赋给a
a = b;
//把临时变量tmp的值赋给a
b = tmp;
System.out.println("swap方法里,a的值是" + a + ",b的值是" + b);
}
public static void main(String[] args)
{
int a = 6;
int b = 9;
swap(a , b);
System.out.println("交换结束后,变量a的值是" + a + ",变量b的值是" + b);
}
}
输出结果:
swap方法里,a的值是9,b的值是6
交换结束后,变量a的值是6,变量b的值是9
swap()方法里a和b的值是9、6,交换结束,变量a和b的值依然是6、9。从这个运行结果看出,main()方法里的变量a和b,并不是swap()方法里的a和b。正如前面讲的,swap()方法的a和b只是main()方法里里变量a和b的复制品。
在main()方法中调用swap()方法时,main()方法还未结束。因此,系统分别为main()方法和swap()方法分配两块栈区,用于保存main()方法和swap()方法的局部变量。 main()方法栈区中a、b变量的值分别赋给swap()方法栈区中a、b参数(就是swap()方法的a、b形参进行了初始化)。
class DataWrap
{
int a;
int b;
}
public class ReferenceTransTest
{
public static void swap(dw)
{
//下面三行代码实现dw的a、b两个成员变量的值交换
//定义一个临时变量来保存dw对象的a成员变量的值
int tmp = dw.a;
把dw对象的b成员变量的值赋给a成员变量
dw.a = dw.b;
//把临时变量tmp的值赋给dw对象的b成员变量
dw.b = tmp;
System.out.println("swap方法里,a成员变量的值是" + dw.a + ",b成员变量的值是" + dw.b);
}
public static void main(String[] args)
{
DataWrap dw = new DataWrap();
dw.a = 6;
dw.b = 9;
swap(dw);
System.out.println("交换结束后,a成员变量的值是" + dw.a + ",b成员变量的值是" + dw.b);
}
}
执行结果:
swap方法里,a成员变量的值是9,b成员变量的值是6
swap方法里,a成员变量的值是9,b成员变量的值是6
从结果来看,在swap()方法里,a、b两个成员变量的值被交换成功。不仅如此,main()方法里的a、b成员变量的值也被交换了。这容易造成一种错觉:调用方法时,传入swap()方法的就是dw对象本身,而不是它的复制品。但这只是一种错觉。
程序从main()方法开始执行,main()开始创建了一个DataWrap对象,并定义了一个dw引用变量来指向DataWrap对象,这是与基本类型不同的地方。
创建一个对象时,系统内存中有两个东西:堆内存中保存了改对象本身,栈内存中保存了引用该对象的引用变量。接着程序通过引用来操作DataWrap对象,把该对象的两个成员分别赋值为6、9。
接下来,main()方法中调用swap()方法,main()方法并未结束,系统会为main()和swap()开辟出两个栈区,用于存放main()和swap()方法的局部变量。同样采用值传递的方式:把main()方法里的dw变量的值赋给swap()方法的dw形参,从而完成swap()方法的dw形参的初始化。值得指出的是,main()方法中的dw是一个引用(也就是指针),它保存了DataWrap对象的地址值,当把dw的值赋给swap()方法的dw形参后,既让swap()方法的dw形参保存这个地址值,即也会引用到堆内存中的DataWrap对象。
这种参数传递时不折不扣的值传递方式,系统一样复制了dw的副本传入swap()方法,但关键在于dw只是一个引用变量,所以系统复制了dw变量,但并未复制DataWrap对象。
swap()方法操作dw形参时,由于dw只是一个引用变量,故实际操作的还是堆内存中的DataWrap对象。
为了更好证明main()方法中的dw和swap()中的dw是两个变量,在swap()方法的最后一行增加如下代码:
//把dw直接赋值为null,让它不再指向任何有效地址
dw = null;
指向上面代码结果是swap()方法中的dw变量不再指向任何有效内存。main()方法调用完swap()方法后,再次访问dw()变量的a、b两个成员变量,依然可以输出9、6。
三、方法重载
Java允许一个类里定义多个同名方法,只要形参列表不同就行。如果同一个类中包含了两个以上方法的方法名相同,但形参列表不同,则被称为方法重载。
Java中确定一个方法需要三个要素。
①调用者,也就是方法的所属者,既可以是类,也可以是对象
②方法名,方法的标识
③形参列表,系统会根据传入的形参列表匹配。
方法重载的要求就是两同一不同:同一个类中方法名相同,参数列表不同。
public class Overload
{
//下面定义了两个test()方法,但方法的形参列表不同
public void test()
{
System.out.println("无参数");
}
public void test(String msg)
{
System.out.println("重载的test方法" + msg);
}
public static void main(String[] args)
{
Overload o1 = new Overload();
//调用test()没有传入参数,因此系统调用的没有参数的test()方法
o1.test();//输出无参数
//调用test()时传入了一个字符串参数
//因此系统调用上面带一个字符串参数的test()方法
o1.test("hello");//输出 重载的test方法hello
}
}
虽然test()方法两个方法名相同,但因为它们的形参不同,所以系统可以正常区分出这两个方法。