跳至主要內容

03. 方法

景天大约 11 分钟

学习目标

  • 掌握方法的定义与使用
  • 掌握方法的重载

方法概述

引入

引例,现在有三个需求:

  1. 编写程序,计算10+20,并输出结果
  2. 编写程序,计算111+222,并输出结果
  3. 编写程序,计算333+444,并输出结果

请问这个程序有哪些缺点呢?

很明显:

这个程序中有大量的代码是重复的,是可以被复用的,是可以优化的。

为了解决这个需求,我们就需要这种语法机制,这意味着方法(method)至少应该解决我们以下三个痛点:

  1. 能够重复实现某种特定功能,解决同一类问题,而不需要重复写代码。
  2. 每次实现这个功能时,可以传入不同的数据。
  3. 传入数据实现功能后,能够得到不同的结果。

核心需求就是,能复用代码,有输入和输出。

这实际上就是方法的作用。与此同时,我们还把使用去实现某种特定功能的过程叫做方法的调用(invoke)。

方法实际上是可以重复完成特定功能的代码块,本质是为了代码复用。

方法的基本使用

方法的定义

一般情况下,定义一个方法是以下语法结构:

[修饰符列表] 返回值类型 方法名 (形式参数列表){
	// 方法体
}

在具体了解每个部分的语义之前,我们需要了解 两个非常重要的概念:

  1. 方法的声明(方法头): [修饰符列表] 返回值类型 方法名 (形式参数列表)
  2. 方法的签名: 方法名 (形式参数列表)

接下来逐一解释语法的每个部分:

  1. 修饰符列表:修饰符列表不是必须的,可以为空不写,现在默认为public static(具体含义面向对象讲)
  2. 返回值类型:方法可能会有结果,这个结果就是返回值,返回值的数据类型称之为返回值类型
    1. 返回值类型可以是基本数据类型,也可以是引用数据类型(例如String)
      • 此时表示方法拥有返回值,必须显式的指出该返回值,否则编译报错
      • 在方法体中用return关键字指示返回值,格式为return + 返回值
      • return后的返回值的数据类型,要和方法声明中的返回值类型保持一致(或者兼容)
      • 方法执行到return语句时,表示方法执行完毕。
    2. 方法完全可以没有结果,也就是没有返回值,但是方法必须要有返回值类型,用关键字void标记。使用void标记的方法,没有返回值,自然也无需指出返回值
  3. 方法名:给方法起个名字,调用方法时用的,方法名的命名需要遵守规范
    • 必须是合法的标识符
    • 方法名最好见名知意
    • 小驼峰命名法
  4. 形式参数列表:方法在实现功能时,有时需要传入数据。方法在定义时就应该告诉方法的调用者应该传入什么数据,这就是形式参数列表,简称形参列表
    • 形参可以是基本数据类型变量,也可以是引用数据类型变量
    • 形参的个数不受限,多个形参之间用逗号隔开
    • 形参的作用域仅在方法内部,它们都是局部变量
    • 形参中起关键决定性作用的是形参的数据类型,形参名只是一个标识符,不会影响方法调用
    • 调用方法时传入的具体数据参数称之为实际参数,简称实参
    • 方法调用时,实参和形参的数据类型要一一对应,并且保持一致(或者兼容)
  5. 方法体:方法体包含具体的语句,定义该方法的功能,由大括号包裹起来。

特别注意 :

一定要分清楚,什么是形式参数和实际参数。形参完全可以理解成一个占位符,它的作用只是告诉方法的调用者,该传入什么类型的参数。而方法的实参才是一个真正的参数。

方法的调用

明确上述语法后,定义完方法后。接下来需要调用方法,使方法生效:

  1. 在Java当中,main方法是程序的入口方法,一个方法必须直接或间接地在main方法中调用才会被执行。

  2. 对于修饰符列表中有static的方法,在同一个class的main方法中的调用方式是:方法名(实际参数列表)

    注:实际上,同一个类中的static方法之间,都可以使用这种方式直接互相调用。

  3. 调用一个有返回值的方法,实际上就是操作返回值,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;
}

