java函数
本文为书籍《Java编程的逻辑》1和《剑指Java:核心原理与应用实践》2阅读笔记
在编写代码过程中,如果需要经常做某一种操作,则类似的代码需要重复写很多遍。比如在一个数组中查找某个数,第一次查找一个数,第二次可能查找另一个数,每查一个数,类似的代码都需要重写一遍,很啰嗦。另外,有一些复杂的操作,可能分为很多个步骤,如果都放在一起,则代码难以理解和维护。计算机程序使用函数这个概念来解决这个问题,即使用函数来减少重复代码和分解复杂操作。
1.1 什么是函数
在数学中,我们都学过函数的概念,其基本格式是: y = f ( x ) y=f(x) y=f(x),表示,对于任一 x x x值都有唯一的 y y y值与之对应。编程中函数与数学中的函数相似,也由输入、操作和输出组成,但它表示的是一段程序代码,这段程序代码有一个名字,表示它的目的(类比 f f f),有零个或多个参数(类比 x x x),有可能返回一个结果(类比 y y y)。我们来看两个简单的例子:
public static int sum(int a, int b){
int sum = a + b;
return sum;
}
public static void print3Lines(){
for(int i=0; i<3; i++){
System.out.println();
}
}
第一个函数的名字叫做sum
,它的目的是对输入的两个数求和,有两个输入参数,分别是int
整数a
和b
,它的操作体是对两个数求和,求和结果放在变量sum
中(这个sum
和函数名字的sum
没有任何关系),然后使用return
语句将结果返回,最开始的public static
是函数的修饰符。第二个函数的名字叫做print3Lines
,它的目的是在屏幕上输出三个空行,它没有输入参数,操作是使用一个循环输出三个空行,它也没有返回值。
通过上面代码结构,总结出函数的基本语法结构:
修饰符 返回值类型 函数名字(参数类型 参数名字,…) {
操作
return返回值;
}
- 函数名字:名字是不可或缺的,表示函数的功能。
- 返回值类型:声明函数返回值数据类型,如果没有返回值则为
void
。 - 参数:参数有 0 0 0个到多个,每个参数由参数的数据类型和参数名字组成。
- 操作:函数的具体操作代码。
- 返回值:函数可以没有返回值,如果没有返回值则类型写成void,如果有则在函数代码中必须使用return语句返回一个值,这个值的类型需要和声明的返回值类型一致。
- 修饰符:
Java
中函数有很多修饰符,分别表示不同的目的,上面使用的修饰符为public static
。 - 局部变量:在
sum
中定义的变量sum
以及形参a
和b
,都是函数sum
的局部变量,局部变量会随着方法运行结束而销毁。
上面是函数定义的语法,在函数定义完后,还需要调用函数,函数才能被执行被调用。
java
中明确规定,任何函数都需要放在一个类中,类暂时还没有介绍,暂时把类当作是存放函数的容器。一个类中可以有多个函数,类里面可以定义一个特殊的方法,叫做main
的函数,这个函数有特殊的含义,表示程序的入口,String[] args
表示从控制台接收到的参数,可以暂时忽略它。Java
中运行一个程序的时候,需要指定一个定义了main
函数的类,Java
会寻找main
函数,并从main
函数开始执行。
public static void main(String[] args) {
…
}
1.2 进一步理解函数
1.2.1 参数传递
1、数组
基本类型和数组都能作为函数传递给函数,但是数组类型和基本类型不一样的是,基本类型传递的是值而数组传递的是地址,所以,基本类型不会对调用者中的变量造成任何影响,但数组不是,在函数内修改数组中的元素会修改调用者中的数组内容。比如下面代码:
public static void reset(int[] arr){
for(int i=0; i<arr.length; i++){
arr[i] = i;
}
}
public static void main(String[] args) {
int[] arr = {10,20,30,40};
reset(arr);
for(int i=0; i<arr.length; i++){
System.out.println(arr[i]);
}
}
在main
函数中,调用reset
函数后,reset
修改了数组保存的值,但是函数参数中的数组变量arr
和main
函数中的数组变量arr
存储的都是相同的位置,而数组内容本身只有一份数据,所以,在reset
中修改数组元素内容和在main
中修改是完全一样的。
2、可变长度参数
前面介绍的函数参数的个数都是固定的,有时函数也需要接收可变长度参数。比如一个求最大值函数,调用事先并不知道需要计算的数据有多少,有可能两个或者多个。java
支持可变长度的参数。
public static int max(int min, int ... a){
int max = min;
for(int i=0; i<a.length; i++){
if(max<a[i]){
max = a[i];
}
}
return max;
}
public static void main(String[] args) {
System.out.println(max(0));
System.out.println(max(0,2));
System.out.println(max(0,2,4));
System.out.println(max(0,2,4,5));
}
max
函数接受一个最小值,以及可变长度的若干参数,返回其中的最大值。可变长度参数的语法是在数据类型后面加三个点...
,在函数内,可变长度参数可以看作是数组。可变长度参数必须是参数列表中的最后一个,一个函数也只能有一个可变长度的参数。可变长度参数实际上会转换为数组参数,也就是说,函数声明max(int min, int...a)
实际上会转换为max(int min, int[] a)
,在main
函数调用max(0,2,4,5)
的时候,实际上会转换为调用max(0, new int[]{2,4,5})
,使用可变长度参数主要是简化了代码书写。
1.2.2 理解返回
return
的含义是显示结束函数执行,并返回结果(如果函数返回值类型定义为void
时,返回null
)。函数返回值类型为void
时,return
不是必需的,在没有return
的情况下,会执行到函数结尾自动返回。return
用于显式结束函数执行,返回调用方。return
可以用于函数内的任意地方,可以在函数结尾,也可以在中间,可以在if
语句内,可以在for
循环内,一旦运行return
语句,函数立马结束执行,返回调用方。函数返回值类型为void
,可以使用不带返回值,即return;
,其实和return null;
是等价的,含义是返回调用方,只是没有返回值而已。函数的返回值最多只能有一个,那如果实际情况需要多个返回值呢?比如,计算一个整数数组中的最大的前三个数,需要返回三个结果。这个可以用数组作为返回值,在函数内创建一个包含三个元素的数组,然后将前三个结果赋给对应的数组元素。如果实际情况需要的返回值是一种复合结果呢?比如,查找一个字符数组中所有重复出现的字符以及重复出现的次数。这个可以用对象作为返回值。
1.2.3 函数重载
每个函数都有一个名字,这个名字表示这个函数的意义,名字可以重复吗?在不同的类里,答案是肯定的,在同一个类里,要看情况。同一个类里,函数可以重名,但是参数不能完全一样,即要么参数个数不同,要么参数个数相同但至少有一个参数类型不一样,就是函数签名不同。同一个类中函数名相同但参数不同的现象,称为函数重载。
为什么需要函数重载呢?一般是因为函数想表达的意义是一样的,但参数个数或类型不一样。比如,求两个数的最大值,在Java
的Math
库中就定义了
4
4
4个函数,如下所示:
public static double max(double a, double b)
public static float max(float a, float b)
public static int max(int a, int b)
public static long max(long a, long b)
调用的匹配过程在之前介绍函数调用的时候,我们没有特别说明参数的类型。这里说明一下,参数传递实际上是给参数赋值,调用者传递的数据需要与函数声明的参数类型是匹配的,但不要求完全一样。什么意思呢?Java
编译器会自动进行类型转换,并寻找最匹配的函数,比如:
char a = 'a';
char b = 'b';
System.out.println(Math.max(a, b));
参数是字符类型的,但Math
并没有定义针对字符类型的max
函数,这是因为char
其实是一个整数,Java
会自动将char
转换为int
,然后调用Math.max(int a, int b)
,屏幕会输出整数结果
98
98
98。
在只有一个函数的情况下,即没有重载,只要可以进行类型转换,就会调用该函数,在有函数重载的情况下,会调用最匹配的函数。
1.2.4 函数递归
一个函数中,除了可以调用别的函数还可以调用自己,当调用自己时,就是函数的递归使用。
数学中有个著名的数列,叫斐波那契数列(Fibonacci
),它满足如下规律:
f
(
0
)
=
1
f
(
1
)
=
1
f
(
2
)
=
f
(
0
)
+
f
(
1
)
=
2
f
(
3
)
=
f
(
1
)
+
f
(
2
)
=
3
f
(
4
)
=
f
(
2
)
+
f
(
3
)
=
5
.
.
.
f
(
n
)
=
f
(
n
−
2
)
+
f
(
n
−
1
)
=
2
\begin{align} f(0)=&1\\ f(1)=&1\\ f(2)=&f(0)+f(1)=2\\ f(3)=&f(1)+f(2)=3\\ f(4)=&f(2)+f(3)=5\\ &...\\ f(n)=&f(n-2)+f(n-1)=2\\ \end{align}
f(0)=f(1)=f(2)=f(3)=f(4)=f(n)=11f(0)+f(1)=2f(1)+f(2)=3f(2)+f(3)=5...f(n−2)+f(n−1)=2
使用递归可以很方便实现斐波那契数列数列计算,计算代码如下:
package com.ieening;
public class FibonacciTest {
public static int calculateFibonacciRecursion(int index) {
if (index == 0 || index == 1) {
return 1;
} else {
return calculateFibonacciRecursion(index - 2) + calculateFibonacciRecursion(index - 1);
}
}
}
测试代码如下:
package com.ieening;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
public class TestFibonacciTest {
@Test
public void testFibonacciTest() {
assertTrue(FibonacciTest.calculateFibonacciRecursion(0) == 1);
assertTrue(FibonacciTest.calculateFibonacciRecursion(1) == 1);
assertTrue(FibonacciTest.calculateFibonacciRecursion(2) == 2);
assertTrue(FibonacciTest.calculateFibonacciRecursion(3) == 3);
assertTrue(FibonacciTest.calculateFibonacciRecursion(4) == 5);
}
}