Bootstrap

Java入门第一章——编程语言基础:方法和递归

方法和递归

方法

不使用“方法”写计算和结果:

public class MethodTest{
    public static void main(String[] args){
        //需求一:请编写计算10+20的结果,并输出结果【功能:计算两个int类型的数据之和】
        int a = 10;
        int b = 20;
        int c = a + b;
        System.out.println(c);
        
        //需求二:请编写计算66+88的结果,并输出结果【功能:计算两个int类型的数据之和】
        int d = 66;
        int e = 88;
        int f = d + e;
        System.out.println(f);
        
        需求三:请编写计算11+22的结果,并输出结果【功能:计算两个int类型的数据之和】
        int x = 11;
        int y = 22;
        int z = x + y;
        System.out.println(z);
        
        //以上三个需求是一个功能:计算两个int类型的数据之和
        //只是每次参与计算具体数据不同
        //上面的代码写了三个计算和的代码,代码没有得到重复使用。
    }
}

方法:某个功能代码只需要写一遍,要使用这个功能,仅需要传递具体的数据,功能完成后返回最终的结果。这样代码可以重复利用,提高代码复用性。

使用方法我们称为" 调用/invoke"。方法定义在一个类体中,一个类中可以定义多个方法,方法编写位置没有先后顺序。方法体中不能再定义方法。

方法的本质就是一段代码片段,这段片段能够完成特定的功能,且可以重复使用。

注:方法在C语言当中称为“函数/Function”。

使用方法的机制把上面的代码重新编写:

public class MethodTest{
    public static void main(String[] args){
        MethodTest.sumInt(10,20);   //30
        MethodTest.sumInt(66,88);   //154
        MethodTest.sumInt(11,22);   //33
    }
    //单独定义一个方法,完成两个int数据类型的和,将结果输出
    public static void sumInt(int a,int b){
        int c = a + b;
        System.out.println(a + "+" + b + "=" + c);
    }
}

1.方法的语法:

//返回值类型是int是java中任意一种类型
[修饰符列表] 返回值类型 方法名(形式参数列表){
    方法体;
    return 返回值;
}
//返回值是void
[修饰符列表] void 方法名(形式参数列表){    
    方法体;
}
1.1关于修饰符列表

*不是必须的

*目前学习到这里,统一写:public static【后面有】

*方法中有static关键字,调用方式:类名 . 方法名(实际参数列表);

1.2返回值类型

*返回值:方法执行结果后返回的数据就是返回值。

*返回值类型:返回值是一个具体存在的数据,数据都是有类型的。此处需要指定返回值的具体类型。

​ java中的任意一种类型都可以是返回值类型(包括基本数据类型和引用数据类型)。

*有的方法可以没有返回值类型:方法执行结束后不返回任何结果,java规定,当一个方法执行结束后不返回任何数据的话,返回值类型位置写“void”。

​ 当方法需要返回一个值,需要在方法体写” return 值; “ 。反过来不需要返回值,即返回值类型位置是"void",则不需要return。

*可以编写 ” return ; ”,且在放回类型是"void"方法中写这样的语句,只要带有return关键字的语句执行,return所在的方法结束(强行结束所在的方法,不是JVM)。

1.3方法名

*只要是合法标识符就行。

*方法名最好是动词,见名知意。

*首字母要求小写,后面每个字母大写。

1.4形式参数列表(形参)

*形参是局部变量:int a ; double b ; float c ; ……

*形参间用逗号隔开。

*形参起决定作用的是数据类型。如:

//方法定义
public static int sum(int a ,int b){} //(int a, int b)是形参列表
//方法调用
sum("abc","def");  //编译器报错
sum(10,20);  //编译通过(10,20)是实参列表

*方法调用的时候,实际给这个方法传递真实数据被称为:实际参数(实参)

*实参列表和形参列表必须满足:数量相同,类型对应相同。

1.5方法体

方法体由大括号括起来,方法体中代码有顺序。

1.6方法调用

方法不调用时不执行。

语法规则(方法修饰符中有static): 类名.方法名(实参列表);

例子:

//public表示公开的
//class 表示定义类
//MethodTest是一个类名
public class MethodTest{  //表示一个公开的类,起名为MethodTest,由于是公开的类,所以源文件必须:MethodTest.java
    //类体
    //类体不能直接编写java语句,除声明变量外
    //方法出现在类体当中
    