使用细节

  1. 方法完全可以没有结果,也就是没有返回值,但是方法必须要有返回值类型,用关键字void标记

  2. 实参的自动类型转换: 方法在调用时,如果填入的实参的数据类型,可以自动类型转换成形参的数据类型,那么即便数据类型不一致,语法上仍然允许该数据类型的变量作为实参。反之强制类型转换不行。方法的返回值和它的返回值类型也存在这种特点!(如果一个方法需要传入一个long类型数据,那我传入一个int值可以吗?如果传入double值可以吗?)

  3. 形参列表中起关键作用的是数据类型,它决定了调用该方法时需要传入什么数据。而

    形参地名字实际上就是一个代号,仅会影响方法内部如何使用外部传入地数据,形参名叫什么其实无所谓。

main()方法详解

main方法代码

public static void main(String[] args) {
	//方法体	
}

解释如下:

  1. public static表示"公开的,静态的",这两个修饰符很重要,我们等到面向对象阶段会重点学习它们。

  2. void表示方法没有返回值。

  3. main是方法名,代表这个方法是主方法,是程序的入口方法。

    main不是关键字,但在Java中它已经成为约定俗成的程序入口方法的方法名。

    不要自定义一个方法叫main!

  4. (String[] args)是形参列表,其中String[]是一种引用类型,数组(马上讲),args是一个形参名

    理论上来说,args可以在符合标识符命名的前提下可以任意写。但是不建议这么做

  5. main方法是启动程序时给JVM调用的,是程序的入口方法

练习

小试牛刀,完成以下练习题:

  1. 求两个数的最大值。
  2. 键盘录入两个值,求最大值。
  3. 定义一个方法用于判断一个正整数的奇偶性,要求:
    1. 奇数方法返回false
    2. 偶数方法返回true
  4. 定义一个计算器(Calculator)它的功能有:
    1. 加,减,乘,除
    2. 求平方
    3. 求a的n次方
    4. ...
  5. 定义一个方法,求圆的面积:
    1. 方法参数:半径 (radius)
    2. 圆周率:3.14(获取用Math.PI获取)

方法重载

引入

引例,现在有三个需求,需要写三个方法完成:

  1. 编写程序,计算两个int类型数据之和,并输出结果
  2. 编写程序,计算两个double类型数据之和,并输出结果
  3. 编写程序,计算两个long类型数据之和,并输出结果

写完方法后,请问这个程序有哪些缺点呢?


很明显:

这三个方法实现的都是相似的功能,都是求和,只不过是针对的数据类型不同罢了。在正常情况下,我们认为方法的名字可以用来区分方法,但像引例中功能几乎一样的多个方法,能不能让它们拥有相同的方法名呢?这样既方便记忆,也方便调用。

Java是存在这种机制的,那就是Java的方法重载(overload)机制。方法重载允许一个类中,多个方法拥有相同的名字。但名字一旦相同后,多个方法之间又如何区分呢?

所以方法的重载是有条件的。

语法要求

一个类中的多个方法,可以具有相同的方法名,但是它们的形参列表必须不同。

形参列表不同意味着:

  1. 形参数量不同
  2. 形参数量相同时,形参的数据类型不同
  3. 形参数量和数据类型都相同时,形参的数据类型的顺序不同

除开上述条件外,其余的任何不同都无法构成方法重载, 有:

  1. 形参的名字不同,可以构成方法重载
  2. 返回值类型不同,可以构成方法重载
  3. 修饰符列表不同,可以构成方法重载

请明确记住,方法的重载只与方法的签名有关。即在方法名相同的情况下,方法签名不同,参数列表不同

请思考:

如果我想在很多方法中,唯一地找到一个方法,需要明确什么?

实参的自动类型转换在重载中的应用

先回答上面思考的问题:

方法的调用必须能够让编译器明确找个某个方法,当多个方法的方法名相同,形参列表还相同的话,就无从唯一确定一个方法了。所以要想唯一确定一个方法,必须明确方法的名字和形参列表,而它两合起来就称之为"方法的签名",这就是签名的由来。

一个类中发生方法重载时,方法名既然相同了,为了确保签名不同,那形参列表就必须不同了。

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,那么到底谁"近"呢?

实际上这个方法的调用,是一个模糊的调用,会编译报错。这一点在开发中,多个方法组成方法重载时,要格外注意。

练习

小试牛刀:

  1. 使用方法重载,控制台输出各种数据类型。
  2. 实现一个功能更强大的计算器。