我们在学习Java编程以后接触到的第一个程序就是"Hello World”,在这当中涉及到两个主要的结构:类和main方法,当时我们只是说明了main方法是程序的入口,那么当我们想要自己定义一个方法时应该如何下手呢?
首先我们要明确方法的概念和作用,从名称上来说,方法也可以被称为函数,是用来解决同一类的问题的。从代码的结构上来说,定义方法可以减少重复的代码,也能使得整个程序结构更加清爽。
public class Test{ public static void main(String[] args){ // 定义两个变量,so easy int a = 10,b = 5; int c = a + b; System.out.println(c);// 15 } }
public class Test{ public static void main(String[] args){ int a = 10,b = 5; int c = 20,d = 10; // 可以看到,虽然变量名称不同,但是计算的逻辑是相同的 // 如果某一段代码反复出现,我们可以考虑将他提取出来变成一个方法 int e = a + b; System.out.println(e);// 15 int f = c + d; System.out.println(f);// 30 int g = e + f; System.out.println(g);// 45 } }
public class Test{ public static void main(String[] args){ int a = 10,b = 5; int c = 20,d = 10; // 原有的代码逻辑将转变为方法的调用 plus(a,b);// 执行方法时输出:15 plus(c,d);// 执行方法时输出:30 plus(e,f);// 执行方法时输出:45 } // 定义一个用于计算两个数加和的方法,计算后输出结果 public static void plus(int m,int n){ int result = m + n; System.out.println(result); } }
如果我们想定义一个方法,那就需要先了解定义方法的结构,按照次序分别为:
方法名称和参数列表构成了方法签名,方法签名可以唯一的确定一个方法,并且对鉴别是否构成重载十分有用。
public class Test{ // 方法签名:main(String[] args) public static void main(String[] args){ int a = 10,b = 5; int c = plus(a,b); } // 方法签名:plus(int m,int n) public static int plus(int m,int n){ return m + n; } }
在定义一个方法后,我们在使用编译器调用时只能够查看到方法签名及返回值类型,我们希望对于相近或重载的方法进一步进行描述,有利于使用者对方法的区分。
对方法添加注释时需要使用文档注释,称之为javadoc,这样在进行调用时就可以显示方法的相关信息,对于方法的注释主要包括以下几个部分:
在编译器中可以输入 /**
快速生成一个方法的模板,效果如下:
public class Test{ /** * 计算两个数的加和 * @param a 第一个加数 * @param b 第二个加数 * @return 两个数的加和 */ public int plus(int a,int b){ return a + b; } }
明确了方法的定义结构之后,我们需要做的就是希望在解决实际问题时知道如何去定义一个方法,并且有一个清晰的思路。
笔者认为一个方法的设计其实更像是整个编程思想的缩影,无论是完成一个复杂的功能还是某一个方法的定义都可以按照下面三个步骤来进行:
要定义一个方法,就要先明确:我需要完成怎样一个功能,用于解决一个什么样的问题?明确了之后我们就可以知道这个方法的用途,进而确定方法的名称、返回值类型、调用访问的权限、是否有其他修饰符。
接下来要我们要根据方法的用途,考虑这个方法执行时都需要什么,是否需要传入一些参数?于是我们可以确定参数列表的部分了。
在明确了方法要解决的问题以及所需要的参数之后,我们就可以分析方法中用该编写什么样的代码来解决问题,也就是最后确定方法体的部分,用上传递进来的参数,最后返回应该返回的变量或进行打印输出。
方法名称的定义比较容易,因为自定义的程度较高,没有什么强制性的规则,只要满足标识符的规定就可以了。一般来说,方法的命名也需要做到见名知意,以小写字母开头,如果遇到多个单词首字母大写,可以是字母和数字的组合。
参数列表的确定主要就是考虑调用方法时需要传入的参数的类型,可以为空,也可以为一个至多个,分别需要声明类型和名称。
除此之外,我们还需要了解一下各种参数类型之间的差别:
public class Test{ public static void main(String[] args){ int a = 10; test(a);// 进行方法的调用,方法中对值进行了修改 System.out.println(a);// 结果为10 } public static void test(int n){ System.out.println(n);// 接收到值,结果为10 n = 100;// 修改n的值,不会影响传入的参数a的值 System.out.println(n);// 结果为100 } }
public class Test{ public static void main(String[] args){ int[] a = {1,2,3}; test(a);// 进行方法的调用,方法中对数组a进行了修改 for(int i = 0;i < a.length;i++){ System.out.println(n);// 结果为10,20,30 } } public static void test(int[] n){ for(int i = 0;i < n.length;i++){ System.out.println(n[i]);// 接收数组的引用,结果为:1,2,3 } for(int i = 0;i < n.length;i++){ n[i] = n[i] * 10;// 修改数组的值,每个元素变为原来的10倍 } // 对于修改对象的属性值同理,直接作用在对象本身 } }
public class Test{ public static void main(String[] args){ int a = 1; int b = 2; int c = 3; test(null);// 调用成功,此时参数为null test();// 调用成功,此时参数个数为0 test(a);// 调用成功,传入1个参数 test(a,b);// 调用成功,传入2个参数 test(new int[]{a,b,c});// 调用成功,也可构建成数组后传入 } public static void test(int... nums){ // 将nums当成数组一样使用即可,可以通过判断数组长度确定传入参数的个数 // 前提是传入的参数不为null,否则会出现空指针异常 if(nums == null){ System.out.println("传入的参数为null"); }else{ System.out.println("传入的参数个数为:" + nums.length); } } }
如何确定一个方法是否需要有返回值呢?在上述的方法中,在返回值类型的部分我们使用的都是void关键字,代表此方法返回值为空,或无需返回。其实,对于一个方法是否需要返回这不是一个语法问题,而是取决于我们使用者的需要,我们来讨论一下这两种情况。
public class Test{ public static void main(String[] args){ // 需要实现如下逻辑:计算两个数的加和,并将得到的结果变为10倍后输出 int a = 1,b = 2; // 在进行方法调用后,我们必须想办法先得到两个数计算加和的结果,再继续下一步 int c = plus(a,b); // 使用对应类型的变量(c)接收返回结果,然后继续下一步操作 int result = c * 10; System.out.println(result); } public static int plus(int a,int b){ return a + b; } }
能够根据需要熟练并快速的写出方法体中的内容这是一个长期训练和锻炼的过程,有的时候我们并不是不知道如何使用方法这种结构,而是给出的问题根本没有任何的思路。在这里笔者将给大家一些建议,因为举再多的例子也无法在短时间内对大家有实质性的帮助。
其实程序本身只是我们一种逻辑思维表达,而且计算机真的很笨,所有的步骤都需要你一步一步去告诉他,比如你想写一个判断素数的程序,不要指望你定义一个变量i,然后使用选择结构在判断条件中写上:if(i == 素数){}计算机就能明白,你首先要让计算机明白什么是素数,或者符合什么样的条件的数是素数。基本上所有的问题都可以转换为一个数学问题,或者是具有步骤的逻辑问题,特别是我们要让计算机帮助我们去完成一项操作或功能的时候,你必须告诉它明确的步骤,以及遇到各种情况要如何处理,毕竟大佬是这么说的:
很多同学看到这句话的第一反应可能是: 我信你个鬼!你个xx头子坏得很! 但是仔细想想其实很有道理,特别是对于初学者,我们在学习编程时一定要尝试去理解计算机是如何工作的,如何教会它来帮助我们解决问题。
那么笔者的建议可以概括为以下几点:
当一个方法被定义以后,只有被调用了才会被执行,否则也是没有意义的。
根据上面的例子,我想对于方法的调用方式大家已经掌握了。没错,很简单:方法名称 + 传入参数。有关于参数的写法上需要作出一点说明,在进行方法定义时,我们需要声明参数的类型,而在调用方法,传入参数时,我们需要做的仅仅是匹配,不要再次声明参数的类型,而只需要保证传入的参数与定义的类型相匹配就好,可以传入一个具体的值,也可以是声明赋值后的变量,还是那句话:类型匹配就好。
方法的执行过程其实比较简单,具体的包含嵌套调用的结构我们将在后面的文章中说明。方法的执行过程其实用到的了一个最基本的结构: 顺序结构 。如果一段代码在执行的过程中遇到了方法调用,那么一定会进入到方法中,将方法中的代码全部执行完毕,再返回到方法的调用处,继续执行后面的代码。
那么这里也给大家解释一下初学者的问题:你说方法中定义的return是返回的意思,那到底返回到哪去了?什么时候返回的?
解释这个问题可以用一句话概括: 返回到了调用该方法的位置 。首先,只有一个方法被调用以后,才会执行其中的代码,才会轮到return语句的执行,那么return之后去哪了呢?自然是返回到调用这个方法的位置继续执行,这个时候,整个方法的调用语句就代表了这个方法的返回值,我们直接使用对应类型的变量接收就可以了。
public class Test{ public static void main(String[] args){ // 需要实现如下逻辑:计算两个数的加和,并将得到的结果变为10倍后输出 int a = 1,b = 2;// 代码执行步骤:1 // 代码执行步骤:2,进行方法的调用 int c = plus(a,b);// 代码执行步骤:4,进行返回值的赋值 int result = c * 10;// 代码执行步骤:5 System.out.println(result);// 代码执行步骤:6 } public static int plus(int a,int b){ return a + b;// 代码执行步骤:3 } }
static修饰符有很多作用,我们这里只讨论它用在方法上时,对方法的调用产生怎样的影响。由于main方法是程序的入口,那么它必须使用static声明,即:不需要实例化对象即可直接执行。那么由于main方法是static修饰的,那么它直接调用的方法必须也是由静态(static)修饰的。
具有返回值的方法在调用后,是不是一定要对返回值进行接收呢?当然不是必须的,如果不接收,方法的值也会正常返回,只不过随即被丢弃了而已。接收时将方法调用语句看成一个整体,直接用对应类型的变量赋值接收即可。
<span id="jump"></span>
重载指的是在一个类中,可以定义多个同名的方法,区别在于参数列表不同。对于重载的概念还是很好理解的,无非是描述了一种现象,在一个类中存在了很多名字相同的方法,大家需要掌握的就是如何定义才符合重载的规则,以及重载有什么用?
不要看这个概念简单,还是有很多同学在此翻车。方法名称相同很好理解,完全一致的才叫做相同,这里对大小写敏感。另外一个概念是:参数列表不同,大家一定要注意,参数列表相同与否,是靠参数类型以及排列顺序来决定的,与参数名称无关。因为参数列表中声明的参数名称只是传入参数的一个代表,并不具备什么具体的区分意义。
public class Test{ // 求两个整数和的方法:plus public int plus(int a,int b){ return a + b; } // 参数列表相同,不构成重载,不能在类中同时存在 public int plus(int c,int d){ return c + d; } // 参数列表不同,构成重载 public double plus(double a,double b){ return a + b; } // 参数列表不同,构成重载,但是不定参数容易构成调用的歧义,不推荐 public int plus(int... a){ return 0; } // 参数列表相同,方法名称不同,不构成重载,可以在类中同时存在 public int Plus(int a,int b){ return a + b; } }
在很多时候,我们使用方法完成一个功能或逻辑,存在很多种情况,有些情况来自于代码逻辑处理的过程中,也有些情况是要对不同的参数类型做出不同的操作。这个时候我们就可以利用重载的特点,用相同的方法名代表我们要处理的逻辑是类似的,然后在参数列表中声明不同的参数类型,这样就可以避免我们在方法中再繁杂的写各种参数个数的判断,参数类型的判断,更加利于维护。同时,使用相同的方法类型,也使得使用者在调用时变得十分方便,不需要在同一功能上记忆各种不同的方法名称,同时又能很好的解决问题。
对于重载方法的调用,由于方法名称相同,jvm主要就是根据传入的参数类型来进行区分,效果如下:
public class Test{ public static void main(String[] args){ int a = 1,b = 2; int c = plus(a,b);// 调用plus(int a,int b) double m = 1.0,n = 2.0; double d = plus(m,n);// 调用plus(double a,double b) } // 求两个整数和的方法:plus public static int plus(int a,int b){ return a + b; } // 方法名相同,参数列表不同,构成重载 public static double plus(double a,double b){ return a + b; } }
从上面的例子我们可以看到,在执行方法调用时主要是通过参数类型来进行区分的。但是当方法中出现不定参数时要尤为注意:
public class Test{ public static void main(String[] args){ int a = 1,b = 2,c = 3; int d = plus(a);// 编译失败,与plus(int... a)和plus(int a,int... b)都匹配 int e = plus(a,b);// 编译成功,调用plus(int a,int b) int f = plus(a,b,c);// 编译失败,与plus(int... a)和plus(int a,int... b)都匹配 int g = plus(new int[]{a,b});// 编译成功,调用plus(int... a) int h = plus(a,new int[]{b,c});// 编译成功,调用plus(int a,int... b) } // 求两个整数和的方法:plus public static int plus(int a,int b){ return a + b; } // 方法名称相同,参数列表不同,构成重载,但是不定参数容易构成调用的歧义,不推荐 public int plus(int... a){ return 0; } // 方法名称相同,参数列表不同,构成重载,但是不定参数容易构成调用的歧义,不推荐 public int plus(int a,int... b){ return 0; } }
从以上的例子我们可以看到,如果重载方法中出现了不定参数,那么在调用时很可能出现歧义,依然要通过手动构建数组的方式来解决,所以在进行方法重载时应该尽量避免出现不定参数, 当不定参数是Object类型时,歧义问题就会更加严重 。