成员变量是用来存储对象的数据信息的,那么如何表示对象的行为功能呢?就要通过方法来实现
方法
概念:
方法也叫函数,是一个独立功能的定义,是一个类中最基本的功能单元。把一个功能封装为方法的目的是,可以实现代码重用,从而简少代码量。
方法的使用原则
- 必须先声明后使用。类,变量,方法等都要先声明后使用
- 不调用不执行,调用一次执行一次。谁先调用,谁先执行
- 方法执行完毕之后,会回到方法调用处
- 方法体中的代码遵循自上而下的顺序依次逐行执行。
声明方法语法格式
格式详解:
- 修饰符: 修饰符后面一一介绍,例如:public,static等都是修饰符
- 返回值类型:表示方法运行的结果的数据类型,方法执行后将结果返回到调用者。可以是基本数据类型也可以是引用数据类型,如果无返回值类型就可以使用void来代替
- 方法名:给方法起一个名字,见名知意,能准确代表该方法功能的名字
- 参数列表:方法内部需要用到其他方法中的数据,需要通过参数传递的形式将数据传递过来,可以是基本数据类型、引用数据类型。带参方法定义时,参数中的数据类型与变量名都不能缺少,缺少任意一个程序将报错。带参方法定义时,多个参数之间使用逗号(,)分隔。参数列表也可以为空。
- 方法体:方法要完成特定功能代码
- return:结束方法,并将方法的结果返回去。如果返回值类型不是void,方法体中必须保证一定有return 返回值;语句,并且要求该返回值结果的类型与声明的返回值类型一致或兼容。如果返回值类型为void时,return 后面不用跟返回值,甚至也可以没有return语句。return语句后面就不能再写其他代码了,否则会报错:Unreachable code
成员方法分为两类
- 实例方法:没有static修饰的方法,必须通过实例对象来调用。
//有参,有返回值的实例方法 public int getMax(int a, int b) { return (a > b ? a : b); }
- 静态方法:有static修饰的方法,也叫类方法,可以由实例对象来调用也可以由类名来调用。推荐使用类名调用
//有参,有返回值的静态方法 public static int getMax(int a, int b) { return (a > b ? a : b); }
如何在其他类中调用方法
实例方法
类方法
代码示例
public class Demo02Method { //无参,无返回值实例方法 public void method1() { System.out.println("无参,无返回值方法"); } //无参,有返回值静态方法 public static int method2() { return 1; } } class TestDemo02Method{ public static void main(String[] args) { //创建对象 Demo02Method method = new Demo02Method(); //调用实例方法 method.method1(); //调用静态方法 method.method2(); //不推荐 Demo02Method.method2(); //推荐 } }
形参和实参
- 形参:在定义方法时方法名后面括号中声明的变量称为形式参数(简称形参)即形参出现在方法定义时。
- 实参:调用方法时方法名后面括号中的使用的值/变量/表达式称为实际参数(简称实参)即实参出现在方法调用时。
注意事项
- 调用时,需要传“实参”,实参的个数、类型、顺序顺序要与形参列表一一对应如果方法没有形参,就不需要也不能传实参。
- 调用时,如果方法有返回值,可以接受或处理返回值结果,当然也可以不接收,那么此时返回值就丢失了。如果方法的返回值类型是void,不需要也不能接收和处理返回值结果。
在本类中访问本类的成员变量和成员方法
- 直接写成员变量名或者是成员方法名,不需要加“对象名."和"类名."。唯一例外:静态方法中不能直接访问本类的非静态的成员变量和成员方法
public class Demo02Method { //无参,无返回值实例方法 public void method1() { method2();//本类中调用其他方法 System.out.println("无参,无返回值方法"); } //无参,有返回值静态方法 public static int method2() { //method1();错误:静态方法中不能直接访问本类的非静态的成员变量和成员方法 return 1; } }
方法调用内存分析
java 程序开始执行的时候先通过类加载器子系统找到硬盘上的字节码 (class)文件,然后将其加载到 java 虚拟机的方法区当中,开始调用 main 方法,main 方法被调用的瞬间,会给 main 方法在“栈”内存中分配所属的活动空间,此时发生压栈动作,main 方 法的活动空间处于栈底。
也就是说,方法只定义不去调用的话,只是把它的代码片段存储在方法区当中,java 虚拟机是不会在栈内存当中给该方法分配活动空间的,只有在调用的瞬间,java 虚拟机才会在“栈 内存”当中给该方法分配活动空间,此时发生压栈动作,直到这个方法执行结束的时候,这个 方法在栈内存中所对应的活动空间就会释放掉,此时发生弹栈动作。
由于栈的特点是先进后出, 所以最先调用的方法(最先压栈)一定是最后结束的(最后弹栈)。比如:main 方法最先被 调用,那么它一定是最后一个结束的。换句话说:main 方法结束了,程序也就结束了(目前 来说是这样)。
方法不调用不执行,调用一次执行一次,每次调用会在栈中有一个入栈动作,即给当前方法开辟一块独立的内存区域,用于存储当前方法的局部变量的值,当方法执行结束后,会释放该内存,称为出栈,如果方法有返回值,就会把结果返回调用处,如果没有返回值,就直接结束,回到调用处继续执行下一条指令。
方法体当中的代码是有执行顺序的,必须遵循自上而下的顺序依次逐行执行,当前行代码必须执行结束之后,下一行代码才能执行,不能跳行执 行,还记得吗?现在再和栈数据结构一起联系起来思考一下,为什么要采用栈数据结构呢?是 不是只有采用这种先进后出的栈数据结构才可以保证代码的执行顺序呢!
查看下列代码,分析是如何执行的
package demo01; /* 1.练习: 设计一个方法可以获取两个int数的较大值,数据来自于参数 2.三要素: (1)方法名称: getMax (2)参数列表: int a,int b (3)返回值类型: int */ public class Demo03GetMax { public static void main(String[] args) { System.out.println("main...start..."); //调用方法: 传递的是常量 int result = getMax(100,200); System.out.println("100和200的最大值: "+result); //调用方法方法: 传递变量 int a = 10, b = 20; int max = getMax(a,b); System.out.println(a+"和"+b+"的最大值: "+max); System.out.println("main...end..."); } //设计一个方法可以获取两个int数的较大值,数据来自于参数 public static int getMax(int a, int b) { int max = (a>b) ? a : b; return max;//结束方法,并且把max中的数据,返还给方法的调用处/者 } }
图解分析
方法的调用总结:
有返回值的方法调用方式
/* 有返回值的方法调用方式 1.赋值调用: 把有返回值的方法的调用结果保存到对应的变量中 ----推荐使用---- 数据类型 变量名称 = 方法名称(参数...); 2.输出/打印调用: 把有返回值的方法的调用结果直接交给输出语句 System.out.println(方法名称(参数...)); 3.单独调用: 既不保存方法的结果,也没有对结果进行输出 -----不推荐使用,没有意义----- 方法名称(参数...); */ public class Demo01DYMethod { public static void main(String[] args) { System.out.println("main...start..."); //1.赋值调用: 把有返回值的方法的调用结果保存到对应的变量中 //数据类型 变量名称 = 方法名称(参数...); int result = getSum(10,20); //可以对结果数据做其它操作 //result *= 100; System.out.println("和: "+result); //2.输出/打印调用: 把有返回值的方法的调用结果直接交给输出语句 //System.out.println(方法名称(参数...)); System.out.println(getSum(100,200)); //3.单独调用: 既不保存方法的结果,也没有对结果进行输出 getSum(5,10); System.out.println("main...end..."); } //定义方法,获取2个int数字之和 public static int getSum(int a, int b) { int sum = a + b; return sum; } }
无返回值的方法调用方式
/* 无返回值的方法调用方式 1.单独调用: 既不保存方法的结果,也没有对结果进行输出 只能采用单独调用 方法名称(参数...); 2.赋值调用: 把有返回值的方法的调用结果保存到对应的变量中 不能赋值调用 数据类型 变量名称 = 方法名称(参数...); 3.输出/打印调用: 把有返回值的方法的调用结果直接交给输出语句 不能输出调用 System.out.println(方法名称(参数...)); */ public class Demo02DYMethod { public static void main(String[] args) { System.out.println("main...start..."); //1.单独调用: 既不保存方法的结果,也没有对结果进行输出 //方法名称(参数...); printSum(10,20); //2.赋值调用: 把有返回值的方法的调用结果保存到对应的变量中 //数据类型 变量名称 = 方法名称(参数...); //int a = printSum(5,15);//错误的,int变量只能保存整数,但是printSum方法执行结束没有返回任何结果数据 //void a = printSum(5,15);//错误的,void根本不是数据类型 //3.输出/打印调用: 把有返回值的方法的调用结果直接交给输出语句 //System.out.println(方法名称(参数...)); //System.out.println(printSum(3,2));//错误: 因为printSum方法执行完毕后,没有任何结果返回 System.out.println("main...end..."); } //定义方法,打印2个int数字之和 public static void printSum(int a, int b) { int sum = a + b; System.out.println("和: "+sum); return ;//结束方法,返回到方法的调用处,注意没有带回任何数据 } }
方法的注意事项
- 方法不能嵌套定义(在定义方法的内部又定义了其它方法),可以调用其它方法
- 方法可以嵌套调用
- 返回值类型,必须要和 return 语句返回的数据的类型要匹配,否则编译失败 。
- 不能在 return 后面写代码, return 意味着方法结束,所有后面的代码永远不会执行,属于无效代码。
- void表示无返回值,可以省略return,也可以单独的书写return,后面不能加数据,写个分号
方法重载
概念:
- 在同一个类中,多个功能相同,但是参数列表不同的多个方法。同时存在一个类中的现象,就叫做方法重载。
作用/目的:
- 减少程序员的学习和使用成本
- 减少了方法名称的数量
JVM 调用
- 根据名称找到对应的方法
- 根据参数的数量找到对应的方法
- 根据参数的类型确定最终要调用的方法 (首先: 做类型完全匹配 其次: 完全匹配的找不到,再做自动类型提升的匹配)
方法重载与哪些因素无关
- 与参数的名称无关
- 与返回值类型无关
- 与修饰符无关
方法重载中参数列表不同有哪些情况
- 参数数量不同
- 参数类型不同
- 多个类型,顺序不同
代码示例
public class Demo { public static void main(String[] args) { method(10,10.0); } //1.此方法只有一个int类型参数 public static void method(int a) { } //2.此方法只有两个int类型参数 //方法2和方法1参数的数量是不同的,可以构成重载 public static void method(int a,int b) { } //3.此方法只有一个double类型参数 //方法3和方法2参数的数量是不同的,可以构成重载 //方法3和方法1参数虽然都是只有一个,但是类型不同,可以构成重载 public static void method(double a) { } //4.此方法有一个int类型参数和一个double类型参数 public static void method(int a,double b){ } //5.此方法有一个double类型参数和一个int类型参数 //方法5和方法4,虽然参数都是2个,但是类型的顺序不同 public static void method(double a,int b){ } }
总结: 同类多个方法同名的前提下,。 只看多个方法的参数(除了名称以外)有区别,就构成重载
方法参数传递
前置知识:
- 使用=进行赋值的特点:把基本类型变量a的值赋值给基本类型变量b时,其实是把a中的值复制一份给变量b,之后不管如何修改变量b中的值,都不会影响变量a中的值
- 变量的作用范围:方法内部定义的变量只在所定义的方法内有效(可以使用),出了方法的作用范围,就不能使用了。
基本数据类型作为方法参数
/* 注意: 1.基本类型变量: 保存的是具体的数据值 2.基本类型变量作为形式参数,形式参数的改变,不会影响实际参数 基本类型变量作为形式参数: 定义方法时,()中定义的参数属于基本类型 不会影响实际参数: 调用方法时,()中给出的参数 */ public class Demo { public static void main(String[] args) { int a = 10; int b = 20; System.out.println("ms...a="+a);//10 System.out.println("ms...b="+b);//20 //调用方法 change( a , b ); System.out.println("me...a="+a);//10 System.out.println("me...b="+b);//20 } public static void change(int a, int b) { System.out.println("cs...a="+a);//10 System.out.println("cs...b="+b);//20 a = a*10; b = b*10; System.out.println("ce...a="+a);//100 System.out.println("ce...b="+b);//200 } }
图解:
结论:
- 基本数据类型的参数,形式参数的改变,不影响实际参数
结论依据:
- 每个方法在栈内存中,都会有独立的栈空间,方法运行结束后就会弹栈消失
引用数据类型作为方法参数
/* 1.引用类型变量: 保存的是对象在堆内存空间的地址值,进行参数传递的时候,传递的也是地址值 2.引用类型变量作为形式参数,通过形式参数找到对应的堆内存空间,修改堆内存空间的内容之后, 通过实际参数看到的一定是修改后的堆内存空间的内容 引用类型作为形式参数,形式参数的改变,会影响实际参数 数组: 1.数组也是一种引用类型: 数组名称保存的也是数组在堆内存空间的地址值 2.数组作为方法参数或者返回值: 传递的都是数组在堆内存空间的地址值 */ public class Demo03RefVar { public static void main(String[] args) { int[] arr = { 10 , 20 }; //System.out.println(arr);//数组名称: 保存数组在内存中的地址值[I@1540e19d System.out.println("ms...arr[0]="+arr[0]);//10 System.out.println("ms...arr[1]="+arr[1]);//20 //调用方法 change( arr ); System.out.println("me...arr[0]="+arr[0]);//100 System.out.println("me...arr[1]="+arr[1]);//200 } public static void change(int[] arr ) { System.out.println("cs...arr[0]="+arr[0]);//10 System.out.println("cs...arr[1]="+arr[1]);//20 arr[0] = arr[0]*10; arr[1] = arr[1]*10; System.out.println("ce...arr[0]="+arr[0]);//100 System.out.println("ce...arr[1]="+arr[1]);//200 } }
图解
结论:
- 对于引用类型的参数,形式参数的改变,影响实际参数的值
结论依据:
- 引用数据类型的传参,传入的是地址值,内存中会造成两个引用指向同一个内存的效果,所以即使方法弹栈,堆内存中的数据也已经是改变后的结果
可变参数
前提:在JDK1.5之后,如果我们定义一个方法需要接受多个参数,并且多个参数类型一致,我们可以对其简化.
其实可变参数本质上就是一个数组
只是后面这种定义,在调用时必须传递数组,而前者更灵活,既可以传递数组,又可以直接传递数组的元素,这样更灵活了。
注意事项
- 一个方法最多只能有一个可变参数,可以变参数可以接收的参数数量是0 到n 个。
/* 编译报错,因为一个方法,只能有一个可变参数 public static void method1(int... nums,String... strs){ }*/
- 如果一个方法包含可变参数,那么可变参数必须是形参列表的最后一个
/* 编译报错,因为如果方法有多个参数,可变参数一定要放在末尾 public static void method2(int... nums,String str){ } */
代码实例
//求n个整数的和 public class ChangeArgs { public static void main(String[] args) { int[] arr = {1, 4, 62, 431, 2}; int sum1 = getSum1(arr); System.out.println(sum1); //500 int sum2 = getSum2(arr); System.out.println(sum2); //500 } // 完成数组 所有元素的求和 // 原始写法 public static int getSum1(int[] arr) { int sum = 0; for (int i = 0; i < arr.length; i++) { sum += arr[i]; } return sum; } // 可变参数写法 public static int getSum2(int... arr) { int sum = 0; for (int i = 0; i < arr.length; i++) { sum += arr[i]; } return sum; } }
递归
递归:指在当前方法内调用自己的这种现象。
递归的分类:
- 递归分为两种,直接递归和间接递归。
- 直接递归称为方法自身调用自己。
- 间接递归可以A方法调用B方法,B方法调用C方法,C方法调用A方法。
注意事项:
- 递归一定要有条件限定,保证递归能够停止下来,否则会发生栈内存溢出。
- 在递归中虽然有限定条件,但是递归次数不能太多。否则也会发生栈内存溢出
代码实例
package demo02; public class RecursionMethod2 { public static void main(String[] args) { int jieCheng = jieCheng(10); System.out.println("10的阶乘是:" + jieCheng); //3628800 } //使用递归求阶乘 public static int jieCheng(int n) { if (n <= 1) { return 1; } return n * jieCheng(n - 1); } }