当前位置: 首页 > news >正文

java-方法详解

目录​​​​​​​

一、方法的定义

二、方法的调用

1.对于非静态方法:

2.对于静态方法:

3.类名.什么情况下可以省略

三、方法的参数传递

(1).实参和形参

形参

实参

(2).基本数据类型参数传递:

(3).引用数据类型参数传递:

四、方法执行时的内存图

1.栈(Stack)

2.堆(Heap)

3.方法区(Method Area)

4.本地方法栈(Native Method Stack)

5.寄存器(Registers)

6.运行案例

(1).JVM 内存结构及方法执行过程

(2).方法执行与内存分配释放

(3).代码执行流程

7.对局部变量生命周期短的解释:

(1).JVM 内存结构与局部变量存储位置

(2).局部变量生命周期与方法执行的关系

(3).总结

五、java8之后的元空间

1.前言

2.永久代

3.出现背景

4.内存位置与管理

5.对类加载等操作的影响

6.永久代的缺陷

7.元空间的优点

8.其他原因

六、方法重载(Overloading)

1.当前代码先不使用方法重载,大家分析一下这样写的代码有什么缺点?

2.使用java语言中的方法重载机制。分析程序优点?

3.当一个程序满足怎样的条件时,代码就构成了方法重载呢?

七、递归方法


一、方法的定义

基本语法格式如下:

修饰符 返回值类型 方法名(参数列表) {// 方法体,包含具体要执行的语句return 返回值;  // 如果返回值类型不是void,需要有对应的返回语句
}

修饰符列表可以是诸如 public(公共的,能被其他类访问)、private(私有的,只能在本类内部访问)、protected(受保护的,在同包及子类中可访问)、static(静态的,属于类而不属于具体对象)等,用于控制方法的访问权限和其他特性。

返回值类型:指定方法执行完后返回的数据类型,例如 int(整数类型)、double(双精度浮点数类型)、String(字符串类型)等。如果方法不返回任何值,使用 void 关键字来表示。(可以是java语言中任何一种数据类型。包括基本数据类型,引用数据类型。例如:byte short int long float double boolean char String...)

方法名:遵循 Java 的标识符命名规则,一般采用驼峰命名法,要做到见名知义,方便代码的阅读和理解。

参数列表:由零个或多个参数组成,参数的格式为 “数据类型 参数名”,用于接收外部传入的数据,多个参数之间用逗号隔开。如果没有参数,括号内也不能省略。

例如:

public int add(int num1, int num2) {return num1 + num2;
}

这个 add 方法是公共的,接收两个 int 类型的参数,返回值类型为 int,功能是计算并返回两个参数相加的结果。

方法的定义与调用:

1. 方法的定义。

    1.1 语法格式:
        [修饰符列表] 返回值类型 方法名(形式参数列表){
            方法体;
        }
    
    1.2 修饰符列表:目前修饰符列表这一块,统一编写public static。后面讲。

    1.3 

