Java基本语法_05_方法


前面有了数据类型和变量,有了运算符和控制语句,可以说有了对数据的基本操作。那么有这样一种情况,以计算器的加法为例,输入不同的数字(假设变量就是输入),总是能得到相应的结果。

以下面的代码为例,以目前学到的知识,对于不同的数字,只能书写类似的代码片段,那么这时候就会很复杂,相似的代码片段太多;并且如果将加法修改为减法,此时就需要修改所有的代码,不太现实:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Example{
public static void main(String[] args){
int a1 = 3, b1 = 5;
int sum1 = 0;
sum1 = a1 + b1;
System.out.println(sum1);

int a2 = 3, b2 = 5;
int sum2 = 0;
sum2 = a2 + b2;
System.out.println(sum2);
}
}

这时候,需要一个概念:代码复用。将相似的代码抽取出来,封装成方法,需要用到的时候,调用它,以加法为例,加法的操作本质上是一样的,只不过需要不同的因子,这个时候,只需要将因子传给方法即可。

可以对比前几篇文章中的System.out.println()方法,括号里面是什么,那么在控制台输出的内容就是什么,括号里面的内容就是参数,这个方法的作用就是将参数打印到控制台上。简单的说:

  1. 方法就是一段代码,并且这段代码片段可以完成某个特定的功能,并且可以被重复的使用。
  2. 方法定义在类体当中、方法体之外。在一个类当中可以定义多个方法,方法编写的位置暂时没有先后顺序,可以随意。但方法体中的代码有顺序。(了解,面向对象部分再介绍)
  3. 注意,方法体当中不能再定义方法。

1. 方法的基础语法

方法的语法结构如下:

1
2
3
[修饰符列表] 返回值类型 方法名(形式参数列表){
方法体;
}
  1. 修饰符列表(参考后面的面向对象部分

    • 可选项,不是必须的;

    • 目前统一写成:public static

    • 方法的修饰符列表当中有static关键字的话,怎么调用这个方法?

      • 类名.方法名(实际参数列表);

      • 方法名(实际参数列表);

        注意,调用本类中的方法,可以不用写类名.

  2. 返回值类型

    • 什么是返回值?

      一个方法是可以完成某个特定功能的,这个功能结束之后需要有一个反馈,大多是都是需要返回最终执行结果的,执行结果可能是一个具体存在的数据,而这个具体存在的数据就是返回值。

    • 返回值类型?

      返回值是一个具体存在的数据,数据都是有类型的,此处需要指定的是返回值的具体类型,如int,boolean等等。除了基本数据类型,也可以是引用数据类型。也可以不返回任何结果,这个类型就是void

    • 返回值类型若不是void,表示这个方法执行结束之后必须返回一个具体的数值,即return 数值;但是调用者那里可以选择不接收

    • 注意,如果返回值类型是void,不可以写 return 数值;这种语句,可以写return ;这样的语句,表示该方法结束,后面的语句不执行。

  3. 方法名

    • 只要是合法的标识符就行;
    • 方法名最好见名知意;
    • 方法名最好是动词;
    • 方法名要求首字母小写,后面每个单词首字母大写(小驼峰)。
  4. 形式参数列表(形参)

    • 形参是局部变量;
    • 形参的个数是:0-N个;
    • 多个形参之间用“逗号”隔开;
    • 形参中起决定作用的是形参的数据类型,形参的名字就是局部变量的名字;
    • 方法在调用的时候,实际给这个方法传递的真实数据被称为:实际参数(实参);
    • 实参列表形参列表必须满足:数量相同,类型对应相同。
  5. 方法体

    方法体必须由大括号括起来,方法体当中的代码有顺序,遵循自上而下的顺序依次执行。

2. 内存分配

方法的作用是代码复用,那么方法中的变量则涉及到内存分配的问题。因为方法可以多次调用,那么里面的变量在多次调用后的关系是什么样呢?方法以及方法执行的过程中的内存分配以及内存变化是怎么样子的呢?

  1. JVM在内存划分上有三块主要的内存空间(除了这三块之外还有其他的内存空间):方法区内存堆内存栈内存

    basic_01.png (1619×632) (gitee.io)

  2. 方法只定义不调用,是不会执行的,并且也不会给该方法分配“运行所属”的内存空间,只有在调用这个方法的时候,才会动态的给这个方法分配所属的内存空间。

  3. 方法代码片段保存在哪里?方法执行过程中的内存在哪里分配?

    1. 方法代码片段属于.class字节码文件的一部分,字节码文件在类加载的时候,将其放到了方法区中,所以JVM的三块主要内存空间,方法区内存最先有数据,存放了代码片段。
    2. 代码片段虽然在方法区内存当中只有一份,但是可以被重复调用,每一次调用这个方法的时候,就会在栈内存中分配一块独立的活动场所,用于给方法中的变量分配需要的内存空间。
    3. 方法在调用的瞬间,会给该方法分配内存空间,在栈内存中发生压栈动作;方法执行结束之后,给该方法分配的内存空间全部释放,此时发生弹栈动作。
    4. 局部变量在“方法体”中声明,局部变量在运行阶段存储在栈内存中。

总体来说,程序在执行的时候,首先在方法区内存存储方法;在调用方法的时候,在栈内存中给方法分配存储空间,此时局部变量等内容存储在栈内存。所以方法内局部变量和外界完全没有关系,即使多次调用之间也没有关联。

3. 方法的重载机制

方法是将类似代码抽象出来,形成比较通用的结构,但是由于存在数据类型,那么对应不同的数据类型,此时仍然无法抽象出来,只能编写一个个不同返回值类型的方法,这样代码看起来比较臃肿。如下所示,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class OverloadTest01{

// 入口
public static void main(String[] args){

// 调用方法
int result1 = sumInt(1, 2);
double result2 = sumDouble(1.0, 2.0);
long result3 = sumLong(1L, 2L);

System.out.println(result1);
System.out.println(result2);
System.out.println(result3);

}

// 定义一个方法,可以计算两个int类型数据的和
public static int sumInt(int a, int b){
return a + b;
}

// 定义一个方法,可以计算两个double类型数据的和
public static double sumDouble(double a, double b){
return a + b;
}

// 定义一个方法,可以计算两个long类型数据的和
public static long sumLong(long a, long b){
return a + b;
}

// 最终希望达到的效果是:程序员在使用上面的三个相似的方法的时候,就像在用一个方法一样。
// Java支持这种机制【有些语言不支持,例如JavaScript】
}

那么对于不同类型数据,比如int类型的加法和float类型的加法,该怎么抽象呢?Java提供了重载机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/*
方法重载体验,体验一下方法重载的优点:
1. 程序员调用方法的时候,比较方便,虽然调用的是不同的方法,但是就感觉在使用一个方法一样,不需要记忆更多的方法名。
2. 代码美观。

前提:功能相似的时候,方法名可以相同。
但是,功能不同的时候,尽可能让这两个方法的名字不同。
*/

public class OverloadTest02{

public static void main(String[] args){

// 调用方法的时候就像在使用一个方法一样
// 参数的类型不同,对应调用的方法不同。
// 此时区分方法不再依靠方法名了,依靠的是参数的数据类型。

System.out.println(sum(1, 2));
System.out.println(sum(1.0, 2.0));
System.out.println(sum(1L, 2L));
}

// 如下所示,方法名相同,但是返回值类型不同,形参数据类型不同。
public static int sum(int a,int b){
System.out.println("sum(int, int)");
return a + b;
}

public static double sum(double a, double b){
System.out.println("sum(double, double)");
return a + b;
}

public static long sum(long a, long b){
System.out.println("sum(long, long)");
return a + b;
}
}

3.1 方法重载

  1. 方法重载又被称为:overload

  2. 什么时候考虑使用方法重载?

    1. 功能相似的时候,尽可能让方法名相同;
    2. 功能不相似的时候,尽可能让方法名不同。
  3. 什么条件满足之后构成了方法重载?

    1. 在同一个类中
    2. 方法名相同
    3. 参数列表不同:
      1. 参数数量不同
      2. 参数顺序不同
      3. 参数类型不同
    4. 方法重载和什么有关系,和什么没有关系?
      1. 方法重载和方法名+参数列表有关系
      2. 方法重载和返回值无关
      3. 方法重载和修饰符列表无关

    总之,方法是为了方便代码复用,重复执行相同的代码。而方法重载,在方法代码方面没有得到复用,但是在调用的时候,对于方法名来说,有一定的复用。

    方法重载:必须在一个类中,方法名相同,参数列表不同(参数数量,参数顺序,参数类型,三者最少有一个不同)

    方法重载代码案例如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    public class OverloadTest03{

    public static void main(String[] args){

    m1();
    m1(10);

    m2(1, 2.0);
    m2(2.0, 1);

    m3(10);
    m3(3.0);
    }

    // 以下两个方法构成重载
    public static void m1(){}
    public static void m1(int a){}

    // 以下两个方法构成重载
    public static void m2(int a, double b){}
    public static void m2(double a, int b){}

    // 以下两个方法构成重载
    public static void m3(int x){}
    public static void m3(double x){}

    // 编译错误,以下方法不是重载,是方法重复(重复定义)
    /*
    public static void m4(int a, int b){}
    public static void m4(int b, int a){}
    */

    /*
    public static void x(){}
    public static int x(){
    return 1;
    }*/

    /*
    void y(){}
    public static void y(){}
    */

    // 以下两个方法构成重载
    public static void m5(int a, char b){}
    public static int m5(float f){
    return 1;
    }
    }

4. 方法的递归调用

  1. 什么是递归?

    方法调用自身。

  2. 递归是很耗费内存的,递归算法可以不用的时候尽量别用。

  3. 程序运行的时候可能会发生这样的一个错误(不是异常,是错误):java.lang.StackOverflowError

    栈内存溢出错误,因为无限调用方法,而方法是在栈内存分配空间,所以导致栈内存溢出。错误发生后无法挽回,只有一个结果,就是JVM停止工作。

  4. 递归必须有结束条件,没有结束条件一定会发生栈内存溢出错误。

  5. 递归即使有了结束条件,也可能会发生栈内存溢出错误,可能是递归的太深了。

注意:递归能不使用,尽量别用。但是有些情况下该功能的实现必须依靠递归方法实现,例如:目录拷贝。

简单代码案例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class RecursionTest01{

// 主方法
public static void main(String[] args){

// 调用doSome方法
doSome();
}

// 以下的代码片段虽然只有一份
// 但是可以被重复的调用,并且只要调用doSome方法就会在栈内存中新分配一块所属的内存空间
public static void doSome(){

System.out.println("doSome begin");
doSome(); // 这段代码不结束,下一段代码是不能执行的。
System.out.println("doSome over");
}
}

递归求和案例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class RecursionDoSum{
public static void main(String[] args){
int factor = 10;
int sum;

sum = doSum(factor);
System.out.println(sum);
}

public static int doSum(int factor){

// 必须要有递归结束条件
if(factor <= 1){
return factor;
}

// 方法调用自身
return factor + doSum(factor - 1);
}
}

5. 备注

参考B站《动力节点》。


文章作者: 浮云
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 浮云 !
  目录