跳到主要内容

第7章-方法

思考:下面这段代码是否有问题?如何改进?

package com.bjpowernode.demo;

/**
* 方法,加法计算,未使用方法
*/
public class MethodDemo {
public static void main(String[] args) {
//加法计算1
int first = 11;
int second = 22;
int third = first + second;
System.out.println(first + "+" + second + "=" + third);

//加法计算2
int one = 28;
int two = 34;
int three = one + two;
System.out.println(one + "+" + two + "=" + three);

//加法计算3
int temp1 = 150;
int temp2 = 330;
int result = temp1 + temp2;
System.out.println(temp1 + "+" + temp2 + "=" + result);
}
}

概述

  • 方法是定义在类中的具有特定功能的一段独立代码

  • 也叫函数

  • 通俗的说,方法是一段可重复调用的代码

  • 定义方法的主要原因

    • 共用的代码
    • 独立业务功能的内聚
  • 方法名称使用动词动名词形式

  • 上面思考部分的问题,是三个加法运算的逻辑基本一致,每多一次加法,都要写重复的代码(差异只是变量的名字和值);使用方法进行改进的版本如下:

    package com.bjpowernode.demo;

    /**
    * 方法,加法计算,使用方法
    */
    public class MethodDemo {
    public static void main(String[] args) {
    //加法计算1
    MethodDemo.addInt(11,22);

    //加法计算2
    MethodDemo.addInt(28,34);

    //加法计算3
    MethodDemo.addInt(150,330);
    }

    /**
    * 整型加法计算
    * @param first 操作数1
    * @param second 操作数2
    */
    public static void addInt(int first, int second){
    int result = first + second;
    System.out.println(first + "+" + second + "=" + result);
    }
    }

    可见,更加简洁,代码也更少;更重要的是,当加法计算的逻辑变化时,只需要改下面的add方法逻辑

定义方法

  • 语法规则

    访问修饰符 [其他修饰符] 返回值类型 方法名([参数类型 参数名,参数类型 参数名,...]){
    //方法体
    return 返回值;
    }

    语法解释:

    • []:表示可选
    • 访问修饰符:包括public、protected、默认修饰符、private,后面会讲解,暂时都使用public
    • 其他修饰符:可选,包括static、abstract等,后面会讲解,暂时都使用static,表示静态的
    • 返回值类型
      • 可以是void(表示无返回值)、8种基本数据类型、引用数据类型
      • 与返回值(可以是字面量、变量或表达式)数据类型要一致
    • 方法名:自定义的方法名,一个典型的标识符,遵守标识符命名规则,使用小驼峰,并且是动词或动名词组合;如sayHello、payBills等
    • 参数,也称形式参数,简称形参、入参:
      • 与变量定义规则相同,作用域也只在方法体中多个形式参数使用逗号(,)分隔,如:int first,int second
      • 用于方法调用时接收实际参数(简称为实参)的值
      • 可以是0~n个形式参数
    • 方法体:接在参数后,使用{}包围
    • 返回值,可选:
      • 使用return关键字返回
      • 如果返回值类型为void,则直接使用return;返回即可;如果业务上不需要,也可以不使用return
      • 如果返回值类型不为void,则需要在方法所有路径都返回与返回值类型一致的值(可以是字面量、变量或表达式)
  • 方法分类

    • 普通方法,依托对象存在,多表示对象行为;后面会讲解
    • 构造方法,依托对象存在,用于对象初始化;后面会讲解
    • 静态方法,依托类存在,通过“类名.方法名”调用,本章讲的都是静态方法
  • 应用实例

    • 实例1,无参、无返回值方法

      package com.bjpowernode.demo;

      public class MethodDemo {
      /**
      * 打招呼
      * 无参、无返回值方法
      */
      public static void sayHello() {
      System.out.println("你好,世界!");
      }
      }
    • 实例2,有参、无返回值方法

      package com.bjpowernode.demo;

      public class MethodDemo {
      /**
      * 跟人打招呼
      * 有参、无返回值方法
      * @param name 姓名
      */
      public static void sayToSomeone(String name) {
      System.out.println("你好" + name + "!");

      return;
      }
      }
    • 实例3,有参、有返回值方法

      package com.bjpowernode.demo;

      public class MethodDemo {
      /**
      * 获取全称
      * 有参、有返回值方法
      * @param firstName 名
      * @param lastName 姓
      * @return 全称
      */
      public static String getFullName(String firstName, String lastName) {
      String fullName = firstName + "." + lastName;

      return fullName;
      }
      }
    • 实例4,有参、有返回值,入参与返回值类型不相同

      package com.bjpowernode.demo;

      public class MethodDemo {
      /**
      * 获取姓名长度
      * 有参、有返回值,参数与返回值类型不相同
      * @param name 姓名
      * @return 姓名长度
      */
      public static int getNameLength(String name) {
      int length = name.length();

      return length;
      }
      }
    • 实例5,有参、有返回值方法,参数为数组

      package com.bjpowernode.demo;

      public class MethodDemo {
      /**
      * 汇总
      * 有参、有返回值方法,参数为数组
      * @param datas
      * @return
      */
      public static int sum(int[] datas){
      int sum = 0;

      for (int i=0; i<datas.length; i++){
      sum += datas[i];
      }

      return sum;
      }
      }

