javaSE_07_方法

Posted wurengen

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了javaSE_07_方法相关的知识,希望对你有一定的参考价值。

知识框架

技术图片

方法的本质以及作用

方法其实就是一段普通的代码片段,并且这段代码可以完成某个特定的功能,而且可以被重复的调用/使用。java 中的方法又叫做 method,在 C 语言中叫做函数。

方法(method、函数)概述:

  • 方法是类或对象行为特征的抽象,用来完成某个功能操作。在某些语言中 也称为函数或过程。
  • 将功能封装为方法的目的是,可以实现代码重用,简化代码
  • Java里的方法不能独立存在,所有的方法必须定义在类里。
  • 方法必须先创建才可以使用,该过程成为方法定义
  • 方法创建后并不是直接可以运行的,需要手动使用后,才执行,该过程成为方法调用
  • 形参后面使用一对儿大括号括起来的是方法体,方法体是完成功能的核心代码,方法体中的代码有执行顺序的要求,遵循自上而下的顺序依次逐行执行,不存在跳行执行的情况。

方法的定义以及调用

技术图片

对于上面的语法格式中具体说明如下:

  • 修饰符列表:此项是可选项,不是必须的,目前大家统一写成 public static
  • 返回值类型:返回值一般指的是一个方法执行结束之后的结果。结果通常是一个数据,所以被称为“值”,而且还叫“返回值”。方法就是为了完成某个特定的功能,方法结束之后大部分情况下都是有一个结果的,而体现结果的一般都是数据。此项可以是 java 语言当中任何一种数据类型,包括基本数据类型,也包括所有的引用数据类型,当然,如果一个方法执行结束之后不准备返回任何数据,则返回值类型必须写 void。返回值类型例如:byte,short,int,long,float,double,boolean,char,String,void ..
  • 方法名:此项需要是合法的标识符,开发规范中要求方法名首字母小写,后面每个单词首字母大写,遵循驼峰命名方式,见名知意,例如:login、getUsername、findAllUser 等。
  • 形式参数列表:(int a, int b)此项又被称为形参,其实每一个形参都是“局部变量”,形参的个数为 0~N 个,如果是多个参数,则采用半角“,”进行分隔,形参中起决定性作用的是参数的数据类型,参数名就是变量名,变量名是可以修改的,也就是说(int a , int b)也可以写成(int x , int y)。
  • 方法体:由一对儿大括号括起来,在形参的后面,这个大括号当中的是实现功能的核心代码,方法体由 java 语句构成,方法体当中的代码只能遵循自上而下的顺序依次逐行执行,不能跳行执行,核心代码在执行过程中如果需要外部提供数据,则通过形参进行获取。
  • 返回值:被return语句返回的值,该值会返回给调用者

代码举例

/*

    注意:
        程序开始执行的时候是先执行main方法。
        因为main方法是一个入口。

        在java语言中所有的方法体中的代码都必须遵循自上而下的顺序依次逐行执行。
        这个必须记住。

        main方法不需要程序员手动调用,是由JVM调用的。
        但是除了main方法之外其他的方法,都需要程序员
        手动调用,方法只有调用的时候才会执行,方法不调用
        是不会执行的。
*/
public class MethodTest {

    // 方法定义在类体当中。
    // 方法定义的先后顺序没有关系。都可以。

    // 主方法。入口。
    public static void main(String[] args) { // 自上而下依次逐行执行。
        // 需求1:请编写程序,计算100和200的求和。
        sumInt(100, 200);
        // 需求2:请编写程序,计算666和888的求和。
        sumInt(666, 888);
        // 需求3:请编写程序,计算111和222的和
        sumInt(111, 222);
    }

    // 专门在这个类体当中定义一个方法,这个方法专门来完成求和。
    // x y z在以下的sumInt方法中都属于局部变量
    // 局部变量有一个特点:方法结束之后,局部变量占用的内存会自动释放。
    public static void sumInt(int x, int y) { // 自上而下的顺序依次逐行执行。
        int z = x + y;
        System.out.println(x + "+" + y + "=" + z);
    }

}

方法的分类

 技术图片

定义方法时,要做到两个明确

  • 明确返回值类型:主要是明确方法操作完毕之后是否有数据返回,如果没有,写void;如果有,写对应的数据类型 
  • 明确参数:主要是明确参数的类型和数量 

