这是我参与「第五届青训营 」伴学笔记创作活动的第 9 天
方法
在 Java 中,方法是程序执行的一个最小单元。
方法的格式
[权限修饰符] [其他修饰符] 返回值类型 方法名([参数列表]) {
/* 方法体内容 */
return [返回值];
}
权限修饰符:确定这个方法的访问范围,public private protected 和默认访问权限
其他修饰符:static 标识这个方法是静态方法; final 标识这个方法不可以被重载
返回值类型:方法可以有返回值,那么返回值类型需要标注清楚;也可以没有那么返回值就是 void。
方法名:方法的方法名需要按照驼峰命名法则来完成对方法的命名
参数列表:方法可以是有参数的那么方法列表需要写清楚形参,如果没有参数,那么直接留空就可以。
注意事项
- 方法内不能定义一个方法,方法和方法之间是平级的
- 方法遇到
return语句,方法就会返回值,方法也会执行结束
方法参数
方法参数知识比较多,主要分为两个方面:
- 形参和实参
- 隐式参数和显式参数
形参和实参
形参(parameter):声明方法 / 函数的时候,在方法上描述的参数。这个参数只有在方法调用时,才会在栈上分配相应的空间。
实参(argument):在调用方法 / 函数的时候,传递的变量,表达式。实参在方法执行之前,必须有一个确定的值,然后将值复制到形参上,然后才能执行对应的方法 / 函数。
本质上形参和实参并不是同一块内存的数据,所以,形参变量在方法中的修改,并不会影响实参变量的实际数值。
void changeArgumentValue(int x, int y) {
x = 10; y = 20;
}
// 调用代码
int x = 1, y = 2;
changeArgumentValue(x, y);
// 这里 x 还是 1,y 还是 2。他们变量的值不会发生改变
当然,类似于C++这样的语言支持引用传递,这样就可以在方法 / 函数中传递实参的引用,从而在方法中修改形参和实参的数据。
void change_argument_value(int& x, int& y) {
x = 10; y = 20
}
// 调用代码
int x = 1, y = 2;
change_argument_value(x, y);
// 这里 x == 10, y == 20。他们变量的值发生改变
按值调用
方法的调用总的来说可以分为:
- 按值调用(call by value):方法接受的是调用者提供的值
- 按引用调用(call by reference):方法接受的是调用者提供的引用
Java的方法调用总是按值调用(call by value)。方法得到的参数值永远都是实参的一个副本(或者说是和实参值相同的两个内存区域)。
关于这个例子可以参考:形参和实参。
可以总结一下几个点(以下的参数军代表实参(argument)):
- 方法不能修改基本类型参数
- 方法不能让对象参数引用一个新的对象
- 方法可以修改对象参数的状态
隐式参数和显式参数
str.indexOf("hello");
隐式(implicit)参数:在上面这个例子中,隐式参数就是 str 。隐式参数也被叫做 目标 或者 接收者。
显式(explicit)参数:显式参数就是方法括号中的参数。
this 关键字显然就在调用的方法中,起到了一个指示隐式参数的一个作用。例如,上面遮蔽的例子中,使用了 this 关键字来区分局部变量和全局变量,本质上是使用本对象的成员变量。
方法重载
方法的名称相同,参数不同那么就视为不同的方法,这种现象就称为重载(reloading)。同时,调用方法的时候,编译器会根据方法的参数查找匹配对应的方法,如果找不到就会发生编译时错误,查找和匹配方法的过程就叫做重载解析(reloading resolution)。
// 下面的案例就是方法重载的典型
public int indexOf(int ch);
public int indexOf(int ch, int fromIndex);
public int indexOf(String str);
public int indexOf(String str, int fromIndex);
// 下面也是方法重载的典型案例但是 *不建议*
public int compare(int i, double d);
public int compare(double d, int i);
// 下面两个方法不是方法重载
// public int compare(int i, int j);
// public double compare(int i, int j);
方法重载在Java中式普遍存在的,不管是静态方法还是构造方法也都存在这样的功能。
方法签名(method signature) 如果要完整描述一个方法,需要方法名以及参数类型来描述。
上面例子中第三个,不算方法重载,但是返回值不同也可以说明两个方法不一样,这是为啥呢?返回值不属于方法签名中的一员,因此不算重载。
静态方法
静态方法就是不在对象上执行的方法,下面就是一个构造方法的例子。
public class Math {
public static double pow(double x, double a) {
double res = 0;
/* 计算过程 */
return res;
}
}
// 调用
Math.pow(a, b);
满足下面两种情况就可以使用静态方法:
下面展示一个例子,null 值的对象变量依然可以调用。
Math m = null;
m.pow(a, b); // 这条语句在编译时会完成静态绑定
Math.pow(a, b); // 替换为类似于这句形式的字节码
上面这个代码看似会发生空指针异常,但是并不会发生,因为按照方法的调用过程,编译器会匹配静态方法,完成静态绑定。虽然不会发生异常,但是编码过程中还是非常不建议这样编写。
构造方法
构造方法,又叫做构造器(constructor),就是用来为成员进行初始化的一个成员方法。
public class 类 {
修饰符 类(参数列表) { /* 方法内容 */ }
}
构造方法特点
- 方法名必须与类名完全一致
- 构造方法没有声明返回值
- 构造方法的参数列表可以是空参或者多个参数
- 构造方法总是伴随
new操作符一起使用
构造方法调用时机
- 构造方法并不需要手动调用,而是由虚拟机自动调用
- 每次创建对象都会调用构造方法
注意事项:
- 对于一个类如果没有定义任何构造方法,默认生成无参构造方法
- 如果一旦声明了构造方法,那么这个类也就不会自动生成无参的构造方法了
- 构造方法不是用来生成对象,本质上是用来初始化类
构造方法调用过程
- 如果构造方法第一行调用了其他构造方法,那么就会按照参数调用其他构造方法
- 否则,
- 所有数据初始化为默认值
- 按照类中声明的顺序执行,字段初始化方法和初始化块
- 执行构造方法主体
public class ConstructorTest {
{ System.out.println("初始化块1"); }
{ System.out.println("初始化块2"); }
static { System.out.println("静态代码块"); }
public ConstructorTest() {
this(123);
System.out.println("无参构造方法");
}
public ConstructorTest(int a) {
System.out.println("有参构造方法");
}
public static void main(String[] args) {
new ConstructorTest();
}
}
// Output:
// 静态代码块
// 初始化块1
// 初始化块2
// 有参构造方法
// 无参构造方法
理解方法的调用过程
虚拟机调用方法也是一个很有趣的话题,具体的执行流程如下:
- 编译器查看对象的声明类型和方法名
- 编译器确定方法调用中提供的参数类型,详见重载解析。
- 如果是
private、static、final方法或者构造方法,那么编译器可以准确的知道应该调用那个方法。这称为静态绑定(static binding)。如果这方法的执行依赖于隐式参数的实际类型,那么这种就必须在运行时使用动态绑定(dynamic binding)。 - 否则,程序运行采用动态绑定的调用方法,虚拟机必须调用与所引用对象的实际类型对应的那个方法。
在动态绑定的过程中最直接的办法就是按照继承链寻找匹配的方法,这样的动态绑定的方法增加了系统开销,虚拟机会自动为类计算了一个方法表(method table)。那么使用方法表解析过程为:
- 虚拟机获取对象实际类型的方法表
- 虚拟机查找定义方法签名的类
- 虚拟机调用这个方法
invokevirtual/invokeinterface
Person:
sleep() -> Person.sleep()
speak() -> Person.speak()
Student:
sleep() -> Person.sleep()
speak() -> Student.speak()
Person p = new Student();
p.speak();
1. 虚拟机获取到 p 变量的实际类型 Student
2. 虚拟机查询定义 speak() 的类。这里 Student 重写了这个方法,因此这个类也就是 Student,此时虚拟机已经知道了应该调用那个方法。
3. 虚拟机调用这个方法(invokevirtual)