六、java函数

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整数ab,它的操作体是对两个数求和,求和结果放在变量sum中(这个sum和函数名字的sum没有任何关系),然后使用return语句将结果返回,最开始的public static是函数的修饰符。第二个函数的名字叫做print3Lines,它的目的是在屏幕上输出三个空行,它没有输入参数,操作是使用一个循环输出三个空行,它也没有返回值。

通过上面代码结构,总结出函数的基本语法结构:

修饰符 返回值类型  函数名字(参数类型 参数名字,…) {
    操作
    return返回值;
}
  1. 函数名字:名字是不可或缺的,表示函数的功能。
  2. 返回值类型:声明函数返回值数据类型,如果没有返回值则为void
  3. 参数:参数有 0 0 0个到多个,每个参数由参数的数据类型和参数名字组成。
  4. 操作:函数的具体操作代码。
  5. 返回值:函数可以没有返回值,如果没有返回值则类型写成void,如果有则在函数代码中必须使用return语句返回一个值,这个值的类型需要和声明的返回值类型一致。
  6. 修饰符:Java中函数有很多修饰符,分别表示不同的目的,上面使用的修饰符为public static
  7. 局部变量:在sum中定义的变量sum以及形参ab,都是函数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修改了数组保存的值,但是函数参数中的数组变量arrmain函数中的数组变量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 函数重载

每个函数都有一个名字,这个名字表示这个函数的意义,名字可以重复吗?在不同的类里,答案是肯定的,在同一个类里,要看情况。同一个类里,函数可以重名,但是参数不能完全一样,即要么参数个数不同,要么参数个数相同但至少有一个参数类型不一样,就是函数签名不同。同一个类中函数名相同但参数不同的现象,称为函数重载。

为什么需要函数重载呢?一般是因为函数想表达的意义是一样的,但参数个数或类型不一样。比如,求两个数的最大值,在JavaMath库中就定义了 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(n2)+f(n1)=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);
    }
}

  1. 马俊昌.Java编程的逻辑[M].北京:机械工业出版社,2018. ↩︎

  2. 尚硅谷教育.剑指Java:核心原理与应用实践[M].北京:电子工业出版社,2023. ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值