静态方法调用的三种形式

  • 直接调用:直接写方法名调用。a()方法调用b()方法的时候,a和b方法都在同一个类中,“类名.”可以省略。如果不在同一个类中“类名.”不能省略。
  • 赋值调用:调用方法,在方法前面定义变量,接收方法返回值
  • 输出语句调用: 在输出语句中调用方法, System.out.println(方法名()) 
需要注意的是,方法在调用的时候,实际传给这个方法的数据被称为实际参数列表,简称实参,java 语法中有这样的规定:实参和形参必须一一对应,所谓的一一对应就是,个数要一样,数据类型要对应相同。例如:实参(100 , 200)对应的形参(int x , int y),如果不是一一对应则编译器就会报错。当然也可能会存在自动类型转换,例如:实参(100 , 200)也可以传递给这样的形参(long a , long b)。

调用方法时的注意:

  • void类型的方法,直接调用即可
  • 非void类型的方法,推荐用变量接收调用

代码举例:

public class Demo02MethodDefine {

    public static void main(String[] args) {
        // 单独调用
        sum(10, 20);
        System.out.println("===========");

        // 打印调用
        System.out.println(sum(10, 20)); // 30
        System.out.println("===========");

        // 赋值调用
        int number = sum(15, 25);
        number += 100;
        System.out.println("变量的值:" + number); // 140
    }

    public static int sum(int a, int b) {
        System.out.println("方法执行啦!");
        int result = a + b;
        return result;
    }

}

方法使用的注意事项

  • 方法应该定义在类当中,但是不能在方法当中再定义方法。不能嵌套。
  • 方法定义的前后顺序无所谓。
  • 方法定义之后不会执行,如果希望执行,一定要调用:单独调用、打印调用、赋值调用。
  • 如果方法有返回值,那么必须写上“return 返回值;”,不能没有。
  • return后面的返回值数据,必须和方法的返回值类型,对应起来。
  • 对于一个void没有返回值的方法,不能写return后面的返回值,只能写return自己。
  • 对于void方法当中最后一行的return可以省略不写。
  • 一个方法当中可以有多个return语句,但是必须保证同时只有一个会被执行到,两个return不能连写。
  • 带有 return 关键字的语句只要执行,所在的方法则执行结束。 

break;语句和return;语句有什么区别? 

  • break;用来终止switch和离它最近的循环。
  • return;用来终止离它最近的一个方法

栈数据结构

栈:stack,又称堆栈,它是运算受限的线性表,其限制是仅允许在标的一端进行插入和删除操作,不允许在其 他任何位置进行添加、查找、删除等操作。

简单的说:采用该结构的集合,对元素的存取有如下的特点

  • 先进后出(即,存进去的元素,要在后它后面的元素依次取出后,才能取出该元素)。例如,子弹压进弹 夹,先压进去的子弹在下面,后压进去的子弹在上面,当开枪时,先弹出上面的子弹,然后才能弹出下面的子弹。
  • 栈的入口、出口的都是栈的顶端位置。

这里两个名词需要注意:

  • 压栈:就是存元素。即,把元素存储到栈的顶端位置,栈中已有元素依次向栈底方向移动一个位置
  • 弹栈:就是取元素。即,把栈的顶端位置元素取出,栈中已有元素依次向栈顶方向移动一个位置。

技术图片

方法执行过程中内存的变化

Java虚拟机的内存划分 :为了提高运算效率,就对空间进行了不同区域的划分,因为每一片区域都有特定的处理数据方式和内存管理方式。如下图所示:

技术图片
好了,接下来我们开始学习方法执行过程中内存是如何变化的?我们先来看一张图片:

 技术图片

上图是一张标准的 java 虚拟机内存结构图,目前我们只看其中的“栈”和“方法区”,其它的后期研究,方法区中存储类的信息,或者也可以理解为代码片段,方法在执行过程中需要的内存空间在栈中分配。java 程序开始执行的时候先通过类加载器子系统找到硬盘上的字节码(class)文件,然后将其加载到 java 虚拟机的方法区当中,开始调用 main 方法,main 方法被调用的瞬间,会给 main 方法在“栈”内存中分配所属的活动空间,此时发生压栈动作,main 方法的活动空间处于栈底。