使用方法

  • 方法定义好后,就可以重复使用

  • 不同类型的方法,调用方式不相同,本章定义的静态方法,都使用“类名.方法名”调用;如果在当前类,也可以直接 通过“方法名“调用,但不建议这样调用

  • 使用方法时,给方法传递的参数需要具备实际的值,称为实际参数,简称实参

  • 实参->形参,实际参数的值会传递给形式参数

  • 注意,调用方法时

    1、实际参数与形式参数的个数必须相同

    2、实际参数与形式参数的类型必须一致

    3、返回值类型必须一致

  • 应用实例,针对上述“定义方法”中的5个实例,类名假设为MethodDemo,调用方式如下

    • 实例1,无参、无返回值方法

      //调用打招呼方法
      MethodDemo.sayHello();
    • 实例2,有参、无返回值方法

      //调用跟人打招呼方法
      MethodDemo.sayToSomeone("张三");
    • 实例3,有参、有返回值方法

      //调用获取全称方法
      String firstName = "尼古拉斯";
      String lastName = "赵四";
      String fullName = MethodDemo.getFullName(firstName,lastName);
      System.out.println(fullName);
    • 实例4,有参、有返回值,入参与返回值类型不相同

      //调用获取姓名长度方法
      int length = MethodDemo.getNameLength("尼古拉斯.赵四");
      //输出返回值
      System.out.println(length);
    • 实例5,有参、有返回值方法,参数为数组

      //调用汇总方法
      int[] myDatas = {1,2,3,4,5,6};
      int sum = MethodDemo.sum(myDatas);
      //输出返回值
      System.out.println(sum);

【练习】

  1. 练习实例内容,完成代码编写
  2. 编写程序,定义一个MyMath类,定义加、减、乘、除4个方法,进行2个操作数的运算,4个方法的形式参数的数据类型别为byte、short、int、long类型,并在main函数中进行调用测试
  3. 【扩展】编写程序,定义一个MyString类,包含一个字符串连接方法
    1. 接收三个形式参数:
      1. 一个参数是字符串数组,如{“第一个”,"第二个","第三个"}
      2. 一个参数是连接符号,如-,*
      3. 一个参数是连接符号的个数,如3
    2. 在方法体中,将字符串中所有元素连接起来,中间使用指定的个数的连接符号连接起来
    3. 示例数据:
      1. 示例数据1:三个实际参数为:{“第一个”,"第二个","第三个"}、"-"、3;则字符串连接方法返回如下字符串:第一个---第二个---第三个
      2. 示例数据2:三个实际参数为:{"张三","李四"}、"*"、"5";则字符串连接方法返回如下字符串:张三*****李四
      3. ...

方法使用注意点