    //方法
    //public表示公开的
    //static表示静态的
    //void表示方法执行结束后不返回任何数据
    //main是方法名:主方法
    //(String[] args):形式参数列表,其中String[]是一种引用数据类型,args是一个局部变量的变量名。
    //所以以下只有args这个局部变量的变量名是随意的。
    //而主方法就需要这样固定编写,这是程序的入口。【SUN规定必须这样写主方法】
    public static void main(String[] args){
        //这里的程序时一定会执行的
        //main方法是JVM负责调用的,是一个入口的位置
        //从这里作为起点开始执行程序
        //我们就可以在这里写java语句来调用其他方法
        MethodTest.sum(20,50);//这句语句的意思是调用MethodTest的sum方法,传递两个实参。(10,20)表示实参列表
        
        //一个方法可以重复被调用
        int a = 88;
        MethodTest.sum(a,88);//(a,88)是参列表
        
        //再次调用
        int j = 50;
        int k = 60;
        MethodTest.sun(j,k);
    }
    
    //自定义方法
    //方法作用:计算两个int类型数据的和,不要求返回结果,但是要求直接输出到控制台。
    //修饰符列表:public static
    //返回值类型:void
    //方法名:sum
    //形式参数列表:(int x,int y)
    //方法体:主要任务是求和之后输出计算结果
    public static void sum(int x,int y){   //(int x, int y )形参列表
        System.out.println(x + "+" + y "=" + (x+y));
    }
}

方法调用不一定在main方法当中,可以再其他方法中。

public class MethodTest{
    public static void sum(int a, int b){
        System.out.println(a + "+" + b "=" +(a + b));
        //调用doSome方法
        MethodTest.doSome();
    }
    
    public static void main(String[] args){
        MethodTest.sum(1,2);
        System.out.println("Hello");
    }
    
    public static void doSome(){
        System.out.println("do some!");
    }
}

方法调用时,形参列表和实参列表必须一致。

public class MethodTest{
    public static void main(String[] args){
        //编译错误,参数数量不同
        //MethodTest.sum();
        //编译错误,实参形参类型不是对应相同
        //MethodTest.sum(true,false);
        
        MethodTest.sum(10L,20L);//可以
    
        //存在自动类型转换int->long
        MethodTest.sum(10,20);    
    }
    public static void sum(long a,long b){
        System.out.println(a + "+" + b "=" + (a + b));
    }
}

正规调用的时候,完整调用方式:类名.方法名(实参列表); 。而在当前类当中,方法含有static关键字,"类名."可以省略不写。

规范:在一个java源文件中只定义一个class,比较清晰。

public class MethodTest{
    public static void main(String[] args){
        //完整调用方式:MethodTest.m();
        //由于当前是在同个类中,类名可以省略
        m();
        
        m2();//报错:这里直接调用m2方法会报错
        
        //这个时候需要添加"类名."
        A.m2();
        
    }
    //写一个方法,类名为m。带了static修饰符
    public static void m(){
        
    }
}
public class A{
    //这里在另一个类中写了方法。
    public static void m2(){
        
    }
}

例子:分析程序输出结果

public class MethodTest{
    public static void main(String[] args){
        System.out.println("main begin");
        m1();
        System.out.println("main over");
    }
    public static void m1(String[] args){
        System.out.println("m1 begin");
        m2();
        System.out.println("m1 over");
    }
    public static void m2(String[] args){
        System.out.println("m2 begin");
        m3();
        System.out.println("m2 over");
    }
    public static void m3(String[] args){
        System.out.println("m3 begin");
        System.out.println("m3 over");
    }
}

这个程序输出结果如下

main begin
m1 begin
m2 begin
m3 begin
m3 over
m2 over
m1 over
main begin
1.7方法类型不是void时

当方法类型不是void时,一定要有" return 值; "语句的执行。且返回值的类型要与方法类型相同。

例子:

//需求:定义一个方法并实现,方法可以计算两个int类型数据的商。将最终计算结果返回给调用者。
public class MethodTest{
    public static void main(String[] args){
        //调用方法
        subtract(30,15);//这里没有接收这个方法返回的数据
        
        //这里采用变量接收方法返回的数据。变量的数据类型与返回的数据类型相同,或者可以自动类型转换。
        int sub = subtract(20,10);
        System.out.println(sub);  //10
    }
    
    public staitic int subtract(int a,int b){
        int c = a - b;
        return c;
    }
}
1.8return语句

*带有return关键字的java语句只要执行,所在方法执行结束。

*在同一个作用域中,return下面(物理上)不能编写任何代码,因为这些代码执行不到,所以导致编译报错。

public class MethodTest{
    public static void main(String[] args){
        
    }
    /*这个方法会编译报错:缺少返回语句
    因为编译器认为 ”return 1;” 无法百分百保证会执行
    public static int m(){
        int a = 10;
        if(a > 3){
            return 1;
        }
    }
    */
    
    //下面这个方法有了else就可以
    public static int m2(){
        int a = 10;
        if(a > 3){
            return 1; 
        }else{
            return 0;
        }
    }
    //或者
    public static int m3(){
        int a = 10;
        if(a > 3){
            return 1;
             //下面的语句会编译错误:因为return语句下面不能编写任何代码
            //System.out.println("Hello");
            //注意:这里是在if的作用域里边
        }
            //注意:这里是在m3()的作用域里边,if语句的作用域外边
            System.out.println("Hello");//可以编译
            return 0;
            //下面的语句会编译错误:因为return语句下面不能编写任何代码
            //System.out.println("Hello");
            
    }
}
1.9在返回值类型是void方法当中使用"return ; "语句

*"return;"语句出现在void方法当中主要是为了结束当前方法。

例子1:

public class MethodTest{
    public static void main(String[] args){
        
    }
    /*编译错误:对于结果为void类型为空的方法无法返回值
    public static void m(){
        return 10;
    }
    */
    
    //这个方法可以
    public static void m1(){
        return;
    }
}

例子2:

public class MethodTest{
    public static void main(String[] args){
        m();
    }
    public static void m(){
        for(int i =0;i<5;i++){
            if(i == 5){
                //"return;"终止的不是for循环,是m()方法
                //所以下面的打印"The end"执行不到。
                return;
            }
            System.out.println(i);
        }
        System.out.println("The end");
    }
}

2.方法执行过程中的内存分配

2.1方法内存分配

*方法只定义不调用时,是不会执行的,并且在JVM中也不会给该方法分配“运行所属”的内存空间。

*在“ JVM内存 ”划分上有三个“主要”内存空间:方法区内存,堆内存,栈内存。

*关于“ 栈 ”数据结构:

(1)栈:stack,是一种数据结构

(2)数据结构反应的是数据存储形态,数据结构是独立学科,不属于任何编程语言的范畴。大部分编程语言都要使用到数据结构。

(3)作为程序员需要精通:数据结构 + 算法【计算机专业必修】

*常见的数据结构:数组、队列、栈、链表、二叉树……

2.2方法执行内存

1.方法代码片段

*方法代码片段数据.class字节码文件的一部分,字节码文件在类加载的时候,将其放到方法区中。所以JVM中三个主要的内存空间方法区内存最先有数据。

*代码片段虽然在方法区内存当中只有一份,但是可以重复调用。每次调用这个方法需要给方法分配独立的活动产所,在栈内存中分配。【栈内存中分配方法运行所属内存空间】

*方法调用时,给该方法分配独立的内存空间,在栈中分配,此时发生“ 压栈 ”动作。方法执行结束后,方法分配的内存空间全部释放,此时发生“ 弹栈 ”动作。

(1)压栈:给方法分配内存

(2)弹栈:释放该方法的内存空间

*局部变量在"方法体"中声明,运行阶段内存在栈中分配。

3.方法的重载机制

以下代码不使用“方法重载机制”,不使用overload。

缺点:sumInt,sumDouble,SumLong方法虽然功能不同,但是功能是相似的:都是求和。分别起了三个不同名字,调用时不方便。

public class OverloadTest{
    //main函数,程序入口
   public static void main(String[] args){
        //调用方法
        int result1  = sumInt(1,2);
        System.out.println(result1);
        double result2 = sumDouble(1.0,2.0);
        System.out.println(result2);
        long result3 = sumLong(1l,2l);
        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;
    }
}

