Minecraft(我的世界)中文论坛

标题: 【万年坑】【该章完结】Java高手训练营第四章:方法

作者: ufof    时间: 2015-9-4 12:39
标题: 【万年坑】【该章完结】Java高手训练营第四章:方法
本帖最后由 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){}
复制代码


然而,我们这节当中先固定的把方法声明为“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字符串将会被打印。

本章小结:



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. }
复制代码

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

本章小结:


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. }
复制代码



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

本章小结:

[groupid=546]Command Block Logic[/groupid]
作者: simon3000    时间: 2015-9-5 12:10
感觉把目录最底下放一个会跟好?

作者: ufof    时间: 2015-9-5 12:16
simon3000 发表于 2015-9-4 20:10
感觉把目录最底下放一个会跟好?

貌似不行。。
作者: 小尐蓝猫    时间: 2015-9-5 13:12
这个坑。。。
作者: yuxuanchiadm    时间: 2015-9-12 23:03
讨论某些东西的叫法总会有一大堆异议是不争的事实。在Java当中有实例方法和静态函数的说法。也就是一般把属于实例的叫做方法,把属于类的叫做函数。而且方法的本质也是第一个参数被隐藏为调用这个方法的对象,也就是this。说到底实例(instance)本身也可以叫做对象(object)。并且大多数时候也是把方法(method)和函数(function)混着叫。只有实际需要区分时才会有区分的意义。简而言之就是方法和函数一般情况下就是一个东西。
作者: ufof    时间: 2015-9-13 20:04
yuxuanchiadm 发表于 2015-9-12 07:03
讨论某些东西的叫法总会有一大堆异议是不争的事实。在Java当中有实例方法和静态函数的说法。也就是一般把属 ...

如果说属于类的叫做函数,那我可以说只要是static的就叫函数了吗?
作者: 2478003816    时间: 2016-2-1 10:24
看到这里我就想,学习这个英语水平要怎么样,数学基础是不是要好..呢{:10_527:}
作者: HackerXuanxu    时间: 2016-5-21 17:35
额,那个......C++里也有类的概念,可是它的也叫函数......
有错误请指出
作者: ufof    时间: 2016-5-21 20:29
HackerXuanxu 发表于 2016-5-21 01:35
额,那个......C++里也有类的概念,可是它的也叫函数......
有错误请指出

我在教程中说明了“方法”和“函数”严格意义上的区别,不过大多数情况下还是根据习惯说的。没必要钻牛角尖。
关于挂图的事情我也了解到了,我争取放假的时候改正过来。

作者: Innovators    时间: 2016-6-9 04:10
那个这个方法必须要有参数的吗?如果没有参数可以运行吗
作者: ufof    时间: 2016-6-9 08:59
Innovators 发表于 2016-6-8 12:10
那个这个方法必须要有参数的吗?如果没有参数可以运行吗

不知层主说的“这个方法”指的是哪个方法。
如果是主方法,必须是String[]参数;如果是自己定义的方法,参数随意,按需求定义。
作者: Innovators    时间: 2016-6-9 13:33
ufof 发表于 2016-6-9 08:59
不知层主说的“这个方法”指的是哪个方法。
如果是主方法,必须是String[]参数;如果是自己定义的方法, ...

抱歉,没注意到这个是目录点击的,在4.5方法的递归中getFactorial(long num)方法必须要参数吗?
作者: ufof    时间: 2016-6-9 14:56
Innovators 发表于 2016-6-8 21:33
抱歉,没注意到这个是目录点击的,在4.5方法的递归中getFactorial(long num)方法必须要参数吗? ...

getFactorial()是求阶乘的方法,必然必须传入一个数字,这个方法才能计算这个数字的阶乘。这是逻辑问题,而非语法问题。
作者: IDE_helloender    时间: 2017-6-16 21:03
2478003816 发表于 2016-2-1 10:24
看到这里我就想,学习这个英语水平要怎么样,数学基础是不是要好..呢{:10_527:} ...

恕我直言,我估计你没读完初中。
作者: 3328763831a    时间: 2019-2-26 18:17
ssssssssssssssssssssssssssssssssssssssssssssssss
作者: 名为123的貘    时间: 2019-8-15 22:20
其实说白了就是参数可分辨地不同
作者: QWQ史蒂夫QWQ    时间: 2019-8-16 13:10
simon3000 发表于 2015-9-5 12:10
感觉把目录最底下放一个会跟好?

大佬活跃之地