提问:下面的代码有问题码?如果有,如何调整?

  1. public static long addLong(){

    }
  2. public static int addInt(){
    return 0;
    return 1;
    return 2;
    }
  3. public static int addInt(int first,int second) {
    return "相加的结果是:" + first + second;
    }
  4. public static int addInt(){
    return 0;

    System.out.println("正在进行整型加法...");
    }
  5. 注意:如果方法返回值类型不为void,方法中所有路径必须有return返回值

    public static String compareAge(int age){
    if(age<=18){
    return "少年";
    }else if(age<30){
    return "青年";
    }
    }
  6. public static void sayHello(){
    return 0;
    }
  7. 形参为int类型,实参为long类型,是否可以?反过来呢?

    1. 形参为int类型,实参为long类型

      方法定义

      /**
      * 汇总方法
      * @param count 最大值
      * @return 汇总值
      */
      public static int sum(int count) {
      //汇总变量
      int sum = 0;

      for (int i = 1; i <= count; i++) {
      //汇总
      sum += i;
      }

      //返回
      return sum;
      }

      方法使用

      //汇总方法调用
      long count = 15;
      int result = MethodDemo.sum(count);
    2. 形参为long类型,实参为int类型

      方法定义

      /**
      * 汇总方法
      * @param count 最大值
      * @return 汇总值
      */
      public static long sum(long count) {
      //汇总变量
      long sum = 0;

      for (int i = 1; i <= count; i++) {
      //汇总
      sum += i;
      }

      //返回
      return sum;
      }

      方法使用

      //汇总方法调用
      int count = 10;
      long result = MethodDemo.sum(count);

    注意:

    1、形参为大类型,实参为小类型,直接兼容,俗称一致;

    2、形参为小类型,实参为大类型,不能调用,即不一致,需要进行强制类型转换

  8. 循环中返回,是否可以?

    public static int sum(int count) {
    //汇总变量
    int sum = 0;

    for (int i = 1; i <= count; i++) {
    //汇总
    sum += i;

    //如果当前循环变量大于5,返回
    if (i > 5) {
    return sum;
    }
    }

    //其他业务
    System.out.println("记录一下汇总值:" + sum);

    //其他情况返回
    return sum;
    }
  9. 方法是否可以嵌套调用其他方法?是否可调用多个?

【练习】

  1. 练习实例内容,熟悉方法使用中的注意点

方法调用栈【扩展】

概述

  • 实际项目中,多个方法往往会协同合作,有层次性的被嵌套调用,以完成业务

  • 比如,一个用户购买行为的下单方法,往往会调用支付、扣减库存、生成物流单、推送消息、增加用户积分等多个子方法

    其中的支付方法,会在方法中继续调用检查余额、扣款等方法

    其中的推送消息子方法,会在方法中继续调用获取用户信息、给指定网络服务商推送消息、回调等孙方法

  • 在这些嵌套调用过程中,父方法调用子方法时,父方法会被挂起,这种机制,在JVM处理中,使用栈形式实现

栈特点

  • 生活中的场景:

    • 农村以前有那种腌肉的缸,或是北方冬天腌大白菜的,先往里面放的肉或大白菜,一般是最后吃;而后放进去的会先吃
    • 家里衣柜叠的很多层衣服,一把也是一件往里面叠起来,拿的时候,从上面一件一件拿,先拿的都是后叠上去的
  • 栈(Stack)特点:先进后出、后进先出,操作包含入栈(Push)和出栈(Pop),如下图

    方法-栈

  • 出栈和入栈的过程,如下图

    方法-入栈和出栈

    入栈和出栈过程

JVM中的栈

  • JVM中,提供一系列机制,保证程序数据的有序存储和访问

    方法-JVM

    JVM结构
  • 其中,最重要的就包括栈和方法区;栈遵守上面描述的栈的特点;方法区则存放类、方法等原始信息,俗称元信息

  • 方法在有层次的嵌套调用过程中,父方法挂起时,就被放入到栈内;子方法调用完成时,父方法重新继续被调用,推出到栈外

应用实例

  • 实例1,下面的代码模拟了一个下单方法,方法调用层次是main->order->pay->deduct

    package com.bjpowernode.demo;

    /**
    * 方法调用栈,模拟下单方法
    */
    public class StackDemo {
    /**
    * 入口方法
    * @param args 命令参数
    */
    public static void main(String[] args) {
    System.out.println("开始main()...");

    //下单
    StackDemo.order();

    System.out.println("完成main()。");
    }

    /**
    * 下单
    */
    public static void order(){
    System.out.println("开始下单...");

    //调用支付方法
    StackDemo.pay();

    System.out.println("完成下单。");
    }

    /**
    * 支付
    */
    public static void pay(){
    System.out.println("开始支付...");

    // //检查余额
    // StackDemo.checkBalance(); //打开注释查看效果
    //扣款
    StackDemo.deduct();

    System.out.println("完成支付。");
    }

    /**
    * 检查余额
    */
    public static void checkBalance(){
    System.out.println("开始检查余额...");

    System.out.println("完成检查余额。");
    }

    /**
    * 扣款
    */
    public static void deduct(){
    System.out.println("开始扣款...");

    System.out.println("完成扣款。");
    }
    }
  • 可以通过在main入口,打入断点,以调试模式运行;然后,在Debugger窗口的Frames中查看效果

    方法-Debugger

    调试运行模式下查看方法调用栈

