本帖最后由 ufof 于 2015-12-26 18:21 编辑



4.1 认识方法



4.1.1 方法概述

方法是程序当中必不可少的组成部分。我们之前在讲hello, world的时候就已经粗略的讲过了主方法。
简单的来说,方法是一个用于封装代码的区。我们之前写程序的时候都是把所有代码放在主方法里面,这也做可行,但是有两个问题:
  • 代码复用性极差
  • 程序分类不明确

为什么会复用性差?如果一段代码我需要在不同的地方执行若干次,需要把整段代码都得赋值粘贴一遍。这样做十分麻烦。

分类不明确的原因:一个程序当中,不同的代码负责不同的事情。有一些代码干这件事;有一些代码干另外一件事。那么我怎么知道这段代码是干什么的?虽然可以加上注释,但是分类明确无法提现出来。

有了方法,这两个问题可以得到解决:

复用性的提高:如果一段代码我要用很多次,我就把它封装在一个方法里面。我要用的时候调用这个方法就可以了。不需要再把整段代码复制粘贴。

分类明确:一段代码如果是干这件事的,我把他封装在方法中;干另一件事的,我把他封装在另一个方法中。这样一来,程序的分类性就慢慢地体现出来了。

综上所述,可见方法的重要性。下一章我们学习如何定义方法。

学生提问:为什么有一些人把方法说成函数?两者有区别吗?

答:两者有区别。简单的来说,定义在类中的叫做方法;独立定义的叫做函数。Java是面向对象的编程语言,所有东西都定义在类当中,所以叫做方法。像C这类的面向过程语言,没有类这个概念,所以叫做函数。


本章小结:
  • 把所有代码放在主方法中是有问题的
  • 方法用于封装代码,主方法可以调用其他方法



4.2  方法的定义和调用方法



4.2.1  方法的定义

方法的定义语法如下:

  1. 若干个修饰符 返回值类型 方法名(参数类型1 参数名称1,参数类型n 参数名称n){
  2.     //方法中的代码
  3. }
复制代码
注1:修饰符是可选的
注2:参数数量完全可变,也可以没有
注3:方法名建议使用小驼峰命名法

就拿我们之间接触过的主方法来讲:
  1. public static void main(String[] args){}
复制代码

  • public是权限修饰符
  • static是静态修饰符
  • void是返回值类型,表示没有返回值
  • main是方法名
  • String[]是字符串数组,是主方法的参数

然而,我们这节当中先固定的把方法声明为“static void”。static具体会在面向对象(上)去讲;void返回值(也就是下一节)的时候会说明。static void前面还可以加上一个public,不过加不加都可以。
那么,我们先来定义一下我们自己的方法吧!

  1. class MethodDemo{
  2.     public static void main(String[] args){
  3.         
  4.     }
  5.    
  6.     static void printString(){                            //定义方法
  7.         System.out.println("hello, method");
  8.     }
  9. }
复制代码
大家先不用管主方法。我们就先看printString()方法。
这个方法很简单,就是一个无返回值无参数的小方法。但是现在如果我们运行这个程序的话,什么都不会发生。因为主方法里什么都没有。我们想要让主方法调用printString方法,怎么做呢?请看下一小节:


4.2.2 方法的调用

方法的调用语法如下:
  1. 方法名(方法要求的参数);
复制代码

注:即使方法没有参数要求,括号也得要写。
注2:如果被调用的方法和调用者在不同的类当中,需要实例化对象,这个我们先不管。

接着用我们上面的例子:

  1. class MethodDemo{
  2.     public static void main(String[] args){
  3.         printString(); //调用方法
  4.     }
  5.    
  6.     static void printString(){
  7.         System.out.println("hello, method");
  8.     }
  9. }
复制代码
结果:



在这个程序的第三行,主方法调用了printString()方法。现在再编译和运行一次,hello, method字符串将会被打印。

本章小结:
  • 方法的定义语法是“修饰符 返回值 方法名(参数列表) {  }”
  • 方法先暂时声明为static void
  • 方法的调用语法是“方法名(需求的参数)”




4.3 方法的参数以及返回值



4.3.1 参数以及返回值概述

如果方法中要使用到的量是未知的,是要被上一级调用者定义的,可以使用参数。例如,我写一个方法,这个方法可以计算加法。加哪两个数我这个方法知道吗?这个时候,可以在方法上定义两个参数。当上一级调用者调用我这个方法的时候,必须给我传进来两个数。这样就可以由上一级调用者指定未知的量。

返回值相当于整个方法的结果。再用一下刚才加法的例子,两个数是参数,那么两个数的和即是这个方法的返回值。有返回值的方法可以被上一级调用者用作一个量,这个量就是方法的返回值。

4.3.2 如何定义有参数的方法

参数是在定义方法时定义的。例如:
  1. class MethodDemo{
  2.     public static void main(String[] args){
  3.    
  4.     }
  5.    
  6.     static void printNumber(int num){
  7.         System.out.println("我的上级调用者输入的数字是:"+num);
  8.     }
  9. }
