第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);
【练习】
- 练习实例内容,完成代码编写
- 编写程序,定义一个MyMath类,定义加、减、乘、除4个方法,进行2个操作数的运算,4个方法的形式参数的数据类型别为byte、short、int、long类型,并在main函数中进行调用测试
- 【扩展】编写程序,定义一个MyString类,包含一个字符串连接方法
- 接收三个形式参数:
- 一个参数是字符串数组,如{“第一个”,"第二个","第三个"}
- 一个参数是连接符号,如-,*
- 一个参数是连接符号的个数,如3
- 在方法体中,将字符串中所有元素连接起来,中间使用指定的个数的连接符号连接起来
- 示例数据:
- 示例数据1:三个实际参数为:{“第一个”,"第二个","第三个"}、"-"、3;则字符串连接方法返回如下字符串:第一个---第二个---第三个
- 示例数据2:三个实际参数为:{"张三","李四"}、"*"、"5";则字符串连接方法返回如下字符串:张三*****李四
- ...
- 接收三个形式参数:
方法使用注意点
提问:下面的代码有问题码?如果有,如何调整?
public static long addLong(){
}public static int addInt(){
return 0;
return 1;
return 2;
}public static int addInt(int first,int second) {
return "相加的结果是:" + first + second;
}public static int addInt(){
return 0;
System.out.println("正在进行整型加法...");
}注意:如果方法返回值类型不为void,方法中所有路径必须有return返回值
public static String compareAge(int age){
if(age<=18){
return "少年";
}else if(age<30){
return "青年";
}
}public static void sayHello(){
return 0;
}形参为int类型,实参为long类型,是否可以?反过来呢?
形参为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);形参为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、形参为小类型,实参为大类型,不能调用,即不一致,需要进行强制类型转换
循环中返回,是否可以?
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;
}方法是否可以嵌套调用其他方法?是否可调用多个?
【练习】
- 练习实例内容,熟悉方法使用中的注意点
方法调用栈【扩展】
概述
实际项目中,多个方法往往会协同合作,有层次性的被嵌套调用,以完成业务
比如,一个用户购买行为的下单方法,往往会调用支付、扣减库存、生成物流单、推送消息、增加用户积分等多个子方法
其中的支付方法,会在方法中继续调用检查余额、扣款等方法
其中的推送消息子方法,会在方法中继续调用获取用户信息、给指定网络服务商推送消息、回调等孙方法
在这些嵌套调用过程中,父方法调用子方法时,父方法会被挂起,这种机制,在JVM处理中,使用栈形式实现
栈特点
生活中的场景:
- 农村以前有那种腌肉的缸,或是北方冬天腌大白菜的,先往里面放的肉或大白菜,一般是最后吃;而后放进去的会先吃
- 家里衣柜叠的很多层衣服,一把也是一件往里面叠起来,拿的时候,从上面一件一件拿,先拿的都是后叠上去的
栈(Stack)特点:先进后出、后进先出,操作包含入栈(Push)和出栈(Pop),如下图
栈 出栈和入栈的过程,如下图
入栈和出栈过程
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中查看效果
调试运行模式下查看方法调用栈
【练习】
- 练习实例内容,熟悉方法调用特点,并掌握如何在调试中查看方法调用栈效果
方法重载
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)
- 普通方法、构造方法和静态方法都可以重载,方式一致
定义规则
- 在同一个类中
- 方法名称完全相同,包括大小写
- 形式参数列表完全不同
- 参数个数不同,推荐
- 参数类型不同,推荐
- 参数个数相同、参数类型相同,但参数顺序不同,不推荐,容易带来使用上理解困难
注意:方法重载,不以返回值类型不同作为规则
使用方式
- 使用时,调用相同方法,但由于传递的实际参数的个数、类型不同,会自动选择形式参数个数、类型匹配的方法进行调用
- 可通过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;
}
}
【练习】
提问
下面两个方法,如果在同一个类中,是重载方法吗?
public static int add(int first, int second) {
//方法体省略
}
public static int add(int one, int two) {
//方法体省略
}下面两个方法,如果在同一个类中,是重载方法吗?
public static int add(int first, int second) {
//方法体省略
}
public static String add(int first, int second) {
//方法体省略
}下面两个方法,如果在同一个类中,是重载方法吗?
public static int operate(int first, String second) {
//方法体省略
}
public static int operate(String first, int second) {
//方法体省略
}
练习实例内容,完成代码编写;掌重载方法的定义和使用
编写程序,定义一个MyOperation类,包含多个方法名为operate的重载方法,重载方法的参数与逻辑如下
- 进行两个整数相加,并返回
- 进行两个小数相加,并返回
- 进行三个小数相加,并返回
- 进行两个字符串连接,并返回
- 进行一个字符串数组中所有元素连接,使用"."连接,并返回;如形式参数为{"尼古拉斯","海棠","赵四"},则返回:尼古拉斯.海棠.赵四
最后,定义一个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;
}
}
}
【练习】
- 练习实例内容,完成代码编写;熟悉递归方法的定义与使用
实战和作业
- 编写程序,模拟用户登录,接收用户多次输入用户名和密码进行验证
- 定义check方法,检查用户名和密码,输入参数为用户名和密码,返回用户名和密码是否正确;如果形式参数值用户名为jack,密码为123,返回验证成功;否则,返回验证失败
- 定义login方法,能多次接收用户输入用户名、密码,并调用check方法检查用户名和密码是否正确,如果正确,提示登录成功,结束循环;如果不正确,提示用户名和密码不正确,再次输入
- 测试上述逻辑
- 编写程序,定义三个重载方法max,具体逻辑如下
- 接收两个整数,返回较大整数
- 接收三个整数,返回较大整数,尝试利用上面那个方法完成
- 接收一个字符串数组,返回长度最长的那个字符串,如果有多个,返回其中任一个即可
- 【扩展】编写程序,完成输入一个整数,返回这个整数的阶乘值,使用递归实现
- 【扩展,读懂就好,工具和代码中有示例代码】编写程序,完成输出指定个数斐波那契数列;如输出指定个数为15,那输出的斐波那契数列1 1 2 3 5 8 13 21 34 55 ...,结合使用for循环、函数定义、函数递归调用