1、方法的声明
- 语法:
【修饰符】返回值类型方法名(多个形参声名) {
// 0~N 条可执行的语句。
// 如果方法签名中有返回值类型声名,该方法里必须包含有效的 return 语句。
}
- 【修饰符】 : 可以省略。
- 可以public | protected | private,static,final | abstract,native,synchronized。
- 但是 final | abstract 不能同时出现。
- 返回值类型 : 绝对不能省略。
- 可以是基本数据类型、数组,任意类、接口、枚举、void。
- void 用于声明该方法没有返回值。
- 方法名 : 从语法角度来讲,只要是标识符即可。
- 必须是多个有意义的单词连缀,第一个单词的首字母小写,后边的每个单词首字母大写
- 形参声明 : 每个形参都满足“类型变量”的格式。
- 多个形参声名之间都用英文逗号隔开。
- 方法体 : 方法体中可执行性的代码。代码的执行是从上到下的。
2、形参 vs 实参
- 方法定义时的参数称为 形式参数,需要数据类型
- 方法调用时的参数称为 实际参数,不需要类型,只需要值
3、隐式参数 vs 显式参数
特性 | 隐式参数 | 显式参数 |
---|---|---|
传递方式 | 自动传递(JVM 处理) | 手动传递(调用时显式赋值) |
核心代表 | this(指向当前对象) | 方法签名中定义的参数(如:int a) |
作用域 | 实例方法内部 | 方法内部(局部变量) |
存在场景 | 仅实例方法 | 所有方法(包括:静态方法) |
典型用途 | 访问当前对象的成员 | 接收外部输入数据 |
1、隐式参数(Implicit Parameter)
- 把方法调用的目标或接收者,称为隐式参数。
* 在方法调用时自动传递的参数,无需在方法声明中显式定义。
- 核心代表:this 关键字,指向当前对象的引用(实例方法的调用者)。
- 局部变量表 的第一位就是用来存储当前对象的引用。
- 特点:
- 自动传递:实例方法调用时,隐式参数 this 由 JVM 自动传递。
- 作用域:仅在实例方法中有效,静态方法没有隐式参数。
- 用途:访问当前对象的成员变量或方法(避免命名冲突)。
public class Person {
private String name;
public void setName(String name) {
// 隐式参数 this 指向调用对象。
// 即:调用 setName 方法的 person 对象。
this.name = name;
}
}
// 使用
Person person = new Person();
person.setName("Alice"); // 隐式参数 this = person
2、显式参数(Explicit Parameter)
- 指在方法声明时显式定义的参数,调用时必须手动传递具体值。
- 核心形式:方法签名中列出的参数(如:(String name, int age))。
- 特点:
- 手动传递:调用方法时需显式传入参数值。
- 作用域:仅在方法内部有效,属于局部变量。
- 用途:接收外部输入,控制方法行为。
public class Calculator {
public int add(int a, int b) { // 显式参数 a 和 b
return a + b;
}
}
// 使用
Calculator calc = new Calculator();
int result = calc.add(3, 5); // 显式参数 a=3, b=5
4、可变参数
- 定义形参时,用法 type… 形参名。
- 本质是数组。
package org.rainlotus.materials.javabase.a04_oop.method.aboutmain;
public class 可变参数的本质是数组 {
// public static void main(String[] args) {
// 将 public static void main(String[] args) 替换成
// public static void main(String... args) 不影响执行。
public static void main(String... args) {
// 输出 true 。
System.out.println(args instanceof String[]);
}
}
- 注意点:
- 每个方法最多 只能有一个可变参数(本质相当于一个数组)。
- 可变参数必须是参数列表中的最后一个。
5、更改器方法 vs 访问器方法
- 更改器方法(mutator method):
- 调用这种方法后,对象的状态会发生改变(类的属性值发生改变)。
- 如:贫血模式 中的类中的 setter 方法。
- 访问器方法(accessor method):
- 只访问对象属性值而不修改对象属性值的方法。
- 如:贫血模式 中的类中的 getter 方法。
- getter 方法只返回实例字段的值,因此,又被称为字段访问器 (field accessor)。
6、方法重载
- 归纳:二同、一不同。
- 二同:同一个类,方法名相同。
- 一个不同:形参列表不同。
- 要完整地描述一个方法,需要指定方法名以及参数类型,这叫作 方法的签名(signature)。
- 返回类型不是方法签名的一部分。
- 即:不能有两个名字相同、参数类型也相同却有不同返回类型的方法。
- 注意点:
- 1、方法重载和【返回值类型】没有任何关系
- 2、方法重载和【是否有 static 】没有任何关系。
- 2、方法重载和【是否有 private 】没有任何关系。
- 2、方法重载和【是否有 final 】没有任何关系。
- 怎么才能唯一确定所调用的方法?
- A、主调者。 // 谁调用方法。
- B、方法名字。 // 调用哪个方法。
- C、匹配实参所对应的类型。 // 重名方法中的一个。
7、方法重写 (override) - - 父子类之间
- 归纳:两同,两小,一个大。
- 两同:方法名相同,参数列表相同。
- 两小:子类重写的方法的返回值类型必须比父类方法的 返回值类型更小,或者相同。
子类重写的方法抛出的异常必须比父类抛出的异常小,或者相同。- 一大:子类重写的方法的访问权限必须比父类的访问权限大,或者相同。
- @Override:是让编译器执行更严格的检查,要求被修改的方法必须重写父类的方法。
- 注意:父类的 private 方法、static 方法、final 方法 不能被重写。
- 子类中可以出现重写父类 private 方法 写法。但 private 方法 没有被重写 而是被隐藏。
- 因为,在子类的 private 方法 上,添加 @Override 注解时,会编译报错,
- 子类中可以出现重写父类 static 方法 写法。但是,static 方法 没有被重写 而是被隐藏。
- 因为,在子类的 static 方法 上,添加 @Override 注解时,会编译报错。
- 子类中不可出现重写父类 final 方法 写法。
- 否则,子类的 final 方法 会出现编译报错。
class Super{
private void privateMethod(){
System.out.println("父类的 private method");
}
static void staticMethod(){
System.out.println("父类的 static method");
}
public final void finalMethod(){
System.out.println("父类的 final method");
}
void method(){
System.out.println("父类的普通 method");
}
}
class Son extends Super{
// @Override //编译报错提示:Method does not override method from its superclass
private void privateMethod(){
System.out.println("子类的 private method");
}
// @Override 编译报错提示:Static methods cannot be annotated with @Override
static void staticMethod(){
System.out.println("子类的 static method");
}
// 编译报错 :
// 'finalMethod()' cannot override 'finalMethod()' in 'org.rainlotus.materials.javabase.a04_oop.method.Super';
// overridden method is final
/*public final void finalMethod(){
System.out.println("子类的 final method");
}*/
@Override
void method(){
System.out.println("子类的普通 method");
}
public static void main(String[] args) {
Super superObject = new Son();
// 输出:父类的 final method
superObject.finalMethod();
// 输出:子类的普通 method
superObject.method();
// 虽然提示 不应该通过类实例访问静态成员 的错误报错,但是能编译运行。
// 输出:父类的 static method
superObject.staticMethod();
}
}
8、递归
它里面包含一个隐式的循环。递归就是在方法中调用自己。
- 递归,一定要保证:在合适的时候结束调用自身。
- 递归一定要向“结束”的那一端递归。
递归头:什么时候不调用自己方法。
* 即:递归的结束条件。
- 作用:防止递归调用无限进行下去。
递归体:什么时候需要调用自己方法。
* 即:当满足某个条件时进行自己调用自己的部分。
* 递归体通常包含递归调用和递归头之外的逻辑。
- 作用:将问题分解为更小的子问题,通过不断调用自身来逐步解决问题。
- 递归的优点:将问题逐渐简单化
- 递归的缺点:会占用大量的系统堆栈,内存耗用多,在递归调用层次多时速度 比循环慢的多。
9、Java 中的 main 方法的作用
- public – main 方法是启动的时候由 JVM 进行加载的。
public 的可访问权限是最高的,所以需要声明为 public;
static – 方法的调用要么是通过对象,要么是通过类。
- main 方法,是**由虚拟机调用**的。所以,无需生成对象,那么声明为 static 即可;main – 至于为什么方法名称叫 main,猜想应该是参考的是 C 语言的方法名。
void – main 方法退出时,并没有需要有相关返回值需要返回,所以是 void;
String[] – 此字符串数组用来运行时接受用户输入的参数;
+ 因为字符串在 Java 中是具有通用普遍性的,所以使用字符串是最优选择;
+ 数组的话,因为我们的参数不止一个,所以数组肯定是合适的;main 方法的作用:
- 程序的入口
- 保证程序的独立运行
- 被 JVM 调用
10、main 方法为什么是静态的?main 方法能同步吗?
- main() 方法一定是静态的。
- 如果 main() 允许是非静态的,那么在调用 main 方法时,JVM 就得实例化它的类。
- 在实例化时,还得调用类的构造函数。如果这个类的构造函数有参数,那么就会出现歧义。
- 是的,main 方法可以在 Java 中同步,synchronized 修饰符允许用于 main 方法的声明中,这样就可以在 Java 中同步 main 方法了。
// 该方法可以正常运行。
public synchronized static void main(String[] args) {
System.out.println("同步 main 方法");
}
11、不用 main 方法如何运行一个类?
- 在 Java7 之前的版本下,可以用 静态代码块 来实现。
class A {
/* public static void main(String[] args) {
System.out.println("主类 Main 方法");
} */
static {
System.out.println("静态初始化块");
System.exit(0);
}
}
- 在 Java7 之后的版本不可以,会提示如下错误:
12、怎么向 main 方法传递参数?
- java 类名 参数1 参数2
- 示例:
- 传递了 三个参数。
package org.rainlotus.materials.javabase.a04_oop.method.aboutmain;
/**
* 在 ~/IdeaProjects/rain-lotus/materials-javabase/target/classes 目录下,执行:
*
* java org.rainlotus.materials.javabase.a04_oop.method.aboutmain.MainMethodCommandLineArgs -g cruel world
* 最终输出:Goodbye, cruel world!
*
* java org.rainlotus.materials.javabase.a04_oop.method.aboutmain.MainMethodCommandLineArgs -h cruel world
* 最终输出:Hello, cruel world!
*
* @author zhangxw
*/
public class MainMethodCommandLineArgs {
public static void main(String[] args) {
if (args.length == 0 || args [0] .equals ("-h")) {
System.out.print("Hello,");
} else if (args [0] .equals ("-g")) {
System.out.print("Goodbye,");
}
// print the other command-line arguments
for (int i= 1; i< args.length; i++) {
System.out.print(" " + args[i]);
}
System.out.println("!");
}
}
14、静态方法可以直接调用非静态方法吗?
- 不可以。
- 1、静态成员变量:
- 在加载类时,才会去调用 类构造器,完成初始化(为静态成员变量赋值)。
- 2、实例成员变量:
- 在创建实例对象时,才会去调用 实例构造器,完成初始化(为实例成员变量赋值)。
- 3、如果在静态方法中直接调用非静态方法,就可能造成访问到未初始化的实例成员变量。
- 因此,编译器会提示报错。
15、方法调用的理解
- 准确地理解如何在对象上应用方法调用非常重要。
- 假设要调用 x.f(args) ,隐式参数 x 声明为类 C 的一个对象。
1、 下面是调用过程的详细描述:
- 1、编译器查看对象的声明类型和方法名。
- 注意:可能存在多个名字为 f 但参数类型不一样的方法。
- 如:可能存在方法 f(int) 和方法 f(String)。
- 编译器将会一一列举 C 类中所有名为 f 的方法和其超类中所有名为 f 而且可访问的方法。
- 如:超类的 private 方法不可被子类访问。
- 至此,编译器已知所有可能要调用的候选方法。
- 2、编译器要确定方法调用中提供的参数类型。
- 如果在所有名为 f 的方法中,存在一个与所提供参数类型完全匹配的方法,就选择这个方法。
- 这个过程称为 重载解析(overloading resolution)。
- 如:对于调用 x.f(“hello”),编译器将会挑选 f(String),而不是 f(int)。
- 由于允许类型转换 (int 可转换成 double,子类可转换成超类 等)。
- 所以,情况可能会变得很复杂。
- 如果编译器没有找到与参数类型匹配的方法 或 发现经过类型转换后有多个方法与之匹配。
- 编译器就会报告一个错误。
- 至此,编译器已经知道需要调用的方法的名字和参数类型。
- 注意:
- 方法的名字和参数列表称为 方法的签名 (signature)。
- 如:f(int) 和 f(String) 是两个有相同名字、不同签名的方法。
- 4、程序运行并且采用 动态绑定 调用方法时.
* 虚拟机必须调用与 x 所引用对象的实际类型对应的那个方法。
- 假设 x 的实际类型是 D,它是 C 类的子类。
- 如果 D 类定义了方法 f(String),就会调用这个方法;
- 否则,将在 D 类的超类中寻找 f(String) , 依此类推。
- 优化:
- 每次调用方法都要完成这个搜索,时间开销相当大。
- 因此,JVM 预先为每个类计算了一个 方法表(method table)。
- 方法表 中列出了所有方法的签名和要调用的实际方法。
- Jvm 加载一个类之后,可以构建这个方法表。
- 为此要结合它在类文件中找到的方法以及超类的方法。
- 这样一来,真正调用方法的时候,JVM 仅查找这个 方法表 就行了。
2、方法调用的示例:
- Jvm 为 Employee 和 Manager 类生成方法表。
- 在 Employee 的 方法表 中,列出了 Employee 类、Object 类 定义的所有方法:
(签名) (方法调用)
// 继承 Object 类的方法
toString() -----> Object.toString()
hashCode() -----> Object.hashCode()
equals(Object obj) -----> Object.hashCode(Object obj)
……
// Employee 类的方法
getName() -----> Employee.getName()
getSalary() -----> Employee.getSalary()
getHireDay() -----> Employee.getHireDay()
raiseSalary(double byPercent) -----> Employee.raiseSalary(double byPercent)
- 在 Manager 的 方法表 中,列出了Manager 类、 Employee 类、Object 类 定义的所有方法:
(签名) (方法调用)
// 继承 Object 类的方法
toString() -----> Object.toString()
hashCode() -----> Object.hashCode()
equals(Object obj) -----> Object.hashCode(Object obj)
……
// 继承 Employee 类的方法
getName() -----> Employee.getName()
getHireDay() -----> Employee.getHireDay()
raiseSalary(double byPercent) -----> Employee.raiseSalary(double byPercent)
// Manager 类的方法
// 重写了 Employee 的getSalary
getSalary() -----> Manager.getSalary()
// Manager 自定义的方法。
setBounus() -----> Manager.setBounus()
- 在运行时,调用 e.getSalary() 的解析过程为:
- 1、首先,JVM 获取 e 的实际类型的 方法表 。
- 可能是 Manager 类、 Employee 类 的 方法表,可能是 Manager 类 的其它子类的 方法表 。
- 2、然后,JVM 查找定义了 getSalary() 签名的类。此时,JVM 已经知道应该调用哪个方法。
- 3、最后,JVM 调用这个方法。
- 动态绑定 有一个非常重要的特性:无须修改现有的代码就可以对程序进行扩展。
- 假设增加一个新类 Executive, 并且变量 e 有可能引用这个类的对象。
- 此时,不需要对包含调用 e.getSalary() 的代码重新进行编译。
- 如果 e 恰好引用一个 Executive 类的对象,就会自动地调用 Executive.getSalary() 方法。