方法重载机制/Overload:在不同的但"功能相似"的方法中,可以使用同一个方法名。

优点:调用时方便,虽然是调用不同的方法,但是感觉在使用一个方法一样。

前提:功能相似时,方法名可以相同。但是功能不同时,尽可能让两个方法名不同。

注:Java支持这种机制,但有些语言不支持。例如javascript。

public class OverloadTest{
    public static void main(String[] args){
        //调用方法的时候,就像在使用一个方法一样
        //参数类型不同,对应调用方法不同。此时区分方法不再依靠方法名,而是依靠参数的数据类型。
        System.out.println(sum(1,2));      //3
        System.out.println(sum(1.0,2.0));  //3.0
        System.out.println(sum(1L,2L));    //3
    }
    //以下三个方法构成了方法重载机制
    public static int sum(int a ,int b){
        return a + b;
    }
    public static double sum(double a ,double b){
        return a + b;
    }
    public static long sum(long a ,long b){
        return a + b;
    }
}

方法重载:

1.方法重载又称:overload

2.使用方法重载:功能相似,尽可能让方法名相同。(但功能不同/不相似的时候,尽可能让方法名不同)

3.满足方法重载条件:在同个类中;方法名相同;参数列表不同(数量,类型,顺序三者有一个不同)。

4.*方法重载和方法名+参数列表有关系。

​ *方法重载与返回值类型、修饰符列表无关。

方法重载应用:

public class OverloadTest{
    public static void main(String[] args){
        /*使用方法重载把下列代码简化
        System.out.println("Coffee add ice");
        System.out.println(10);
        System.out.println("true);
        */
       
        U.p("coffee add ice");  //coffee add ice
        U.p(10);  //10
        U.p(true);  //true
        
    }
}
//自定义类
class U{
    public static void p(byte b){
        System.out.println(b);
    }
     public static void p(int b){
        System.out.println(b);
    }
     public static void p(String b){
        System.out.println(b);
    }
     public static void p(char b){
        System.out.println(b);
    }
     public static void p(double b){
        System.out.println(b);
    }
     public static void p(boolean b){
        System.out.println(b);
    }
}

4.方法的递归

1.方法递归:方法自身调用自身。

*语法规则:

a(){
    a();
}

*递归是很耗费栈内存,可以不用时尽量不用。

*程序运行时发生一个错误:java.lang.StackOverFlowError。这个不是异常,是一个栈内存溢出错误。错误发生时无法挽回,JVM停止工作。

*递归一定要有结束条件,不然必定发生栈溢出错误。【有条件,如果递归太深也可能发生栈溢出错误】

注意:有些情况下必须依靠递归方式。例如:目录拷贝。

例子1:不使用递归,计算1~N的和

public class RecoursionTest{
    public static void main(String[] args){
        int n = 4;
        int retValue = sum(n);
        System.out.println(retValue);
    }
    public static int sum(int n){
        int result = 0;
        for(int i=1;i<=n;i++){
            result += i;
        }
        return result;
    }
}

例子2:使用递归,计算1~N的求和

public class RecoursionTest{
    public static void main(String[] rags){
        int a = sum(3);
        System.out.println(a);  //6
    }
    public static void sum(int n){
        if(n == 1){
            return 1;
        }
        return n + sum(n-1);
    }
}

例子3:不使用递归,计算N的阶乘(N!)。

注:N! = N * (N-1) *…… *2 *1

public static RecoursionTest{
    public static void main(String[] args){
        int n =5;
        int retValue = method(n);
        System.out.println(retValue);  //120
    }
    public static int method(int n){
        int result = 1;
        for(int i=n;i>0;i--){
            result *=i;
        }
        return result;
    }
}

例子4:使用递归,计算N的阶乘(N!)。

public static RecoursionTest{
    public static void main(String[] args){
        int n =5;
        int retValue = method(n);
        System.out.println(retValue); //120
    }
    public static int method(int n){
        if(n == 1){
            return 1;
        }
        return n * method(n-1);
    }
}

——本章节为个人学习笔记。学习视频为动力节点Java零基础教程视频:动力节点—JAVA零基础教程视频

;