返回值类型
        可以是java语言中任何一种数据类型。包括基本数据类型,引用数据类型。
        例如:byte short int long float double boolean char String....
        如果方法执行结束的时候没有返回任何数据给调用者,返回值类型写:void。
        切记:不能空着不写。

        返回值类型是int表示:方法结束的时候会返回一个整数给调用者。
        返回值类型是String表示:方法结束的时候会返回一个字符串给调用者。
        返回值类型是void表示:方法结束的时候不返回任何数据给调用者。
    
    1.4 当返回值类型不是void的时候,方法在结束的时候必须使用“return 值;”语句来完成数据的返回。

    1.5 return语句有两种写法:
        第一种:return 值;
        第二种:return;
        不管是哪一种,只要return语句执行,方法必然结束。
    
    1.6 当返回值类型是void的时候:
        不能编写"return 值;" 这样的语句。
        但是可以编写"return;"语句,主要是用来终止方法的执行。也可以不编写“return;”,这个要看具体的业务。
    
    1.7 当调用一个返回值类型不是void的方法时,方法结束的时候会返回值,这个值可以采用变量接收。注意变量的类型。
    变量的类型一定要和返回值类型一致。或者能够自动类型转换,或者强制类型转换。

    1.8 方法执行结束有返回值,但是对于调用者来说,可以选择接收,也可以选择不接收。

    1.9 方法名:只要是合法的标识符即可。首字母小写,后面每个单词首字母大写。(一般方法都是一个行为,所以方法名一般都是动词。)
    方法名最好是反映了这个方法所实现的功能。
        deleteUser
        saveUser
        login
        logout
        ....

    1.10 形式参数列表一般简称为:形参。形参个数是:0-N个。多个的话,使用逗号隔开。
    形参是局部变量。形参中起决定性作用的是:形参的类型。形参名随意。

    1.11 方法体:任何一个方法都有一个方法体。方法体用大括号括起来:
        在大括号中编写“java语句;”
        并且方法体中的代码有执行顺序,遵循自上而下。


2. 方法的调用。
    当这个方法修饰符列表有static关键字的时候:
        调用的语法格式:
            类名.方法名(实际参数列表);
    
    实际参数列表简称为:实参。

    实参和形参列表必须一一对应:
        类型要一一对应。
        个数要一一对应。