也就是说,方法只定义不去调用的话,只是把它的代码片段存储在方法区当中,java 虚拟机是不会在栈内存当中给该方法分配活动空间的,只有在调用的瞬间,java 虚拟机才会在“栈内存”当中给该方法分配活动空间,此时发生压栈动作,直到这个方法执行结束的时候,这个方法在栈内存中所对应的活动空间就会释放掉,此时发生弹栈动作。由于栈的特点是先进后出,所以最先调用的方法(最先压栈)一定是最后结束的(最后弹栈)。比如:main 方法最先被调用,那么它一定是最后一个结束的。换句话说:main 方法结束了,程序也就结束了(目前来说是这样)。 

接下来我们来看一段代码,同时画出内存结构图,以及使用文字描述该程序的内存变化:
public class MethodTest {
    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");
        System.out.println("m2 over");
    }
}
运行结果如下图所示:
技术图片
通过上图的执行结果我们了解到,main 方法最先被调用,但是它是最后结束的,其中 m2方法最后被调用,但它是最先结束的。大家别忘了调用的时候分配内存是压栈,结束的时候是释放内存弹栈哦。为什么会是上图的结果呢,我们来看看它执行的内存变化,请看下图:
技术图片
通过上图的分析,可以很快明白,为什么输出结果是这样的顺序,接下来我们再采用文字的方式描述它的内存变化: 
  1. 类加载器将 class 文件加载到方法区。
  2. 开始调用 main方法,在栈内存中给 main方法分配空间,开始执行 main方法,输出”mainbegin”。
  3. 调用 m1()方法,在栈内存中给 m1()方法分配空间,m1()方法处于栈顶,具备活跃权,输出”m1 begin”。
  4. 调用 m2()方法,在栈内存中给 m2()方法分配空间,m2()方法处于栈顶,具备活跃权,输出”m2 begin”,继续输出”m2 over”。
  5. m2()方法执行结束,内存释放,弹栈。
  6. m1()方法这时处于栈顶,具备活跃权,输出”m1 over”。
  7. m1()方法执行结束,内存释放,弹栈。
  8. main()方法这时处于栈顶,具备活跃权,输出”main over”。
  9. main()方法执行结束,内存释放,弹栈。
  10. 栈空了,程序结束。
大家是否还记得之前的课程中曾经提到方法体当中的代码是有执行顺序的,必须遵循自上而下的顺序依次逐行执行,当前行代码必须执行结束之后,下一行代码才能执行,不能跳行执行,还记得吗?现在再和栈数据结构一起联系起来思考一下,为什么要采用栈数据结构呢?是不是只有采用这种先进后出的栈数据结构才可以保证代码的执行顺序呢!

方法重载/overload

什么是方法重载呢?

方法重载(overload)是指在一个类中定义多个同名的方法,但要求每个方法具有不同的参数的类型或参数的个数。调用重载方法时,Java 编译器能通过检查调用的方法的参数类型和个数选择一个恰当的方法。方法重载通常用于创建完成一组任务相似但参数的类型或参数的个数不同的方法。调用方法时通过传递给它们的不同个数和类型的实参来决定具体使用哪个方法。

总结:

方法重载指同一个类中定义的多个方法之间的关系,满足下列条件的多个方法相互构成重载

  • 多个方法在同一个类中
  • 多个方法具有相同的方法名
  • 多个方法的参数不相同,个数不同算不同,顺序不同算不同,类型不同也算不同。

重载的特点:

  • 与返回值类型无关,只看参数列表,且参数列表必须不同。(参数个数或参数类 型)。调用时,根据方法参数列表的不同来区别。

代码举例

package com.itheima_05;
 
/*
    方法重载:
        多个方法在同一个类中
        多个方法具有相同的方法名
        多个方法的参数不相同,类型不同或者数量不同
        与返回值无关
        在调用的时候,Java虚拟机会通过参数的不同来区分同名的方法
 */
public class MethodDemo {
    public static void main(String[] args) {
        //调用方法
        int result = sum(10,20);
        System.out.println(result);
 
        double result2 = sum(10.0,20.0);
        System.out.println(result2);
 
        int result3 = sum(10,20,30);
        System.out.println(result3);
    }
 
    //需求1:求两个int类型数据和的方法
    public static int sum(int a, int b) {
        return a + b;
    }
 
    //需求2:求两个double类型数据和的方法
    public static double sum(double a, double b) {
        return a + b;
    }
 
