成员变量是用来存储对象的数据信息的,那么如何表示对象的行为功能呢?就要通过方法来实现
方法
概念:
方法也叫函数,是一个独立功能的定义,是一个类中最基本的功能单元。把一个功能封装为方法的目的是,可以实现代码重用,从而简少代码量。
方法的使用原则
- 必须先声明后使用。类,变量,方法等都要先声明后使用
- 不调用不执行,调用一次执行一次。调用时必须严格匹配方法的参数情况。谁先调用,谁先执行
- 方法执行完毕之后,会回到方法调用处
- 方法体中的代码遵循自上而下的顺序依次逐行执行。
- 方法与方法之间是平级关系,方法不能嵌套定义(在定义方法的内部又定义了其它方法),可以嵌套调用其它方法
- 方法必须写在类中,不能独立存在。
声明方法语法格式
方法声明时:
- 返回值类型、形参列表可以按照需求进行填写
真正需要关注的就两点:
- 分析方法是否需要申明返回值类型;
- 分析方法是否需要接收参数。
例如:
格式详解:
- 修饰符: 修饰符后面一一介绍,例如: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(); //推荐
}
}
如何在本类中调用方法
- 直接写方法名,不需要加“对象名."和"类名."。唯一例外:静态方法中不能直接访问本类的非静态的成员变量和成员方法,因为静态的内容加载时间早于非静态内容。需要在先创建对象才能间接使用。实例方法可以直接访问静态和非静态的资源。
public class Demo02Method {
//无参,无返回值实例方法
public void method1() {
method2();//本类中调用其他方法
System.out.println("无参,无返回值方法");
}
//无参,有返回值静态方法
public static int method2() {
//method1();错误:静态方法中不能直接访问本类的非静态的成员变量和成员方法
return 1;
}
}
形参和实参
- 形参:在定义方法时方法名后面括号中声明的变量称为形式参数(简称形参)即形参出现在方法定义时。形参列表可以有多个,甚至可以没有; 如果有多个形参,多个形参必须用“,”隔开,且不能给初始化值。
- 实参:调用方法时方法名后面括号中的使用的值/变量/表达式称为实际参数(简称实参)即实参出现在方法调用时。
注意事项
- 调用时,需要传“实参”,实参的个数、类型、顺序顺序要与形参列表一一对应如果方法没有形参,就不需要也不能传实参。
- 调用时,如果方法有返回值,可以接受或处理返回值结果,当然也可以不接收,那么此时返回值就丢失了。如果方法的返回值类型是void,不需要也不能接收和处理返回值结果。
方法调用内存分析
java 程序开始执行的时候先通过类加载器子系统找到硬盘上的字节码 (class)文件,然后将其加载到 java 虚拟机的方法区当中,开始调用 main 方法,main 方法被调用的瞬间,会给 main 方法在“栈”内存中分配所属的活动空间,此时发生压栈动作,main 方 法的活动空间处于栈底。
也就是说,方法只定义不去调用的话,只是把它的代码片段存储在方法区当中,java 虚拟机是不会在栈内存当中给该方法分配活动空间的,只有在调用的瞬间,java 虚拟机才会在“栈 内存”当中给该方法分配活动空间,此时发生压栈动作,直到这个方法执行结束的时候,这个 方法在栈内存中所对应的活动空间就会释放掉,此时发生弹栈动作。
由于栈的特点是先进后出, 所以最先调用的方法(最先压栈)一定是最后结束的(最后弹栈)。比如:main 方法最先被 调用,那么它一定是最后一个结束的。换句话说:main 方法结束了,程序也就结束了(目前 来说是这样)。
方法不调用不执行,调用一次执行一次,每次调用会在栈中有一个入栈动作,即给当前方法开辟一块独立的内存区域,用于存储当前方法的局部变量的值,当方法执行结束后,会释放该内存,称为出栈,如果方法有返回值,就会把结果返回调用处,如果没有返回值,就直接结束,回到调用处继续执行下一条指令。
方法体当中的代码是有执行顺序的,必须遵循自上而下的顺序依次逐行执行,当前行代码必须执行结束之后,下一行代码才能执行,不能跳行执行,还记得吗?现在再和栈数据结构一起联系起来思考一下,为什么要采用栈数据结构呢?是不是只有采用这种先进后出的栈数据结构才可以保证代码的执行顺序呢!
查看下列代码,分析是如何执行的
/*
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;
}
}
无返回值的方法调用方式
无返回值方法的调用只能直接调用。
public class Demo02 {
public static void main(String[] args) {
print(); //直接调用
}
public static void print(){
for (int i = 0; i < 3; i++) {
System.out.println("hello");
}
}
}
总结:
- 方法没有被调用的时候,在方法区中的字节码文件中存放
- 方法被调用的时候,需要进入到栈内存中运行
方法的参数传递机制
在传输实参给方法的形参的时候,并不是传输实参变量本身, 而是传输实参变量中存储的值,这就是值传递。
传递基本数据类型:形参值的改变不会影响实参。基本类型的参数传输存储的数据值。。
示例代码
public class ChangeValue {
public static void main(String[] args) {
int a = 1;
int b = 2;
System.out.println("a的值:" + a + " b的值:" + b); // a的值:1 b的值:2
change(a, b);
System.out.println("a的值:" + a + " b的值:" + b);// a的值:1 b的值:2
}
private static void change(int a, int b) {
int temp = a;
a = b;
b = temp;
System.out.println("a的值:" + a + " b的值:" + b);// a的值:2 b的值:1
}
}
传递引用数据类型:方法的形参是引用数据类型时,形参地址值的改变不会影响实参,但是形参地址值里面的数据的改变会影响实参,例如,修改数组元素的值,或修改对象的属性值。引用类型的参数传输存储的地址值。
示例代码
class Student {
public int age;
}
public class ChangeValue2 {
public static void main(String[] args) {
Student student = new Student();
student.age = 18;
System.out.println(student.age); // 18
changeValue(student);
System.out.println(student.age); // 28
}
private static void changeValue(Student student) {
student.age = 28;
System.out.println(student.age);// 28
}
}
注意事项:
在方法中,形参 = 新new对象,那么就和实参无关了
/*
陷阱1:在方法中,形参 = 新new对象,那么就和实参无关了
*/
class MyData {
int num;
}
class Test {
public static void change(MyData my) {
my = new MyData();//形参指向了新对象
my.num *= 2;
System.out.println(my.num);//0
}
public static void main(String[] args) {
MyData m = new MyData();
m.num = 1;
System.out.println(m.num);//1
change(m);//调用完之后,m对象的num属性值仍然为1
System.out.println(m.num);//1
}
}
注意:String、Integer等特殊类型容易错。
class StringUtil{
public void change(String str){
str += "你好";//String对象不可变,一旦修改就会产生新对象
System.out.println(str);//尚你好
}
}
public class Test {
public static void main(String[] args) {
StringUtil util = new StringUtil();
String str = "尚";
System.out.println(str); //尚
util.change(str);
System.out.println(str); //尚
}
}
可变参数
在JDK1.5之后,如果我们定义一个方法时,此时某个形参的类型可以确定,但是形参的个数不确定,那么我们可以使用可变参数。
代码示例
public class Demo01 {
public static void main(String[] args) {
//求1-n个整数中的最大值
int max = test(1,2,-3,45,3,4);
System.out.println("max = " + max); //max = 45
}
private static int test(int... i) {
int max = i[0];
for (int j : i) {
if (j > max){
max = j;
}
}
return max;
}
}
注意事项:
- 一个方法最多只能有一个可变参数
- 如果一个方法包含可变参数,那么可变参数必须是形参列表的最后一个
可变形参的本质就是数组,如下所示:
只是后面这种定义,在调用时必须传递数组,而前者更灵活,既可以传递数组,又可以直接传递数组的元素,这样更灵活了。
下面2个方法编译器认为是一个方法,编译报错
private static void sum(int... i) {
}
private static void sum(int[] i) {
}
下面2个方法本质是相同的,调用的时候jvm不能明确调用哪个,也会报错
private static void sum(int... i) {
}
private static void sum(int i, int ... a) {
}
方法重载
方法重载:指在同一个类中,允许存在一个以上的同名方法,只要它们的参数列表不同即可,与修饰符和返回值类型无关。
参数列表不同是指
-
数据类型个数不同,数据类型不同,数据类型顺序不同。
重载方法调用
-
JVM通过方法的参数列表,调用不同的方法。
代码示例
public class Demo{
//求两个整数的最大值
public int max(int a, int b) {
return a > b ? a : b;
}
//求三个整数的最大值
public int max(int a, int b, int c) {
return max(max(a, b), c);
}
//求两个小数的最大值
public double max(double a, double b) {
return a > b ? a : b;
}
}
递归
递归:指在当前方法内调用自己的这种现象。
递归的分类:
- 递归分为两种,直接递归和间接递归。
- 直接递归称为方法自身调用自己。
- 间接递归可以A方法调用B方法,B方法调用C方法,C方法调用A方法。
注意事项:
- 递归一定要有条件限定,保证递归能够停止下来,否则会发生栈内存溢出。
- 在递归中虽然有限定条件,但是递归次数不能太多。否则也会发生栈内存溢出。
示例代码
//计算斐波那契数列(Fibonacci)的第n个值 ,
public class Demo06 {
public static void main(String[] args) {
int result= getResult(6);
System.out.println("result = " + result);
}
private static int getResult(int i) {
if (i == 1 || i == 2){
return 1;
}
//规律:一个数等于前两个数之和,
return getResult(i-1)+getResult(i-2);
}
}