public class MethodTest03{public static void main(String[] args){// 调用方法int result = add(1, 2);System.out.println(result);// 这个过程就存在自动类型转换。long result2 = add(200, 300);System.out.println(result2);// concat方法结束后返回的是String值,必须使用String类型变量接收这个值。String content = concat("hello,", "jack");System.out.println(content);add(1111, 2222);// 错误//add(false, 1);// 错误//add(1);// 用完整语法调用一次方法。String retValue = MethodTest03.concat("abc", "def");System.out.println(retValue);}// 求和的一个方法。public static int add(int a, int b){System.out.println("add..........");//int z = x + y;//return z;return a + b;}public static String concat(String x, String y){//String z = x + y;//return z;return x + y;}// 报错原因:因为返回值类型是int,那么结束时就必须要返回一个整数。但是没有写 return 返回语句来完成数据的返回/*public static int sum(int a, int b){}*/// 解决错误:添加了返回语句。public static int sum(int a, int b){//int c = a + b;//return c;return 0;  //语法对的,不管答案是否对,就返回0}public static boolean m1(){// 不兼容的类型: int无法转换为boolean//return 1;// 修改return true;}// 缺少返回语句/*public static String m2(){}*/// 修改public static String m2(){return "say hello";}public static void m3(){}// 编译报错:言行不一。/*public static void m4(){return 100;}*/public static void m5(){return;}// 缺少返回值/*public static int m6(){return;}*/}

二、方法的调用

1.对于非静态方法

需要先创建类的对象,然后通过对象来调用方法。例如:

class Calculator {public int add(int num1, int num2) {return num1 + num2;}
}public class Main {public static void main(String[] args) {Calculator calculator = new Calculator();int result = calculator.add(3, 5);System.out.println("结果是:" + result);}
}

这里先实例化了 Calculator 类得到对象 calculator,再通过该对象调用 add 方法。

2.对于静态方法:

可以直接通过类名来调用,当然也能用对象调用,但更推荐使用类名调用的方式,示例如下:

class MathUtils {public static int multiply(int num1, int num2) {return num1 * num2;}
}public class Main {public static void main(String[] args) {int result = MathUtils.multiply(2, 4);System.out.println("结果是:" + result);}
}

3.类名.什么情况下可以省略

public class methodTest {//调用方法是,类名。什么情况下可以省略//调用者 和 被调用者 在同一个类中时,可以省略public static void main(String[] args) {//调用方法m1methodTest.m1();//省略  类名.m1();//调用m2方法A.m2();m2();  //编译报错}public static void m1(){System.out.println("m1方法执行了");}
}
class A{public static void m2(){System.out.println("m2方法执行了");}
}

三、方法的参数传递

(1).实参和形参

在编程语言(包括 Java 等许多语言)中,实参(Actual Parameter)和形参(Formal Parameter)是与函数或方法调用相关的重要概念,以下为你详细介绍:

形参

定义:形参是在函数或方法定义时,括号内声明的参数。它规定了该函数或方法期望接收的数据类型以及对应的变量名称,相当于一个 “占位符”,用于在函数或方法内部表示外部传入的数据。

特点:

只有在函数或方法被调用时,形参才会被分配内存空间来存储相应的值。

其作用域局限于所在的函数或方法内部,一旦函数或方法执行完毕,形参所占用的内存空间就会被释放。

示例(以 Java 方法为例):

public int add(int num1, int num2) {  
// 这里的num1和num2就是形参return num1 + num2;
}

在上述 add 方法定义中,int num1 和 int num2 就是形参,定义了该方法期望接收两个整数类型的数据,在方法内部可以使用这两个形参进行相应的运算操作。

实参

定义:实参是在调用函数或方法时,实际传递给函数或方法的具体数据值或者变量。它对应着函数或方法定义时形参所规定的类型和数量要求,为形参提供了真实的、具体的内容。

特点:

实参可以是常量、变量、表达式等,只要其值的类型与对应的形参类型相匹配即可。

实参在调用函数或方法前就已经存在并且有确定的值,通过传递把这些值赋予对应的形参,从而让函数或方法基于这些实际传入的值进行相应的操作。

示例(同样以 Java 方法调用为例):

public class Main {public static void main(String[] args) {int a = 3;int b = 5;int result = add(a, b);  // 这里的a和b就是实参System.out.println("结果是:" + result);}public static int add(int num1, int num2) {return num1 + num2;}
}

在 main 方法中调用 add 方法时,变量 a 和 b 作为实参传递给 add 方法,它们的值会分别赋给 add 方法中的形参 num1 和 num2,然后 add 方法基于这些值进行相加运算并返回结果。

总之,形参和实参是函数或方法调用过程中相互配合的两个要素,通过实参向形参传递数据,使得函数或方法能够灵活地处理不同的输入情况,实现各种功能。

(2).基本数据类型参数传递:

传递的是值的副本在方法内部对参数进行修改,不会影响到方法外部原来变量的值。例如:

public class Main {public static void changeValue(int num) {num = 10;}public static void main(String[] args) {int number = 5;changeValue(number);System.out.println("number的值还是:" + number); // 输出依然是5}
}

(3).引用数据类型参数传递:

传递的是对象的引用地址,在方法内部通过该引用对对象的属性等进行修改,会影响到外部对象的状态。比如对于数组:

public class Main {public static void changeArray(int[] arr) {arr[0] = 100;}public static void main(String[] args) {int[] array = {1, 2, 3};changeArray(array);System.out.println("数组第一个元素变为:" + array[0]); // 输出为100}
}

四、方法执行时的内存图

1.方法如果只定义,不调用是不会分配内存空间的。(从java8开始,方法的字节码指令存储在元空间metaspace当中。元空间使用的是本地内存)

2.方法调用的瞬间,会在栈内存当中分配活动场所,此时发生压栈动作

1.栈(Stack)

作用:方法运行时使用的内存。例如,当 main 方法运行时,会进入方法栈中执行。

解释:栈是一种数据结构,在 Java 中用于存储局部变量方法调用的相关信息。每当一个方法被调用时,一个新的栈帧就会被压入栈中,栈帧中包含了方法的局部变量、操作数栈等信息。当方法执行完毕后,对应的栈帧就会被弹出栈。

2.堆(Heap)

作用:存储对象或者数组。通过 new 关键字创建的对象都存储在堆内存中。

解释:堆是 Java 中用于存储对象实例的内存区域。与栈不同,堆内存的分配和回收是由垃圾回收器(Garbage Collector)来管理的。对象在堆中创建后,只要还有引用指向它,它就不会被垃圾回收。

3.方法区(Method Area)

作用:存储可以运行的.class 文件

解释:方法区是 JVM 规范中的一个概念,用于存储已被 JVM 加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。在 Java 8 之后,方法区的实现由永久代(Permanent Generation)改为元空间(Metaspace),元空间使用本地内存。

4.本地方法栈(Native Method Stack)

作用:JVM 在使用操作系统功能的时候使用,和我们开发无关。

解释:本地方法栈与栈类似,但是它用于支持本地方法(Native Method)的调用。本地方法是用其他语言(如 C 或 C++)编写的方法,通过 Java Native Interface(JNI)来调用。

5.寄存器(Registers)

作用:给 CPU 使用,和我们开发无关。

解释:寄存器是 CPU 内部的存储单元,用于存放指令、数据和地址。在 Java 编程中,我们通常不需要关心寄存器的操作,因为这是由 CPU 和 JVM 的底层机制来处理的。

6.运行案例

/*1.方法如果只定义,不调用是不会分配内存空间。(从Java8开始,方法的字节码指令存储在元空间metaspace当中。元空间使用的是本地内存。)2.方法调用的瞬间,会在JVM的栈内存当中分配活动场所,此时发生压栈动作。3.方法一旦结束,给该方法分配的内存空间就会释放。此时发生弹栈动作。*/
public class methodtest01 {public static void main(String[] args) {System.out.println("main begin");m1();System.out.println("main over");}public static void m1(){System.out.println("m1 begin");m2();System.out.println("m1 over");}public static void m2(){System.out.println("m2 begin");m3();System.out.println("m2 over");}public static void m3(){System.out.println("m3 begin");System.out.println("m3 over");}
}

运行结果:

解析:

(1).JVM 内存结构及方法执行过程

JVM 内存结构:

栈内存(stack):

如图所示,栈内存中包含多个方法栈帧,每个方法在执行时都会在栈内存中分配一个栈帧。例如图中的 main 方法栈帧、m1 方法栈帧、m2 方法栈帧和 m3 方法栈帧。栈帧中包含局部变量表和操作数栈等信息。局部变量表用于存储方法中的局部变量,操作数栈用于进行表达式的计算等操作。 在代码中,当 main 方法开始执行时,main 方法的栈帧就会被压入栈内存。当 main 方法中调用 m1 方法时,m1 方法的栈帧会被压入栈内存,此时 main 方法的栈帧处于等待状态。以此类推,当 m1 调用 m2,m2 调用 m3 时,相应的栈帧都会依次压入栈内存。

堆内存(heap):图中展示了堆内存的区域,但在这段代码中没有直接涉及到堆内存的操作。一般来说,对象的实例会在堆内存中分配空间,而栈内存中的局部变量表可能会持有对这些对象的引用。

元空间(metaspace,Java 8 之后):根据注释和图片右侧说明,方法的字节码指令等信息存储在元空间中,元空间使用的是本地内存。例如 MethodTest01.class、String.class、System.class 等类的相关信息(包括方法字节码等)都存储在元空间。在这段代码中,虽然没有直接体现元空间的具体操作,但当类被加载时,类的元数据(包括方法字节码)就会被加载到元空间中。

(2).方法执行与内存分配释放

方法定义与内存分配:如注释所说 “方法如果只定义,不调用是不会分配内存空间”。在这段代码中,m1、m2、m3 方法虽然已经定义,但在 main 方法调用它们之前,它们并不会在栈内存中分配栈帧空间。只有当 main 方法中调用 m1 时,m1 方法才会在栈内存中分配栈帧,这就是 “方法调用的瞬间,会在 JVM 的栈内存当中分配活动场所,此时发生压栈动作”。 例如,当执行到 m1(); 这行代码时,m1 方法的栈帧被压入栈内存,栈帧中会包含 m1 方法的局部变量(这里没有)和操作数栈等信息,用于 m1 方法的执行。

方法执行与内存释放:当 m3 方法执行完毕,m3 方法的栈帧就会从栈内存中弹出,释放其占用的空间,这就是 “方法一旦结束,给该方法分配的内存空间就会释放。此时发生弹栈动作”。然后 m2 方法继续执行后面的代码,当 m2 方法执行完毕,其栈帧也会弹出。同理,m1 方法和 main 方法依次执行完毕后,它们的栈帧也会依次弹出栈内存,完成整个程序的执行过程

(3).代码执行流程

程序从 main 方法开始执行,首先输出 "main begin"。

然后调用 m1 方法,m1 方法开始执行,输出 "m1 begin",接着调用 m2 方法。

m2 方法执行,输出 "m2 begin",再调用 m3 方法。

m3 方法执行,输出 "m3 begin" 和 "m3 over",m3 方法结束,其栈帧弹出。

m2 方法继续执行后面的代码,输出 "m2 over",m2 方法结束,其栈帧弹出。

m1 方法继续执行后面的代码,输出 "m1 over",m1 方法结束,其栈帧弹出。

main 方法继续执行后面的代码,输出 "main over",main 方法结束,其栈帧弹出,程序结束。

这段代码和图片结合起来,很好地展示了 Java 程序在 JVM 中的执行过程,包括方法栈帧在栈内存中的分配与释放,以及元空间存储类的相关信息等重要概念。

7.对局部变量生命周期短的解释:

(1).JVM 内存结构与局部变量存储位置

栈内存(stack)与局部变量:

从图中可以看到,在栈内存中,每个方法栈帧都包含一个局部变量表。例如 main 方法栈帧、m1 方法栈帧等都有自己的局部变量表。局部变量就存储在这些局部变量表中。

以代码中的 main 方法为例,当 main 方法开始执行时,它的栈帧被压入栈内存,此时 main 方法中的局部变量(这里没有实际的局部变量,但假设有的话)就会被分配到 main 方法栈帧的局部变量表中。

(2).局部变量生命周期与方法执行的关系

方法调用与局部变量生命周期开始:当一个方法被调用时,该方法的栈帧被压入栈内存,此时方法中的局部变量开始其生命周期。也就是说,局部变量的生命周期是从方法被调用开始的。例如,当 main 方法调用 m1 方法时,m1 方法的栈帧被压入栈内存,m1 方法中的局部变量开始存在于 m1 方法栈帧的局部变量表中,它们的生命周期开始。

方法执行结束与局部变量生命周期结束:

一旦方法执行结束,该方法的栈帧就会从栈内存中弹出,方法栈帧中的局部变量表也随之消失,局部变量也就不复存在了,这就意味着局部变量的生命周期结束。例如,当 m3 方法执行完毕,m3 方法的栈帧弹出栈内存,m3 方法中的局部变量生命周期结束。同理,m2、m1 方法执行完毕后,它们各自的局部变量生命周期也都结束。

相比之下,堆内存中的对象,如果没有被垃圾回收,其生命周期可能会跨越多个方法的调用,而局部变量仅仅在其所属方法的执行期间存在,方法结束就消失,所以说局部变量生命周期短。

(3).总结

局部变量存储在栈内存的方法栈帧的局部变量表中,其生命周期与方法的执行紧密相关,方法调用时开始,方法结束时结束,这使得局部变量的生命周期相对较短,与堆内存中可能长期存在的对象形成鲜明对比。这种机制有助于内存的高效管理和快速回收,因为栈内存的分配和释放速度通常比堆内存更快。

五、java8之后的元空间

1.前言

java里面垃圾回收效果最差的是永久代,而且永久代溢出也是一个非常常见的问题,从java7开始,原来存储于永久代的数据就不断的被移到其他位置,有不少数据都移动到了堆区,比如字符串常量池挪到了堆区,在java8最终将永久代移除,另外新增了一个区,也就是元空间(Metaspace),元空间承接了部分永久代的数据。本文接下来介绍元空间

2.永久代

永久代与元空间有千丝万缕的联系,首先来看一下永久代。

永久代更规范的名字的叫做方法区,永久代是方法区的一种实现方式,方法区是java规范中定义的,只有hotspot(动态编译)才有永久代。之所以叫它永久代是因为垃圾回收效果很差,大部分的数据会一直存在直到程序停止运行。 永久代里面一般存储类相关信息,比如类常量、字符串常量、方法代码、类的定义数据等,如果要回收永久代的空间,需要将类卸载,而类卸载的条件非常苛刻,所以空间一般回收很难。当程序中有大量动态生成类时,这些类信息都要存储到永久代,很容易造成方法区溢出。

3.出现背景

在 Java 8 之前,永久代用于存放类的元数据信息(比如类的结构信息、方法数据、常量池等),但存在一些问题。例如,永久代有固定的大小限制,在运行时如果加载的类过多,很容易出现 java.lang.OutOfMemoryError: PermGen space 这样的内存溢出错误,而且其内存回收等管理相对不够灵活。Java 8 为了更好地管理类的元数据,引入了元空间。

4.内存位置与管理

内存位置:

元空间使用的是本地内存(Native Memory),而非 Java 堆内存。它不再受限于 JVM 设定的固定堆大小范围,理论上只要本地内存足够,元空间可以按需扩展,这使得它能处理更多的类元数据信息,避免了之前永久代那种容易因内存受限而导致的溢出情况。

自动内存管理:

元空间的内存管理由元空间虚拟机自动进行,它会根据类加载、卸载等情况动态地调整内存占用。不过,虽然不用像之前那样手动设置永久代的初始大小和最大大小了,但在一些应用场景下,我们也可以通过一些 JVM 参数来合理配置元空间的相关内存参数,例如 -XX:MetaspaceSize 用于设置元空间初始大小,-XX:MaxMetaspaceSize 用来设定元空间的最大可分配内存大小,避免无节制地占用本地内存。

5.对类加载等操作的影响

类加载方面:

在类加载过程中,类的元数据会被存储到元空间中。当应用不断加载新的类时,元空间会相应地分配内存来存放这些新的元数据信息。像一些动态生成类的场景(比如使用字节码生成技术的框架),元空间能更好地适应不断增加的类元数据需求。

类卸载方面:

当一个类不再被使用,满足了卸载条件(比如该类的所有实例都已经被回收,并且加载该类的类加载器也已经被回收等)时,其对应的元数据会从元空间中被清理掉,释放内存以供后续其他类的元数据使用。

6.永久代的缺陷


固定大小的内存区域:

永久代是一个固定大小的内存区域,其大小在JVM启动时通过参数-XX:PermSize和-XX:MaxPermSize指定。这种固定大小的方式存在明显的弊端,如果设置得过小,可能导致内存不足,出现OutOfMemoryError: PermGen space错误;如果设置得过大,则可能浪费内存资源。
垃圾回收的复杂性:

永久代的垃圾回收机制较为复杂,尤其是在类卸载时,需要扫描整个永久代以标记无用的类元数据。这种操作可能导致垃圾回收暂停时间增加,影响应用程序的性能。
使用限制:

永久代的大小限制也影响了Java应用程序在动态生成类(如大量使用反射、动态代理、JSP编译等)场景下的性能。由于永久代的大小固定,这些动态生成的类可能会迅速耗尽永久代的内存。


7.元空间的优点


动态扩展:元空间使用本地内存(native memory)而不是堆内存,这意味着它可以根据需要动态扩展,不再受JVM启动参数的限制。这解决了永久代因固定大小带来的内存不足问题,尤其在应用程序运行过程中生成大量类的场景中非常有用。
垃圾回收的优化:元空间的使用简化了垃圾回收的过程。类元数据不再与堆内存共享同一个垃圾回收机制,而是由JVM根据需要自动管理。这减少了垃圾回收的复杂性和暂停时间。
更好的内存利用率:由于元空间使用的是本地内存,可以更灵活地管理和分配内存资源。应用程序可以根据实际需求调整元空间的大小,而不必在启动时预先指定一个固定的大小。
减少OutOfMemoryError:由于元空间可以动态扩展,内存不足的风险大大降低,这有助于提高应用程序的稳定性和可靠性。


8.其他原因


促进JVM的融合:移除永久代也是为了促进HotSpot JVM与JRockit VM的融合,因为JRockit没有永久代的概念。
优化内存管理:随着Java应用程序的规模和复杂性的增加,特别是在云计算和大数据环境中,JVM内存管理面临的挑战越来越大。元空间的引入提供了更灵活和高效的内存管理方式。


综上所述,Java 8中移除永久代并引入元空间是为了解决永久代带来的各种问题,并优化JVM的内存管理和应用程序的性能。这一改变是Java平台向前迈出的重要一步,为开发者提供了更灵活和高效的内存管理工具。

六、方法重载(Overloading)

1.当前代码先不使用方法重载,大家分析一下这样写的代码有什么缺点?

/*当前代码先不使用方法重载,大家分析一下这样写的代码有什么缺点?缺点一:代码不美观。缺点二:不方便调用,程序员需要记忆更多的方法名。
*/
public class OverloadTest01{public static void main(String[] args){// 调用方法sumInt(10, 20);sumLong(10L, 20L);sumDouble(3.0, 2.0);}public static void sumInt(int a, int b){int c = a + b;System.out.println(a + "+" + b + "=" + c);}public static void sumLong(long a, long b){long c = a + b;System.out.println(a + "+" + b + "=" + c);}public static void sumDouble(double a, double b){double c = a + b;System.out.println(a + "+" + b + "=" + c);}}

2.使用java语言中的方法重载机制。分析程序优点?

/*使用java语言中的方法重载机制。分析程序优点?在java语言中允许在一个类中定义多个方法,这些方法的名字可以一致。优点1:代码美观。优点2:方便调用了,程序员需要记忆的方法名少了。
*/
public class OverloadTest02{public static void main(String[] args){// 调用方法sum(10, 20);sum(10L, 20L);sum(3.0, 2.0);}public static void sum(int a, int b){int c = a + b;System.out.println(a + "+" + b + "=" + c);}public static void sum(long a, long b){long c = a + b;System.out.println(a + "+" + b + "=" + c);}public static void sum(double a, double b){double c = a + b;System.out.println(a + "+" + b + "=" + c);}}

3.当一个程序满足怎样的条件时,代码就构成了方法重载呢?

/*
当一个程序满足怎样的条件时,代码就构成了方法重载呢?条件1:在同一个类中。条件2:方法名一致。条件3:形式参数列表不同:类型不同算不同顺序不同算不同个数不同算不同方法重载是编译阶段的机制还是运行阶段的机制?方法重载机制是编译阶段的机制。在编译阶段已经完成了方法的绑定。在编译阶段已经确定了要调用哪个方法了。什么情况下我们考虑使用方法重载呢?在以后的开发中,在一个类中,如果两个方法的功能相似,建议将方法名定义为同一个名字。此时就使用了方法重载机制。
*/
public class OverloadTest03{public static void main(String[] args){m1();m1("abc");m2(10, 20);m2(10L, 20L);m3("x", 10);m3(10, "x");}// 形参的个数不同public static void m1(){System.out.println("m1()");}public static void m1(String s){System.out.println("m1(String s)");}// 形参类型不同public static void m2(int a, int b){System.out.println("m2(int a, int b)");}public static void m2(long a, long b){System.out.println("m2(long a, long b)");}// 形参顺序不同public static void m3(String s, int a){System.out.println("m3(String s, int a)");}public static void m3(int a, String s){System.out.println("m3(int a, String s)");}// 以下这两个方法没有构成方法重载,属于方法重复定义了。语法错误,编译器报错。/*public static void doSome(int a, int b){}public static void doSome(int x, int y){}*/
}

所以方法重载指在同一个类中,有多个方法名相同但参数列表不同(参数个数、参数类型或者参数顺序不同)的方法。返回值类型不同不能作为方法重载的依据。例如:

public class Calculator {public int add(int num1, int num2) {return num1 + num2;}public double add(double num1, double num2) {return num1 + num2;}public int add(int num1, int num2, int num3) {return num1 + num2 + num3;}
}

这样在调用 add 方法时,编译器会根据传入的实际参数的类型、个数等来决定调用哪个具体的重载方法。

七、递归方法

递归调用如果没有结束条件的话,会出现栈内存溢出错误:java.lang.StackOverflowError