    //需求3:求三个int类型数据和的方法
    public static int sum(int a, int b, int c) {
        return a + b + c;
    }
 
}

方法递归

递归:递归其实就是方法在执行的过程中调用了另一个方法,而另一个方法则是自己本身。

递归的分类:
  • 递归分为两种,直接递归和间接递归。
  • 直接递归称为方法自身调用自己。
  • 间接递归可以A方法调用B方法,B方法调用C方法,C方法调用A方法。

注意事项:

  • 递归一定要有条件限定,保证递归能够停止下来,否则会发生栈内存溢出。
  • 在递归中虽然有限定条件,但是递归次数不能太多。否则也会发生栈内存溢出。
  • 构造方法,禁止递归

代码举例

/*
    方法递归?
        1、什么是方法递归?
            方法自己调用自己,这就是方法递归。

        2、当递归时程序没有结束条件,一定会发生:
            栈内存溢出错误:StackOverflowError
            所以:递归必须要有结束条件。(这是一个非常重要的知识点。)

            JVM发生错误之后只有一个结果,就是退出JVM。

        3、递归假设是有结束条件的,就一定不会发生栈内存溢出错误吗?
            假设这个结束条件是对的,是合法的,递归有的时候也会出现栈内存溢出错误。
            因为有可能递归的太深,栈内存不够了。因为一直在压栈。
        
        4、在实际的开发中,不建议轻易的选择递归,能用for循环while循环代替的,尽量
        使用循环来做。因为循环的效率高,耗费的内存少。递归耗费的内存比较大,另外
        递归的使用不当,会导致JVM死掉。
        (但在极少数的情况下,不用递归,这个程序没法实现。)
        所以:递归我们还是要认真学习的。

        5、在实际的开发中,假设有一天你真正的遇到了:StackOverflowError
        你怎么解决这个问题,可以谈一下你的思路吗?
            我来谈一下我的个人思路:
                首先第一步:
                    先检查递归的结束条件对不对。如果递归结束条件不对,
                    必须对条件进一步修改,直到正确为止。

                第二步:假设递归条件没问题,怎么办?
                    这个时候需要手动的调整JVM的栈内存初始化大小。
                    可以将栈内存的空间调大点。(可以调整大一些。)
                
                第三步:调整了大小,如果运行时还是出现这个错误,
                没办法,只能继续扩大栈的内存大小。
                
                (java -X)这个可以查看调整堆栈大小的参数
*/

// 使用递归,请编写程序,计算1~n的和。
public class RecursionTest03{
    public static void main(String[] args){
        // 1~3的和
        int n = 3;
        int r = sum(n);
        System.out.println(r); // 6
    }

    public static int sum(int n){
        //n最初等于3
        // 3 + 2 (2是怎么的出来的:n - 1)
        //sum(n - 1); 
        if(n == 1){
            return 1;
        }
        // 程序能执行到此处说明n不是1
        return n + sum(n-1);
    }
}

???????递归的基本使用:

package FileDemo;
 
public class DiGuiDemo {
    public static void main(String[] args) {
        //回顾不死神兔问题,求第20个月兔子的对数
        // 每个月的兔子对数:1,1,2,3,5,8,...
        int[] arr = new int[20];
        arr[0] = 1;
        arr[1] = 1;
        for (int i = 2; i < arr.length; i++) {
            arr[i] = arr[i - 1] + arr[i - 2];
        }
        System.out.println(arr[19]);
        System.out.println(f(20));
    }
    /*
    递归解决问题,首先就是要定义一个方法:
     定义一个方法f(n):表示第n个月的兔子对数 那么,第n-1个月的兔子对数该如何表示呢?
     f(n-1) 同理,第n-2个月的兔子对数该如何表示呢?
     f(n-2)
     StackOverflowError:当堆栈溢出发生时抛出一个应用程序递归太深
     */
 
    public static int f(int n) {
        if (n == 1 || n == 2) {
            return 1;
        } else {
            return f(n - 1) + f(n - 2);
        }
    }
}

以上是关于javaSE_07_方法的主要内容,如果未能解决你的问题,请参考以下文章

JavaSE知识-08(面向对象_继承&方法&final)

JavaSE基础入门_019_Java8特性

JavaSE_坚持读源码_Class对象_Java1.7

JavaSE复习_3 继承

JavaSE_03_Thread类02

javaSE_《图书馆管理系统》_