函数和方法
如果我们经常要进行一些相似的处理过程,就可以把这个处理过程封装为函数。
函数可以被多次重复调用,从而实现代码重用和隔离的目的。
在面向对象的语言中,函数经常和对象绑定在一起,为区分起见,这时它被称为方法。
因为java是完全面向对象的,函数必须从属于某个类。所以java中的函数都被称为方法。
如果方法前以 static 修饰,则称为静态方法,可以粗略地认为,它与 c 语言的函数的概念大致相等了。
方法可以在其括号中列出调用它时需要准备的参数,叫形式参数。
方法也可以有一个返回值(或没有,此时写void)。
下面,我们调用方法 f,求两个整数的末位是否相同。
1 public class A0403 2 { 3 static boolean f(int a, int b){ 4 int a1 = a % 10; 5 int b1 = b % 10; 6 return a1==b1; 7 } 8 9 public static void main(String[] args){ 10 System.out.println(f(101,12)); 11 System.out.println(f(65432,12)); 12 } 13 }
这个方法接受两个参数,都是整数,它进行一系列的处理后,返回一个布尔值。
return 语句结束 f 方法的执行,并返回一个指定的值。
在遇到 return 语句后,f 方法的其它代码就不再执行,而是要返回调用它的那个方法中。当然,在此方法中,return 语句后恰好没有更多的语句了。
如果一个方法定义了返回值,它在结束执行之前就一定会遇到 return 语句,否则会引发编译错误。
形参独立原理
如果在被调函数中改变了形参的值,会不会影响调用的一方呢? 不会!
我们看这个例子:
1 public class A0404 2 { 3 static void f(int x){ 4 int sum = x % 10; 5 x /= 10; 6 sum = sum * 10 + x % 10; 7 x /= 10; 8 sum = sum * 10 + x % 10; 9 System.out.println(sum); 10 System.out.println("x=" + x); 11 } 12 13 public static void main(String[] args){ 14 int a = 368; 15 f(a); 16 System.out.println("a=" + a); 17 } 18 }
这里的函数f的功能是:把传给它的3位数 x 的数位反转并输出。
为了观察,我们额外输出了 a 的值,以及 x 的值。
通过结果可以看到,虽然 x 的值在计算过程中发生了变化,但这并不会影响 a 的值。
实际上,在调用 f 之前,要为它准备需要的参数,这些参数必须新创建,所以才称为形式参数。
也就是说,在没有调用 f 函数的时候,这些形参变量是不存在的,在多次调用 f 的时候,这些形参就会被创建多次。
创建形参后,把实参的值(这里就是 a 的值)拷贝给它,然后才开始 f 的执行。
当 f 执行结束后,形参变量会被自动释放掉。
从更底层的机制看,这个形参的分配与释放的过程是通过栈来完成的。
函数调用前,要把返回的位置,需要的参数等信息压栈,在函数执行完毕后,自动弹栈,恢复执行前的样子。
如果被调用的函数还会去调用其它函数,这个过程还会继续上演。
这样,栈可能就会越涨越高,但函数的执行总有结束的时候,那时栈就会落回来。
如果由于某种设计失误,导致函数调用一直没能正确返回,而是不断地调用其它的函数,就会导致栈的溢出,这是很常见的程序错误。
从参数传递的原理上我们看到,实参与形参是各自独立的变量,除了开始的时候,实参拷贝给形参外,它们再不会有任何联系。这叫做
"形参独立原理"。
这种设计为我们省去了许多麻烦,但有时我们可能会想让被调方与主调方共享一个变量,而不是各自独立,这怎么办呢?
答案是传指针,在java中叫做:引用。
引用作为参数
引用的本质是持有另一个对象的地址。
如果对一个引用进行复制,只不过是两个引用指向了同一个对象,并没复制对象本身。
引用与面向对象的体系紧密联系在一起,所以要等到学了初步的面向对象的知识后我们才能更好地理解它。
但这里,我们可以先看看数组的行为,来窥其端倪。
数组就是一种对象类型,我们定义的数组变量,实际上是指向实际数组对象的指针,或说:引用。
1 public class A0404 2 { 3 static void f(int[] x){ 4 for(int i=0; i<x.length; i++){ 5 if(x[i] < 0) x[i] = -x[i]; 6 } 7 } 8 9 public static void main(String[] args){ 10 int[] a = {5, -3, 6, 10, 0, -15}; 11 f(a); 12 System.out.println(java.util.Arrays.toString(a)); 13 } 14 }
这里的形参 x,实参 a 都不是数组本身,它们是指向数组的引用。
虽然 x 和 a 也是按照同样的规则,不会相互影响,但由于它们指向了同一个对象,这就引起了复杂的现象。
从结果上,我们可以观察到,在 f 方法中修改了数组对象的值,在主调方打印数组时也看到了这些变化。
其原理图:
引用是一种在主调函数和被调函数间共享数据的常用手段。
在上面的这个例子中,引用变量本身并没有发生变化。发生变化的是引用所指向的对象,所以“形参独立性原理”并没有因此而破坏。
我们可能会看到某些材料上写着: java 有两种传递参数的方式,一种是传值(传拷贝),另一种是传引用。
这可能会产生一些误导。实际上Java只有一种传递参数的方式,就是传值。
而这个被传递的值有可能会恰好是一个引用类型,就会引起一些类似共享变量的效果,其实并没有什么特殊的秘密,也不应该算是“另一种”传递方式。