【练习】

  1. 练习实例内容,熟悉方法调用特点,并掌握如何在调试中查看方法调用栈效果

方法重载

package com.bjpowernode.demo;

/**
* 方法,加法计算,使用方法
*/
public class MethodDemo {
public static void main(String[] args) {
//加法计算1
MethodDemo.addInt(11,22);

//加法计算2
MethodDemo.addInt(28,34);

//加法计算3
MethodDemo.addInt(150,330);

//加法计算n
}

/**
* 整型加法计算
* @param first 操作数1
* @param second 操作数2
*/
public static void addInt(int first, int second){
int result = first + second;
System.out.println(first + "+" + second + "=" + result);
}
}

问题:上面是在《定义方法》中讲到的加法运算方法addInt。如果现在希望有一个long类型的加法、String类型的加法,如何定义?再定义一个addLong、addString方法?

:明显不合适,因为每个行为后都要带一个数据类型,其实参数就能决定,直接使用多个add,但参数不同即可;另外,可参考System.out.print方法设计,学习Java中自带类中方法实现

概述

  • 上面说的直接使用多个add方法,但参数不同,其实也与System.out.print方法机制类似
  • 这种机制就是“方法重载”(Overload)
  • 普通方法、构造方法和静态方法都可以重载,方式一致

定义规则

  1. 在同一个类中
  2. 方法名称完全相同,包括大小写
  3. 形式参数列表完全不同
    1. 参数个数不同,推荐
    2. 参数类型不同,推荐
    3. 参数个数相同、参数类型相同,但参数顺序不同,不推荐,容易带来使用上理解困难

注意:方法重载,不以返回值类型不同作为规则

使用方式

  • 使用时,调用相同方法,但由于传递的实际参数的个数、类型不同,会自动选择形式参数个数、类型匹配的方法进行调用
  • 可通过System.out.print输出不同类型的数据查看效果
  • 对于自定义的重载方法,可通过调试运行模式,断点跟踪代码走向查看实际调用方法

应用实例

  • 实例1,不同的加法示例

    package com.bjpowernode.demo;

    /**
    * 方法重载,不同类型的加法示例
    */
    public class OverloadDemo {
    /**
    * 入口方法
    *
    * @param args 命令参数
    */
    public static void main(String[] args) {
    //将会调用int类型参数的重载方法
    int first = 10;
    int second = 20;
    int intResult = OverloadDemo.add(first,second);
    System.out.println(intResult);

    //将会调用long类型参数的重载方法
    long one = 200;
    long two = 300;
    long longResult = OverloadDemo.add(one,two);
    System.out.println(longResult);

    //将会调用3个参数的重载方法
    int third = 30;
    intResult = OverloadDemo.add(first,second,third);
    System.out.println(intResult);
    }

    /**
    * 方法重载,整型方法
    *
    * @param first
    * @param second
    * @return
    */
    public static int add(int first, int second) {
    int result = first + second;

    return result;
    }

    /**
    * 方法重载,类型不同
    *
    * @param first
    * @param second
    * @return
    */
    public static long add(long first, long second) {
    long result = first + second;

    return result;
    }

    /**
    * 方法重载,参数个数不同
    *
    * @param first
    * @param second
    * @param third
    * @return
    */
    public static int add(int first, int second, int third) {
    int result = first + second + third;

    return result;
    }
    }

