04、方法

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) 是两个有相同名字不同签名方法

  • 3、若是 private 方法、static 方法、final 方法、构造器编译器可准确地知道应该调用哪个方法
    * 这称为 静态绑定(static binding)。
    • 如果要调用的方法依赖于隐式参数实际类型。那么,就必须在运行时使用 动态绑定
      • 在示例中,编译器会利用 动态绑定 生成一个调用 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() 方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值