 所有的递归调用必须要有结束条件。

4. 在实际开发中,使用递归调用的时候,即使有的时候,结束条件是存在的,并且结束条件也是合法的。
但仍然会发生栈内存溢出错误,这可能是因为递归太深,栈内存不够了导致的。所以递归调用一般是不建议
使用的。只有在不使用递归调用时这个问题解决不了的情况下,才建议使用递归调用。

    原则:能用循环尽量使用循环。
    因为递归调用太耗费栈内存。

5. 在实际开发中,如果因为递归调用发生了栈内存溢出错误,你该怎么办?
    首先可以调整栈内存的大小。扩大栈内存。
    如果扩大之后,运行一段时间还是出现了栈内存溢出错误。
    可能是因为递归结束条件不对。需要进行代码的修改。

递归就是一个方法在其自身内部调用自身的编程技巧。需要有终止条件,否则会导致无限循环调用,最终造成栈溢出错误。例如

使用递归计算n的阶乘(经常考的面试题:笔试题)

public class Main {public static int factorial(int n) {if (n == 0) {return 1;}return n * factorial(n - 1);}public static void main(String[] args) {int result = factorial(5);System.out.println("5的阶乘是:" + result);}
}
// 使用递归计算n的阶乘(经常考的面试题:笔试题)
// 5的阶乘:5 * 4 * 3 * 2 * 1
public class RecursionTest04{public static void main(String[] args){int n = 5;int result = jieCheng(n);System.out.println("result = " + result);}public static int jieCheng(int n){if(n == 1){return 1;}return n * jieCheng(n - 1);}
}

方法递归的内存图


http://www.mrgr.cn/news/83213.html

相关文章:

