什么是方法(method、函数):
- 方法是类或对象行为特征的抽象,用来完成某个功能操作。在某些语言中也称为函数或过程。
- 将功能封装为方法的目的是,可以实现代码重用,简化代码
- Java里的方法不能独立存在,所有的方法必须定义在类里。
方法的定义和调用
方法的语法机制
- 修饰符:此项是可选项,不是必须的,目前大家统一写成 public static,后面会详细讲解。
-
返回值类型:
-
返回值一般指的是一个方法执行结束之后的结果。结果通常是一个数据,所以被称为“值”,而且还叫“返回值”。方法执行结束之后的返回值实际上是给调用者了。谁调用就返回给谁。
- 此项可以是 java 语言当中任何一种数据类型,包括基本数据类型,也包括所有的引用数据类型,当然,如果一个方法执行结束之后不准备返回任何数据,则返回值类型必须写 void。
- 如果返回值类型是void,那么在方法体当中不能有“return 值;”这样的语句。但是可以有“return;”语句。这个语句“return;”的作用就是用来终止当前方法的。
- 除了void之外,剩下的都必须有“return 值;”这样的语句
-
只要有“return”关键字的语句执行,当前方法必然结束。return只要执行,当前所在的方法结束,记住:不是整个程序结束。在同一个“域”中,“return”语句后面是不能写任何代码的,因为它无法执行到。
-
- 方法名:此项需要是合法的标识符,开发规范中要求方法名首字母小写,后面每个单词首字母大写,遵循驼峰命名方式,见名知意,例如:login、getUsername、findAllUser 等。
- 形式参数列表(int a, int b):此项又被称为形参,其实每一个形参都是“局部变量”,形参的个数为 0~N 个,如果是多个参数,则采用半角“,”进行分隔,形参中起决定性作用的是参数的数据类型,参数名就是变量名,变量名是可以修改的,也就是说(int a , int b)也可以写成(int x , int y)。
- 方法体:由一对儿大括号括起来,在形参的后面,这个大括号当中的是实现功能的核心代码,方法体由 java 语句构成,方法体当中的代码只能遵循自上而下的顺序依次逐行执行,不能跳行执行,核心代码在执行过程中如果需要外部提供数据,则通过形参进行获取。
代码示例:
public class MethodDemo { public static void main(String[] args) { //调用方法 isEvenNumber(); } //需求:定义一个方法,在方法中定义一个变量,判断该数据是否是偶数 public static void isEvenNumber() { //定义变量 int number = 10; number = 9; //判断该数据是否是偶数 if(number%2 == 0) { System.out.println(true); } else { System.out.println(false); } } }
方法的调用
- 我们可以在方法体中通过方法名调用要执行的方法,且只有被调用才会执行。方法在调用的时候,实际传给这个方法的数据被称为实际参数列表,简称实参,java 语法中有这样的规定:实参和形参必须一一对应,所谓的一一对应就是,个数要一样,数据类型要对应相同。例如:实参(100 , 200)对应的形参(int x , int y),如果不是一一对应则编译器就会报错。当然也可能会存在自动类型转换,例如:实参(100 , 200)也可以传递给这样的形参(long a , long b)
- 静态方法调用:类名.方法名(实际参数列表);a()方法调用b()方法的时候,a和b方法都在同一个类中,“类名.”可以省略。如果不在同一个类中“类名.”不能省略。
- 成员方法调用:创建对象使用对象变量名调用方法
方法的分类
使用方法的注意事项:
- 方法体是完成功能的核心代码,方法体中的代码有执行顺序的要求,遵循自上而下的顺序依次逐行执行,不存在跳行执行的情况。
- 方法被调用一次,就会执行一次
- 没有具体返回值的情况,返回值类型用关键字void表示,那么方法体中可以不必使用return语句。如果使用,仅用来结束方法。
- 定义方法时,方法的结果应该返回给调用者,交由调用者处理。我们可以使用变量接收此结果。
- 方法中只能调用方法或属性,不可以在方法内部定义方法。
- 方法定义的顺序无所谓
break和return的区别
- break;用来终止switch和离它最近的循环。
- return;用来终止离它最近的一个方法。
如果有返回值类型就必须保证百分之一百执行return语句,否则报错: 缺少返回语句。下面的代码就会报缺少返回语句错误
public class Demo { public static void main(String[] args) { m(); } public static int m(){ boolean flag = true; //编译器不负责运行程序,编译器只讲道理。 // 对于编译器来说,编译器只知道flag变量是boolean类型 // 编译器会认为flag有可能是false,有可能是true if(flag){ // 编译器觉得:以下这行代码可能会执行,当然也可能不会执行 // 编译器为了确保程序不出现任何异常,所以编译器说:缺少返回语句 return 1; } } }
java 虚拟机内存结构图
要想真正掌握 java,内存的分析是必要的。一旦掌握内存的分配,在程序没有运行之前我们就可以很精准的预测到程序的执行结果。好了,接下来我们开始学习方法执行过程中内存是如何变化的?我们先来看一张图片: 上图是一张标准的 java 虚拟机内存结构图,目前我们只看其中的“栈”和“方法区”,其它的后期研究,方法区中存储类的信息,或者也可以理解为代码片段,方法在执行过程中需要 的内存空间在栈中分配。java 程序开始执行的时候先通过类加载器子系统找到硬盘上的字节码(class)文件,然后将其加载到 java 虚拟机的方法区当中,开始调用 main 方法,main 方法被调用的瞬间,会给 main 方法在“栈”内存中分配所属的活动空间,此时发生压栈动作,main 方法的活动空间处于栈底。 也就是说,方法只定义不去调用的话,只是把它的代码片段存储在方法区当中,java 虚拟机是不会在栈内存当中给该方法分配活动空间的,只有在调用的瞬间,java 虚拟机才会在“栈内存”当中给该方法分配活动空间,此时发生压栈动作,直到这个方法执行结束的时候,这个方法在栈内存中所对应的活动空间就会释放掉,此时发生弹栈动作。 栈的特点是先进后出,所以最先调用的方法(最先压栈)一定是最后结束的(最后弹栈)。比如:main 方法最先被调用,那么它一定是最后一个结束的。换句话说:main 方法结束了,程序也就结束了(目前来说是这样)。 接下来我们详细来说说数据结构栈。栈数据结构
要想理解方法执行过程中内存的分配,我们需要先学习一下栈数据结构,那么什么是数据结构呢?其实数据结构是一门独立的学科,不仅是在 java 编程中需要使用,在其它编程语言中也会使用,在大学的计算机课程当中,数据结构和算法通常作为必修课出现,而且是在学习任何一门编程语言之前先进行数据结构和算法的学习。数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。数据结构往往同高效的检索算法和索引技术有关。 常见的数据结构有哪些呢?例如:栈、队列、链表、数组、树、图、堆、散列表等。目前我们先来学习一下栈(stack)数据结构,这是一种非常简单的数据结构。如下图所示: 栈(stack)又名堆栈,它是一种运算受限的线性表。其限制是:仅允许在表的一端进行插入和删除运算。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈(push),它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈、退栈或弹栈(pop),它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。 通过以上的学习,我们可以得知栈数据结构存储数据有这样的特点:先进后出,或者后进先出原则。也就是说最先进去的元素一定是最后出去,最后进去的元素一定是最先出去,因为一端是开口的,另一端是封闭的。方法执行过程中内存的变化
接下来我们来看一段代码,同时画出内存结构图,以及使用文字描述该程序的内存变化:public class MethodTest { 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"); } }运行结果如下图所示: 通过上图的执行结果我们了解到,main 方法最先被调用,但是它是最后结束的,其中 m2方法最后被调用,但它是最先结束的。大家别忘了调用的时候分配内存是压栈,结束的时候是释放内存弹栈哦。为什么会是上图的结果呢,我们来看看它执行的内存变化,请看下图: 通过上图的分析,可以很快明白,为什么输出结果是这样的顺序,接下来我们再采用文字的方式描述它的内存变化:
- 类加载器将 class 文件加载到方法区。
- 开始调用 main方法,在栈内存中给 main方法分配空间,开始执行 main方法,输出”mainbegin”。
- 调用 m1()方法,在栈内存中给 m1()方法分配空间,m1()方法处于栈顶,具备活跃权,输出”m1 begin”。
- 调用 m2()方法,在栈内存中给 m2()方法分配空间,m2()方法处于栈顶,具备活跃权,输出”m2 begin”,继续输出”m2 over”。
- m2()方法执行结束,内存释放,弹栈。
- m1()方法这时处于栈顶,具备活跃权,输出”m1 over”。
- m1()方法执行结束,内存释放,弹栈。
- main()方法这时处于栈顶,具备活跃权,输出”main over”。
- main()方法执行结束,内存释放,弹栈。
- 栈空了,程序结束。
方法练习
/* 编写一个方法,输出大于某个正整数n的最小的质数 比如:这个正整数n是2 也就是要输出:大于2的最小的质数,结果就是3 比如:这个正整数n是9 也就是要输出:大于9的最小的质数,结果就是11 大于11的最小的质数是:13 思路: 首先,系统一定会先给你一个“正整数n”,然后你基于 这个n往后++,每加1得到的新数m判断一下是否为质数。 */ public class Homework2{ public static void main(String[] args){ printZuiXiaoZhiShu(5); printZuiXiaoZhiShu(10); printZuiXiaoZhiShu(12); printZuiXiaoZhiShu(100); } // 这方法就是用来打印最小质数的。 public static void printZuiXiaoZhiShu(int n){ while(true){ n++; // n自加1 // 判断此时的n是否为质数 boolean flag = isZhiShu(n); if(flag){ System.out.println(n); break; } } } // 定义一个专门的方法,来判断某个数字是否为质数 // 这个方法的形参是:被判断的数字num // 这个方法的返回值类型是true表示是质数,是false表示非质数。 public static boolean isZhiShu(int num){ // 你怎么判断num是否是一个质数 // 质数只能被1和自身整除 for(int i = 2; i < num; i++){ if(num % i == 0){ return false; } } //程序能够执行到此处说明num已经是质数了。 return true; } }
方法重载/overload
重载的概念- 在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可。
- 与返回值类型无关,只看参数列表,且参数列表必须不同。(参数个数或参数类型)。调用时,根据方法参数列表的不同来区别。重载仅针对同一个类中方法的名称与参数进行识别,与返回值无关,换句话说不能通过返回值来判定两个方法是否相互构成重载
总结
Java 编译器能通过检查调用的方法的参数类型和个数选择一个恰当的方法。方法重载通常用于创建完成一组任务相似但参数的类型或参数的个数不同的方法。调用方法时通过传递给它们的不同个数和类型的实参来决定具体使用哪个方法。什么情况下我们考虑使用方法重载呢?在同一个类当中,如果多个功能是相似的,可以考虑将它们的方法名定义的一致,使用方法重载机制,这样便于程序员的调用,以及代码美观,但相反,如果两个方法所完成的功能完全不同,那么方法名也一定要不一样,这样才是合理的。 代码满足什么条件的时候构成方法重载呢?满足以下三个条件:- 在同一个类当中。
- 方法名相同。
- 参数列表不同:个数不同算不同,顺序不同算不同,类型不同也算不同。
代码示例
/* 方法重载(overload): 什么时候需要考虑使用方法重载? 在同一个类当中,如果“功能1”和“功能2”它们的功能是相似的, 那么可以考虑将它们的方法名一致,这样代码既美观,又便于 后期的代码编写(容易记忆,方便使用)。 注意:方法重载overload不能随便使用,如果两个功能压根不相干, 不相似,根本没关系,此时两个方法使用重载机制的话,会导致 编码更麻烦。无法进行方法功能的区分。 什么时候代码会发生方法重载? 条件1:在同一个类当中 条件2:方法名相同 条件3:参数列表不同 参数的个数不同算不同 参数的类型不同算不同 参数的顺序不同算不同 只要同时满足以上3个条件,那么我们可以认定方法和方法之间发生了 重载机制。 注意: 不管代码怎么写,最终一定能让java编译器很好的区分开这两个方法。 方法重载和方法的“返回值类型”无关。 方法重载和方法的“修饰符列表”无关。 */ public class OverloadTest03{ public static void main(String[] args){ m1(); m1(100); m2(10, 3.14); m2(3.14, 10); m3(100); m3(3.14); } public static void m1(){ System.out.println("m1无参数的执行!"); } // 这个方法的参数个数和上面的方法的参数个数不同。 public static void m1(int a){ System.out.println("m1有一个int参数执行!"); } public static void m2(int x, double y){ System.out.println("m2(int x, double y)"); } // 参数的顺序不同,也算不同。 public static void m2(double y, int x){ System.out.println("m2(double y, int x)"); } public static void m3(int x){ System.out.println("m3(int x)"); } // 参数的类型不同。 public static void m3(double d){ System.out.println("m3(double d)"); } class MyClass{ // 不在同一个类当中,不能叫做方法重载。 public static void m1(int x, int y){ } }