#+|Java方法


Java方法

  • Java方法的本质及作用
    • Java方法的定义及调用
      • Java栈数结构
        • Java方法执行过程中内存的变化
          • Java递归方法
            • Java方法重载

#+|Java方法
文章图片

Java方法的本质及作用 先来看一段代码,分析以下程序存在哪些缺点,应该如何去改进:
public static void main(String[] args) { //请计算10和20的和 int a = 10; int b = 20; int c = a + b; System.out.println(a + "+" + b + "=" + c); //请计算111和222的和 int x = 111; int y = 222; int z = x + y; System.out.println(x + "+" + y + "=" + z); //请计算222和333的和 int m = 222; int n = 333; int e = m + n; System.out.println(m + "+" + n + "=" + e); }

每一次求和的时候都把代码重新写了一遍,显然代码没有得到“重复利用”,表面上看是三个功能,但实际上只是“一个”求和功能,只不过每一次参与求和的实际数值不同。
java中有没有一种方式或者语法,能让我们把功能性代码写一次,然后给这个功能性代码传递不同的数据,来完成对应的功能呢?
答案是:当然有。这就需要我们掌握java语言中的方法机制。
接下来大家看看改进之后的代码:
public static void main(String[] args) { //调用求和方法计算10和20的和 sumInt(10 , 20); //调用求和方法计算666和888的和 sumInt(111, 222); //调用求和方法计算888和999的和 sumInt(222, 333); } //专门负责求和的方法 public static void sumInt(int a , int b){ int c = a + b; System.out.println(a + "+" + b + "=" + c); }

方法其实就是一段普通的代码片段,并且这段代码可以完成某个特定的功能,而且可以被重复的调用/使用。java中的方法又叫做method,在C语言中叫做函数。
Java方法的定义及调用 定义/声明方法的语法格式如下所示:
[修饰符列表] 返回值类型 方法名(形式参数列表){ 方法体; }例如代码: public static void sumInt(int a , int b){ int c = a + b; System.out.println(a + "+" + b + "=" + c); } public static是修饰符列表; void是返回值类型; sumInt是方法名; (int a , int b)是形式参数列表,简称形参,每一个形参都是局部变量; 形参后面使用一对儿大括号括起来的是方法体,方法体是完成功能的核心代码,方法体中的代码有执行顺序的要求,遵循自上而下的顺序依次逐行执行,不存在跳行执行的情况。 再如代码: public static int sumInt(int a , int b){ int c = a + b; return c; } 以上程序中sumInt之前的int是返回值类型。

1、[修饰符列表],此项是可选项,不是必须的,目前大家统一写成public static。
2、返回值类型,此项可以是java语言当中任何一种数据类型,包括基本数据类型,也包括所有的引用数据类型,当然,如果一个方法执行结束之后不准备返回任何数据,则返回值类型必须写void。返回值类型例如:byte,short,int,long,float,double,boolean,char,String,void等。
3、方法名,此项需要是合法的标识符,开发规范中要求方法名首字母小写,后面每个单词首字母大写,遵循驼峰命名方式,见名知意,例如:login、getUsername、findAllUser等。
4、 形式参数列表(int a, int b),此项又被称为形参,其实每一个形参都是“局部变量”,形参的个数为0~N个,如果是多个参数,则采用半角“,”进行分隔,形参中起决定性作用的是参数的数据类型,参数名就是变量名,变量名是可以修改的,也就是说(int a , int b)也可以写成(int x , int y)。
5、方法体,由一对儿大括号括起来,在形参的后面,这个大括号当中的是实现功能的核心代码,方法体由java语句构成,方法体当中的代码只能遵循自上而下的顺序依次逐行执行,不能跳行执行,核心代码在执行过程中如果需要外部提供数据,则通过形参进行获取。

当一个方法声明之后,我们应该如何去让这个方法执行呢,当然,这个时候就需要亲自去调用这个方法了,调用方法的语法格式是(前提是方法的修饰符列表中带有static关键字):“类名.方法名(实际参数列表); ”,例如以下代码:
public class Test01 { public static void main(String[] args) { Test01.sumInt(100, 200); Test01.sumDouble(1.0, 2.0); } public static void sumInt(int x , int y){ System.out.println(x + "+" + y + "=" + (x + y)); } public static void sumDouble(double a , double b){ System.out.println(a + "+" + b + "=" + (a + b)); } }

#+|Java方法
文章图片

方法在调用的时候,实际传给这个方法的数据被称为实际参数列表,简称实参,java语法中有这样的规定:==实参和形参必须一一对应,所谓的一一对应就是,个数要一样,数据类型要对应相同。==例如:实参(100 , 200)对应的形参(int x , int y),如果不是一一对应则编译器就会报错。当然也可能会存在自动类型转换,例如:实参(100 , 200)也可以传递给这样的形参(long a , long b)。
实际上方法在调用的时候,有的情况下“类名.”是可以省略的,我们来看看什么情况下它可以省略不写:
public class Test02 { public static void main(String[] args) { sumInt(100, 200); //“类名.”可以省略 sumDouble(1.0, 2.0); //“类名.”可以省略 //doOther(); //编译报错 Other.doOther(); //“类名.”不能省略 } public static void sumInt(int x , int y){ System.out.println(x + "+" + y + "=" + (x + y)); } public static void sumDouble(double a , double b){ System.out.println(a + "+" + b + "=" + (a + b)); } }

public class Other{ public static void doOther(){ System.out.println("Other doOther..."); } }

【#+|Java方法】#+|Java方法
文章图片

当在a()方法执行过程中调用b()方法的时候,并且a()方法和b()方法在同一个类当中,此时“类名.”可以省略不写,但如果a()方法和b()方法不在同一个类当中,“类名.”则不能省略。
Java栈数结构
常见的数据结构有哪些呢?例如:栈、队列、链表、数组、树、图、堆、散列表等。目前我们先来学习一下栈(stack)数据结构,这是一种非常简单的数据结构。如下图所示:
#+|Java方法
文章图片

*栈(stack)又名堆栈,它是一种运算受限的线性表。其限制是:仅允许在表的一端进行插入和删除运算。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈(push),它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈、退栈或弹栈(pop),它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。*如下图所示:
#+|Java方法
文章图片

栈数据结构存储数据有这样的特点:先进后出,或者后进先出原则。也就是说最先进去的元素一定是最后出去,最后进去的元素一定是最先出去,因为一端是开口的,另一端是封闭的。
Java方法执行过程中内存的变化 我们先来看一张图片:
#+|Java方法
文章图片

方法区中存储类的信息,或者也可以理解为代码片段,方法在执行过程中需要的内存空间在栈中分配。java程序开始执行的时候先通过类加载器子系统找到硬盘上的字节码(class)文件,然后将其加载到java虚拟机的方法区当中,开始调用main方法,main方法被调用的瞬间,会给main方法在“栈”内存中分配所属的活动空间,此时发生压栈动作,main方法的活动空间处于栈底。
方法只定义不去调用的话,只是把它的代码片段存储在方法区当中,java虚拟机是不会在栈内存当中给该方法分配活动空间的,只有在调用的瞬间,java虚拟机才会在“栈内存”当中给该方法分配活动空间,此时发生压栈动作,直到这个方法执行结束的时候,这个方法在栈内存中所对应的活动空间就会释放掉,此时发生弹栈动作。由于栈的特点是先进后出,所以最先调用的方法(最先压栈)一定是最后结束的(最后弹栈)。比如:main方法最先被调用,那么它一定是最后一个结束的。换句话说:main方法结束了,程序也就结束了(目前来说是这样)。
接下来我们来看一段代码,同时画出内存结构图,以及使用文字描述该程序的内存变化:
public class Test03 { public static void main(String[] args) { System.out.println("main begin"); m1(); System.out.println("main over"); } public static void m1() { System.out.println("m1 begin"); m2(); System.out.println("m1 over"); } public static void m2() { System.out.println("m2 begin"); System.out.println("m2 over"); } }

#+|Java方法
文章图片

main方法最先被调用,但是它是最后结束的,其中m2方法最后被调用,但它是最先结束的。大家别忘了调用的时候分配内存是压栈,结束的时候是释放内存弹栈哦。为什么会是上图的结果呢,我们来看看它执行的内存变化,请看下图:
#+|Java方法
文章图片

(1)类加载器将class文件加载到方法区。
(2) 开始调用main方法,在栈内存中给main方法分配空间,开始执行main方法,输出”main begin”。
(3)调用m1()方法,在栈内存中给m1()方法分配空间,m1()方法处于栈顶,具备活跃权,输出”m1 begin”。
(4)调用m2()方法,在栈内存中给m2()方法分配空间,m2()方法处于栈顶,具备活跃权,输出”m2 begin”,继续输出”m2 over”。
(5)m2()方法执行结束,内存释放,弹栈。
(6)m1()方法这时处于栈顶,具备活跃权,输出”m1 over”。
(7) m1()方法执行结束,内存释放,弹栈。
(8) main()方法这时处于栈顶,具备活跃权,输出”main over”。
(9)main()方法执行结束,内存释放,弹栈。
(10) 栈空了,程序结束。
Java递归方法 什么是方法递归?我们先来看一段代码:
public class Test04 { public static void main(String[] args) { m(); } public static void m(){ System.out.println("m begin"); m(); System.out.println("m over"); } }

#+|Java方法
文章图片

我们可以看到以上代码的执行过程中,一直输出“m begin”,“m over”一次也没有输出,直到最终发生了错误:java.lang.StackOverflowError,这个错误是栈内存溢出错误,错误发生后,JVM退出了,程序结束了。
实际上以上代码在m()方法执行过程中又调用了m()方法,方法自身调用自身,这就是方法递归调用。以上程序实际上等同于以下的伪代码(说明问题,但是无法执行的代码):
#+|Java方法
文章图片

m()方法一直在被调用(方法中的代码必须遵循自上而下的顺序依次逐行执行,不能跳行执行),对于栈内存来说一直在进行压栈操作,m()方法从未结束过,所以没有弹栈操作,即使栈内存足够大(也是有限的内存),总有一天栈内存会不够用的,这个时候就会出现栈内存溢出错误。通过以上研究得出递归必须要有合法的结束条件,没有结束条件就一定会发生StackOverflowError。我们再来看看有结束条件的递归,例如以下代码:
#+|Java方法
文章图片

综上所述,递归其实就是方法在执行的过程中调用了另一个方法,而另一个方法则是自己本身。在代码角度来看就是在a()方法中调用a()方法,使用递归须谨慎,因为递归在使用的时候必须有结束条件,没有结束条件就会导致无终止的压栈,栈内存最终必然会溢出,程序因错误的发生而终止。
Java方法重载 首先看一段代码:
public static void main(String[] args) { int x1 = 10; int x2 = 20; int retValue1 = sumInt(x1 , x2); System.out.println(x1 + "+" + x2 + "=" + retValue1); long y1 = 10L; long y2 = 20L; long retValue2 = sumLong(y1 , y2); System.out.println(y1 + "+" + y2 + "=" + retValue2); double z1 = 10.0; double z2 = 20.0; double retValue3 = sumDouble(z1, z2); System.out.println(z1 + "+" + z2 + "=" + retValue3); } public static int sumInt(int a , int b){ return a + b; } public static long sumLong(long a , long b){ return a + b; } public static double sumDouble(double a , double b){ return a + b; }

#+|Java方法
文章图片

我们可以看到以上三个方法功能“相似”,都是求和,只不过参与求和的数据类型不同,因此定义了三个方法,分别起了三个不同的方法名。
使用方法重载机制之后会是怎样,请看以下代码以及运行结果:
public static void main(String[] args) { int x1 = 10; int x2 = 20; int retValue1 = sum(x1 , x2); System.out.println(x1 + "+" + x2 + "=" + retValue1); long y1 = 10L; long y2 = 20L; long retValue2 = sum(y1 , y2); System.out.println(y1 + "+" + y2 + "=" + retValue2); double z1 = 10.0; double z2 = 20.0; double retValue3 = sum(z1, z2); System.out.println(z1 + "+" + z2 + "=" + retValue3); } public static int sum(int a , int b){ return a + b; } public static long sum(long a , long b){ return a + b; } public static double sum(double a , double b){ return a + b; }

#+|Java方法
文章图片

什么是方法重载呢?
方法重载(overload)是指在一个类中定义多个同名的方法,但要求每个方法具有不同的参数的类型或参数的个数。调用重载方法时,Java编译器能通过检查调用的方法的参数类型和个数选择一个恰当的方法。方法重载通常用于创建完成一组任务相似但参数的类型或参数的个数不同的方法。调用方法时通过传递给它们的不同个数和类型的实参来决定具体使用哪个方法。
什么情况下我们考虑使用方法重载呢?
在同一个类当中,如果多个功能是相似的,可以考虑将它们的方法名定义的一致,使用方法重载机制,这样便于程序员的调用,以及代码美观,但相反,如果两个方法所完成的功能完全不同,那么方法名也一定要不一样,这样才是合理的。
代码满足什么条件的时候构成方法重载呢?满足以下三个条件:
1、 在同一个类当中。
2、方法名相同。
3、 参数列表不同:个数不同算不同,顺序不同算不同,类型不同也算不同。

总之,方法1和方法2要想构成方法重载,首先它们在同一个类当中,方法名一样,参数列表不同(类型、个数、顺序),这样java虚拟机在运行的时候就可以分清楚去调用哪个方法了。其实,最终要调用哪个方法,还是取决于调用的时候传递的实际参数列表。所以在java编程中要区分两个方法,首先看方法名,如果方法名一致,则继续看它们的形式参数列表。
你有没有觉得每一次输出的时候“System.out.println(); ”这行代码很麻烦,我们来封装一个工具类,请看以下代码:
public class S{ public static void p(){ System.out.println(); } public static void p(int data){ System.out.println(data); } public static void p(long data){ System.out.println(data); } public static void p(float data){ System.out.println(data); } public static void p(double data){ System.out.println(data); } public static void p(boolean data){ System.out.println(data); } public static void p(char data){ System.out.println(data); } public static void p(String data){ System.out.println(data); } }

public class STest { public static void main(String[] args) { S.p("hello world!"); S.p(10); S.p(9.0); S.p(false); S.p('国'); int a = 10; int b = 20; int c = a + b; S.p(a + "+" + b + "=" + c); } }

#+|Java方法
文章图片

你只需要“U.p(); ”,当然,你需要把U.java文件编译生成的U.class文件拷贝到你的硬盘当中,一直携带着,什么时候需要的话,把U.class文件放到classpath当中就可以使用了。

    推荐阅读