  • 项目代码第8讲:Socket和OPC UA客户端通信;数据库的表格内容谁填的?(OPC Client和Web);在Program.cs中单独开启一个线程
  • L28.【LeetCode笔记】移动零(三种解法)
  • Eclipse配置Tomcat服务器(最全图文详解)
  • 论文导读 | 数据库系统中基于机器学习的基数估计方法
  • 键盘过滤驱动
  • Express 加 sqlite3 写一个简单博客
  • Springboot Bean创建流程、三种Bean注入方式(构造器注入、字段注入、setter注入)、循坏依赖问题
  • 【机器视觉】OpenCV 图像金字塔(高斯、拉普拉斯)和直方图
  • 统一门户单点登入(C#-OOS机制)
  • 04、Redis深入数据结构
  • 使用PVE快速创建虚拟机集群并搭建docker环境
  • https原理
  • 华为C语言编程规范总结
  • WinCC flexible SMART V4 SP2软件安装事项
  • C++中的表达式
  • 高性能网络模式:Reactor 和 Proactor
  • Linux标准IOday3
  • yum换源
  • Spring——自动装配
  • 【python基础——异常BUG】
  • redis:安装部署、升级以及失败回退
  • 【算法】八大排序算法
  • UI自动化测试框架playwright--初级入门
  • 音视频入门基础:MPEG2-PS专题(5)——FFmpeg源码中,解析PS流中的PES流的实现
  • K-means算法在无监督学习中的应用
  • 第四、五章图论和网络爬虫+网络搜索