复制代码

这个方法需求的参数是一个int类型。在方法体当中会使用到这个int。

我们在调用这个方法时也得要传入一个int:

  1. class MethodDemo{
  2.     public static void main(String[] args){
  3.         printNumber(5271);        //给printNumber()方法传入int类型:5271
  4.     }
  5.    
  6.     static void printNumber(int num){
  7.         System.out.println("我的上级调用者输入的数字是:"+num);
  8.     }
  9. }
复制代码
结果:



一个方法也可以要求多个参数。通过逗号隔开。例如:

  1. class MethodDemo{
  2.     public static void main(String[] args){
  3.         printNumber(5271,3.14);        //给printNumber()方法传入int类型:5271,double类型3.14
  4.     }
  5.    
  6.     static void printNumber(int num, double num2){
  7.         System.out.println("我的上级调用者输入的第一个数字是:"+num);
  8.         System.out.println("我的上级调用者输入的第二个数字是:"+num2);
  9.     }
  10. }
复制代码
结果:



在方法上定义的参数叫做“形式参数”,简称为形参;实际传入的数叫做“实际参数”,简称为“实参”。在上一个程序当中,printNumber()方法的num和num2就是形参;main()传入的5271和3.14叫做实参。

现在,我们学习了参数的使用之后,来写这样的一个方法。这个方法可以接收两个数字,并打印出这两个数字的和。

  1. class MethodDemo {
  2.     public static void main(String[] agrs) {
  3.         add(10,4);                                   //调用add()方法。传入10和4作为实参
  4.     }
  5.     static void add(int num, int num2) {   //add()方法,需求两个int
  6.          System.out.println(num+num2);   //将两个数字的和打印   
  7.     }
  8. }
复制代码
结果:




4.3.3 定义有返回值的方法

返回值是一个方法的最终值。当方法返回一个值时,该方法结束。上一级调用者可以使用有返回值的方法作为一个值来使用。如果一个方法不需要返回值,将返回值定义为void。

如果方法要返回一个值,语法如下:

  1. return 值;
复制代码

当然,如果是void的方法,可以通过return结束方法。但是没有任何值被返回。

  1. return;
复制代码

例如,我们有一个方法,这个方法可以给你返回一个0。

  1. class MethodDemo {
  2.     public static void main(String[] agrs) {
  3.         System.out.println(myMethod());    //由于myMethod()有返回值,可以把它作为一个值来使用。
  4.     }
  5.    
  6.     static int myMethod() {                //返回一个int类型的方法
  7.         return 0;                        //返回0
  8.     }
  9. }
复制代码
结果:



在这个程序当中,myMethod()方法被定义为返回int类型的方法。方法体中将0返回。因此,main()中可以将myMethod()作为一个值来使用,就可以直接将其打印输出。

定义方法,参数是两个int,返回他们的和:

  1. class MethodDemo{
  2.     static int add(int num, int num2){
  3.         return num+num2;
  4.     }
  5.     public static void main(String[] args){
  6.         int sum = add(5,1);
  7.         System.out.println(sum);
  8.     }
  9. }
复制代码
结果:



在这个方法当中,返回值类型为int。return是表示返回的关键字,后面紧跟的就是要返回的值。
有返回值的方法可以被上一级调用者用作一个值。在主方法当中,add(5,1)返回的值赋值给了sum这个变量,然后打印输出。

一个方法可以有多个返回值,但是一般使用判断语句区分开,例如定义减法方法:

  1. class MethodDemo{
  2.     static int subtract(int num, int num2){
  3.         if(num>num2){                //如果num>num2
  4.             return num-num2;        //返回num-num2
  5.         }
  6.         else{
  7.             return num2-num;        //不然,返回num2-num
  8.         }
  9.     }
  10.     public static void main(String[] args){
  11.         System.out.println(subtract(4,7));
  12.     }
  13. }
复制代码
结果:



4.3.4 参数可变方法

参数可变方法是Java 1.5中新加入的一个功能之一。如果一个方法的某个参数的数量不确定,可以使用参数可变方法。

语法如下:

  1. 若干个修饰符 返回值类型 方法名(参数类型... 参数名){
  2.     //代码
  3. }
复制代码

可见,如果一个参数的数量不确定,在参数列表中的参数类型后面加上“...”即可。如果一个参数类型是这样的,其会变成一个数组类型。由于数组类型我们没有学到,请参数第六章。

本章小结:
  • 被上一级调用者指定的量叫做参数
  • 方法的最终值叫做返回值,可以被上一级调用者用作一个量
  • 方法的参数和返回值在整个方法定义时被定义
  • 如果没有返回值声明为void
  • return用于返回其后面的值
  • 当方法返回了一个值之后,方法结束
  • Java 1.5新功能之一是可变数量参数,在参数类型后面加上“...”即可
  • 当一个参数类型是可变的,其变成一个数组类型



4.4 方法的重载