【练习】

  1. 提问

    1. 下面两个方法,如果在同一个类中,是重载方法吗?

      public static int add(int first, int second) {
      //方法体省略
      }

      public static int add(int one, int two) {
      //方法体省略
      }
    2. 下面两个方法,如果在同一个类中,是重载方法吗?

      public static int add(int first, int second) {
      //方法体省略
      }

      public static String add(int first, int second) {
      //方法体省略
      }
    3. 下面两个方法,如果在同一个类中,是重载方法吗?

      public static int operate(int first, String second) {
      //方法体省略
      }

      public static int operate(String first, int second) {
      //方法体省略
      }
  2. 练习实例内容,完成代码编写;掌重载方法的定义和使用

  3. 编写程序,定义一个MyOperation类,包含多个方法名为operate的重载方法,重载方法的参数与逻辑如下

    1. 进行两个整数相加,并返回
    2. 进行两个小数相加,并返回
    3. 进行三个小数相加,并返回
    4. 进行两个字符串连接,并返回
    5. 进行一个字符串数组中所有元素连接,使用"."连接,并返回;如形式参数为{"尼古拉斯","海棠","赵四"},则返回:尼古拉斯.海棠.赵四

    最后,定义一个MyOperationTest类,在其main方法中对MyOperation类的每个operate方法进行测试

递归方法【扩展】

问题:下面的代码,会出现什么问题?

package com.bjpowernode.demo;

/**
* 递归方法
*/
public class RecurionDemo {
/**
* 入口方法
* @param args
*/
public static void main(String[] args) {
//调用跳舞方法
RecurionDemo.dance();
}

/**
* 跳舞
*/
public static void dance(){
System.out.println("一起跳舞一起嗨...");

RecurionDemo.dance(); //递归调用

System.out.println("嗨完继续喝起来。");
}
}

:递归方法使用不当,耗尽JVM中栈资源,产生StackOverflowError异常,程序崩溃。

概述

  • 递归方法一般是自己调用自己
  • 在实际项目中,有极少部分的业务中需要使用递归形式的方法调用
  • 如,树形商品类型的遍历、树形组织机构的遍历等
  • 递归方法使用要适当,不能像提问的那样,有无穷的方法调用层次,导致程序崩溃
  • 也就是说,要设计好递归的结束逻辑

应用实例

  • 实例1,递归累加;注意其中return 1;的代码

    package com.bjpowernode.demo;

    import java.util.Scanner;

    /**
    * 递归方法,递归累加
    */
    public class RecurionDeo {
    /**
    * 入口方法
    *
    * @param args
    */
    public static void main(String[] args) {
    //接收输入
    Scanner scanner = new Scanner(System.in);
    System.out.print("请输入进行累加的整数:");
    int index = scanner.nextInt();

    //调用递归累加方法
    int result = RecurionDeo.add(index);

    //输出
    System.out.println("递归累加结果为:" + result);
    }

    /**
    * 递归累加
    *
    * @param index
    * @return
    */
    public static int add(int index) {
    if (index > 1) {
    return index + add(index - 1);
    } else {
    //重要逻辑,当index为1时,返回1;没有这个逻辑,会导致递归方法无穷调用,程序崩溃
    return 1;
    }
    }
    }

【练习】

  1. 练习实例内容,完成代码编写;熟悉递归方法的定义与使用

实战和作业

  1. 编写程序,模拟用户登录,接收用户多次输入用户名和密码进行验证
    1. 定义check方法,检查用户名和密码,输入参数为用户名和密码,返回用户名和密码是否正确;如果形式参数值用户名为jack,密码为123,返回验证成功;否则,返回验证失败
    2. 定义login方法,能多次接收用户输入用户名、密码,并调用check方法检查用户名和密码是否正确,如果正确,提示登录成功,结束循环;如果不正确,提示用户名和密码不正确,再次输入
    3. 测试上述逻辑
  2. 编写程序,定义三个重载方法max,具体逻辑如下
    1. 接收两个整数,返回较大整数
    2. 接收三个整数,返回较大整数,尝试利用上面那个方法完成
    3. 接收一个字符串数组,返回长度最长的那个字符串,如果有多个,返回其中任一个即可
  3. 【扩展】编写程序,完成输入一个整数,返回这个整数的阶乘值,使用递归实现
  4. 【扩展,读懂就好,工具和代码中有示例代码】编写程序,完成输出指定个数斐波那契数列;如输出指定个数为15,那输出的斐波那契数列1 1 2 3 5 8 13 21 34 55 ...,结合使用for循环、函数定义、函数递归调用