03. 方法
学习目标
- 掌握方法的定义与使用
- 掌握方法的重载
方法概述
引入
引例,现在有三个需求:
- 编写程序,计算10+20,并输出结果
- 编写程序,计算111+222,并输出结果
- 编写程序,计算333+444,并输出结果
请问这个程序有哪些缺点呢?
很明显:
这个程序中有大量的代码是重复的,是可以被复用的,是可以优化的。
为了解决这个需求,我们就需要这种语法机制,这意味着方法(method)至少应该解决我们以下三个痛点:
- 能够重复实现某种特定功能,解决同一类问题,而不需要重复写代码。
- 每次实现这个功能时,可以传入不同的数据。
- 传入数据实现功能后,能够得到不同的结果。
核心需求就是,能复用代码,有输入和输出。
这实际上就是方法的作用。与此同时,我们还把使用去实现某种特定功能的过程叫做方法的调用(invoke)。
方法实际上是可以重复完成特定功能的代码块,本质是为了代码复用。
方法的基本使用
方法的定义
一般情况下,定义一个方法是以下语法结构:
[修饰符列表] 返回值类型 方法名 (形式参数列表){
// 方法体
}
在具体了解每个部分的语义之前,我们需要了解 两个非常重要的概念:
- 方法的声明(方法头): [修饰符列表] 返回值类型 方法名 (形式参数列表)
- 方法的签名: 方法名 (形式参数列表)
接下来逐一解释语法的每个部分:
- 修饰符列表:修饰符列表不是必须的,可以为空不写,现在默认为public static(具体含义面向对象讲)
- 返回值类型:方法可能会有结果,这个结果就是返回值,返回值的数据类型称之为返回值类型
- 返回值类型可以是基本数据类型,也可以是引用数据类型(例如String)
- 此时表示方法拥有返回值,必须显式的指出该返回值,否则编译报错
- 在方法体中用return关键字指示返回值,格式为return + 返回值
- return后的返回值的数据类型,要和方法声明中的返回值类型保持一致(或者兼容)
- 方法执行到return语句时,表示方法执行完毕。
- 方法完全可以没有结果,也就是没有返回值,但是方法必须要有返回值类型,用关键字void标记。使用void标记的方法,没有返回值,自然也无需指出返回值
- 返回值类型可以是基本数据类型,也可以是引用数据类型(例如String)
- 方法名:给方法起个名字,调用方法时用的,方法名的命名需要遵守规范
- 必须是合法的标识符
- 方法名最好见名知意
- 小驼峰命名法
- 形式参数列表:方法在实现功能时,有时需要传入数据。方法在定义时就应该告诉方法的调用者应该传入什么数据,这就是形式参数列表,简称形参列表
- 形参可以是基本数据类型变量,也可以是引用数据类型变量
- 形参的个数不受限,多个形参之间用逗号隔开
- 形参的作用域仅在方法内部,它们都是局部变量
- 形参中起关键决定性作用的是形参的数据类型,形参名只是一个标识符,不会影响方法调用
- 调用方法时传入的具体数据参数称之为实际参数,简称实参
- 方法调用时,实参和形参的数据类型要一一对应,并且保持一致(或者兼容)
- 方法体:方法体包含具体的语句,定义该方法的功能,由大括号包裹起来。
特别注意 :
一定要分清楚,什么是形式参数和实际参数。形参完全可以理解成一个占位符,它的作用只是告诉方法的调用者,该传入什么类型的参数。而方法的实参才是一个真正的参数。
方法的调用
明确上述语法后,定义完方法后。接下来需要调用方法,使方法生效:
在Java当中,main方法是程序的入口方法,一个方法必须直接或间接地在main方法中调用才会被执行。
对于修饰符列表中有static的方法,在同一个class的main方法中的调用方式是:方法名(实际参数列表)
注:实际上,同一个类中的static方法之间,都可以使用这种方式直接互相调用。
调用一个有返回值的方法,实际上就是操作返回值,void方法没有返回值,不能做任何操作。
但是要注意:
Demo:
public static void main(String[] args) {
// Result of 'NewDemo.sum()' is ignored
// 方法既然有返回值,那么建议去接收或者使用这个返回值
int sumValue = sum(10, 20);
System.out.println(sumValue);
int num1 = 100;
int num2 = 200;
// 操作方法调用就是操作方法的返回值
System.out.println(sum(num1, num2));
System.out.println(sum(num1, num2) + 100);
}
// 定义一个方法,来完成求两个int类型数值的和
public static int sum(int num1, int num2) {
return num1 + num2;
}
使用细节
方法完全可以没有结果,也就是没有返回值,但是方法必须要有返回值类型,用关键字void标记
实参的自动类型转换: 方法在调用时,如果填入的实参的数据类型,可以自动类型转换成形参的数据类型,那么即便数据类型不一致,语法上仍然允许该数据类型的变量作为实参。反之强制类型转换不行。方法的返回值和它的返回值类型也存在这种特点!(如果一个方法需要传入一个long类型数据,那我传入一个int值可以吗?如果传入double值可以吗?)
形参列表中起关键作用的是数据类型,它决定了调用该方法时需要传入什么数据。而
形参地名字实际上就是一个代号,仅会影响方法内部如何使用外部传入地数据,形参名叫什么其实无所谓。
main()方法详解
main方法代码
public static void main(String[] args) {
//方法体
}
解释如下:
public static表示"公开的,静态的",这两个修饰符很重要,我们等到面向对象阶段会重点学习它们。
void表示方法没有返回值。
main是方法名,代表这个方法是主方法,是程序的入口方法。
main不是关键字,但在Java中它已经成为约定俗成的程序入口方法的方法名。
不要自定义一个方法叫main!
(String[] args)是形参列表,其中String[]是一种引用类型,数组(马上讲),args是一个形参名
理论上来说,args可以在符合标识符命名的前提下可以任意写。但是不建议这么做
main方法是启动程序时给JVM调用的,是程序的入口方法
练习
小试牛刀,完成以下练习题:
- 求两个数的最大值。
- 键盘录入两个值,求最大值。
- 定义一个方法用于判断一个正整数的奇偶性,要求:
- 奇数方法返回false
- 偶数方法返回true
- 定义一个计算器(Calculator)它的功能有:
- 加,减,乘,除
- 求平方
- 求a的n次方
- ...
- 定义一个方法,求圆的面积:
- 方法参数:半径 (radius)
- 圆周率:3.14(获取用Math.PI获取)
方法重载
引入
引例,现在有三个需求,需要写三个方法完成:
- 编写程序,计算两个int类型数据之和,并输出结果
- 编写程序,计算两个double类型数据之和,并输出结果
- 编写程序,计算两个long类型数据之和,并输出结果
写完方法后,请问这个程序有哪些缺点呢?
很明显:
这三个方法实现的都是相似的功能,都是求和,只不过是针对的数据类型不同罢了。在正常情况下,我们认为方法的名字可以用来区分方法,但像引例中功能几乎一样的多个方法,能不能让它们拥有相同的方法名呢?这样既方便记忆,也方便调用。
Java是存在这种机制的,那就是Java的方法重载(overload)机制。方法重载允许一个类中,多个方法拥有相同的名字。但名字一旦相同后,多个方法之间又如何区分呢?
所以方法的重载是有条件的。
语法要求
一个类中的多个方法,可以具有相同的方法名,但是它们的形参列表必须不同。
形参列表不同意味着:
- 形参数量不同
- 形参数量相同时,形参的数据类型不同
- 形参数量和数据类型都相同时,形参的数据类型的顺序不同
除开上述条件外,其余的任何不同都无法构成方法重载, 有:
- 形参的名字不同,可以构成方法重载
- 返回值类型不同,可以构成方法重载
- 修饰符列表不同,可以构成方法重载
请明确记住,方法的重载只与方法的签名有关。即在方法名相同的情况下,方法签名不同,参数列表不同
请思考:
如果我想在很多方法中,唯一地找到一个方法,需要明确什么?
实参的自动类型转换在重载中的应用
先回答上面思考的问题:
方法的调用必须能够让编译器明确找个某个方法,当多个方法的方法名相同,形参列表还相同的话,就无从唯一确定一个方法了。所以要想唯一确定一个方法,必须明确方法的名字和形参列表,而它两合起来就称之为"方法的签名",这就是签名的由来。
一个类中发生方法重载时,方法名既然相同了,为了确保签名不同,那形参列表就必须不同了。
Demo1:
// 方法1
public static void test(int a){}
// 方法2
public static void test(double a){}
// 方法3
public static void test(float a){}
如果调用的是:
test(10);
请问调用的是方法几呢?
在方法的概述这一节中,我们讲过:实参能够自动类型转换去匹配形参的数据类型,看起来方法1、2、3都能够匹配,那么究竟该选谁呢?这就不得不提,Java设计原则中一个非常重要的原则: 就近原则
就近原则 指的是:当有多个选项都能正确匹配时,那么优先选择"最近"的。
回到上面那个案例,显然方法1最近,因为它不需要类型转换。而如果去掉方法1,那么方法3将胜出,因为它类型转换的"距离"会"近"一点。
理解就近原则,需要活学活用,以后我们还会碰到,到时候再解释。
Demo2:
// 方法1
public static void test(int a,double b){}
// 方法2
public static void test(double a,int b){}
假如调用的代码是:
test(10, 10);
请问调用的是方法1还是方法2呢?
显然不好确定,无论是1还是2都需要类型转换才能匹配,既然都转换,并且都是int------>double,那么到底谁"近"呢?
实际上这个方法的调用,是一个模糊的调用,会编译报错。这一点在开发中,多个方法组成方法重载时,要格外注意。
练习
小试牛刀:
- 使用方法重载,控制台输出各种数据类型。
- 实现一个功能更强大的计算器。