4.4.1  重载概述

Java允许出现多个方法方法名一样但是参数不同的情况,这种情况被称为方法重载。这样大大方便了程序的编写。比如说定义一个加法方法,两个int可以相加、两个double可以相加、两个long也可以相加.....因为重载的存在,我们不需要定义不同名称的方法了。

虚拟机负责判断使用哪个方法。

满足下列情况之一,构成重载

  • 参数数量不同
  • 参数顺序不同(不同类型)
  • 参数类型不同


4.4.2  三种重载形式

一、参数数量不同:

  1. static int add(int x, int y){
  2.     return x+y;
  3. }
  4. static int add(int x, int y, int z){
  5.     return x+y+z;
  6. }
复制代码

二、参数顺序不同:

  1. static double add(int x, double y){
  2.     return x+y;
  3. }
  4. static double add(double y, int x){
  5.     return x+y;
  6. }
复制代码

注意:两者类型也得要不同。不然无法构成重载

三、
参数类型不同
  1. static int add(int x, int y){
  2.     return x+y;
  3. }
  4. static double add(double x, double y){
  5.     return x+y;
  6. }
复制代码

4.4.3 测试

我们先定义两个方法。其中一个方法需要两个int参数;另一个需要两个double参数。打印两个数的乘积。

  1. class MethodDemo {
  2.     public static void main(String[] agrs) {
  3.         getProduct(2,5);                                //使用int, int
  4.         getProduct(0.5,0.5);                            //使用double, double
  5.     }

  6.     static void getProduct(int num, int num2) {            //重载1:int, int
  7.         System.out.println(num * num2);
  8.     }

  9.     static void getProduct(double num, double num2) {    //重载2:double, double
  10.         System.out.println(num * num2);
  11.     }
  12. }
复制代码
结果:



在这个程序当中,getProduct()方法有两个重载形式:int, int 和 double, double。因此,调用时可以选择使用哪个重载形式。


本章小结
  • 方法重载是指多个同名称的方法拥有不同的参数的情况
  • 由虚拟机判断选用哪个方法
  • 方法重载在三个情况下发生:数量、顺序、类型



4.5 方法的递归



4.5.1 递归概述

递归是一种算法。指在方法中调用自己。这样的算法可以逐步逼近结果。但是要保证两点:

  • 的确是可以趋近结果
  • 必须是要有条件的递归,不然是死循环

递归的缺点在于如果递归次数过多,有栈内存溢出的风险。

4.5.2 阶乘例子

先简单介绍一下阶乘:

n!=n*(n-1)*(n-2)*(n-3).....*3*2*1

例如7!=7*6*5*4*3*2*1=5040

我们现在通过Java实现:

  1. static long getFactorial(long num){
  2.     long sum = 0;
  3.     if(num==0){
  4.         return 1;
  5.     }
  6.     else{
  7.         sum = num*getFactorial(num-1);
  8.         return sum;
  9.     }
  10. }
复制代码

定义getFactorial方法,返回值和参数都是long类型。
在第七行中完成了递归。getFactorial(num-1)相当于重新调用了方法本身,传进去的参数是num-1。一直重复调用本身直到num==0为止。这个算法就实现了:

  1. class RecursionDemo{
  2.     public static void main(String[] args){
  3.         System.out.println(getFactorial(5));
  4.     }
  5.    
  6.     static long getFactorial(long num){
  7.         long sum = 0;
  8.         if(num==0){
  9.             return 1;
  10.         }
  11.         else{
  12.             sum = num*getFactorial(num-1);
  13.             return sum;
  14.         }
  15.     }
  16. }
复制代码
结果:



本章小结:
  • 递归是指方法调用本身
  • 递归必须要有一个条件控制,不然会是死循环
  • 递归的缺点是如果递归次数过多有栈内存溢出风险



4.6 栈内存



4.6.1  栈内存概述

这一章我们不讲Java,我们就了解一下在程序运行时具体发生了什么。
Java把栈内存去分成栈内存和堆内存。虽然这两个词只有一字之差,但是区别还是蛮大的。

当一个方法被调用时,其就会被分配一个栈区。方法中的变量/常量在方法中的栈区内存放。
栈内存中的栈区都是有序排放的,遵循后进先出的原则。



当声明一个变量/常量时,其在其方法的栈区中被存放:

  1. public static void main(String[] args){
  2.     int num = 5;
  3.     double d = 3.1;
  4.     aMethod()
  5. }

  6. void aMethod(){
  7.     int num = 3;
  8.     double d = 4.1;
  9. }
复制代码



我想现在大家可以明白为什么递归过多会栈内存溢出了。每一次递归都会分配一个栈区,所以会越来越卡。
这个概念就先讲到这里。

本章小结:
  • Java把内存规划成栈内存和堆内存
  • 当一个方法被调用时,栈内存会给方法分配一个栈区
  • 变量/常量都在其方法的栈区中存放
  • 栈内存是有序的


[groupid=546]Command Block Logic[/groupid]