Java 方法基础

Java 方法基础

方法

方法是一段封装特定逻辑的可重用代码块,用于解决“重复代码冗余”问题。例如通过方法封装“累加”逻辑,避免多次编写相同的 for 循环。

示例:累加逻辑的代码优化

优化前:重复累加代码

package com.method_array;

/**
 * @Author Jing61
 */
public class MethodDemo {
    public static void main(String[] args) { 
        int sum1 = 0;
        for(int i = 1; i <= 100; i++) {
            sum1 += i;
        }
        System.out.println(sum1);
        
        int sum2 = 0;
        for(int i = 10; i <= 100; i++){
            sum2 += i;
        }
        System.out.println(sum2);
        
        int sum3 = 0;
        for(int i = 50; i <= 100; i++){
            sum3 += i;
        }
        System.out.println(sum3);
    }
}

问题:三段 for 循环逻辑完全一致,仅起始和结束值不同,代码冗余。

优化后:封装为方法调用

package com.method_array;

/**
 * @Author Jing61
 */
public class MethodDemo {
    public static void main(String[] args) {
        // 调用 sum 方法,传入不同参数实现不同范围的累加
        int sum1 = sum(1, 100), sum2 = sum(10, 100), sum3 = sum(50, 100); 
        System.out.println(sum1);
        System.out.println(sum2);
        System.out.println(sum3);
    }
    
    /**
     * 累加方法:计算从 a 到 b 的所有整数的和
     * @param a 累加起始值
     * @param b 累加结束值
     * @return 累加结果
     */
    public static int sum(int a, int b) {
        int total = 0;
        for(int i = a; i <= b; i++) {
            total += i;
        }
        return total; // 返回累加结果
    }
}

说明ab形参(方法定义时的参数),调用时传入的 1/10010/100 等是实参(方法调用时的具体值)。

方法的声明与调用

方法声明语法

修饰符 返回值类型 方法名(参数列表) {
    // 方法体:具体业务逻辑
    return 返回值; // 若返回值类型为 void,无需 return(或写 return;)
}
  • 修饰符:常见为 public static(公共静态方法,可直接通过类名调用);
  • 返回值类型:方法执行后返回的数据类型(如 int/String),无返回值时用 void
  • 方法名:遵循“小驼峰命名法”(如 sumgetUserName);
  • 参数列表数据类型 参数名, 数据类型 参数名(可无参数,如 void printHello());
  • return:将结果返回给调用者,执行后方法立即结束。

方法调用方式

根据方法是否有返回值,调用方式分为两种:

有返回值的方法

用变量接收返回值,直接在输出、计算中使用等:

// 方式1:变量接收
int result = sum(1, 100);
System.out.println(result);

// 方式2:直接输出
System.out.println(sum(10, 100));

// 方式3:参与计算
int total = sum(1, 10) + sum(11, 20);

// 等等

无返回值的方法(返回值类型为 void)

直接调用,无需接收返回值:

// 示例:无返回值的打印方法
public static void printHello() {
    System.out.println("Hello, Java!");
}

// 调用
printHello();

方法的内存机制(栈实现)

Java 中方法的执行依赖栈内存,遵循“先进后出”原则:

  1. 程序启动时,main 方法先入栈,占据一块内存空间;
  2. 调用其他方法(如 sum)时,该方法入栈,在栈顶执行;
  3. 方法执行完毕后,出栈并释放内存,回到调用它的方法(如 main)继续执行。

示例流程(以 main 调用 sum(1,100) 为例):

  • main 入栈 → 调用 sum(1,100)sum 入栈 → sum 执行 → sum 出栈 → main 继续执行。

形参与实参的区别(值传递)

Java 方法传参遵循值传递原则:仅传递参数的“值副本”,而非参数的内存地址。方法内部修改形参,不会影响外部实参。

示例:值传递验证(交换变量)

package com.method_array;

/**
 * @Author Jing61
 */
public class PassByValue {
    /**
     * 尝试交换两个整数的方法(形参)
     * @param a 形参1
     * @param b 形参2
     */
    public static void exchange(int a, int b) {
        int temp = a;
        a = b;
        b = temp;
        // 方法内部:a=20,b=10
    }
    
    public static void main(String[] args) {
        int a = 10; // 实参1
        int b = 20; // 实参2
        System.out.println("交换前:a = " + a + ",b = " + b); // 10 20
        exchange(a, b);
        System.out.println("交换后:a = " + a + ",b = " + b); // 10 20,值未交换
    }
}

原因

  • 实参 a/bmain 栈帧中占据内存;
  • 调用 exchange 时,会在 exchange 栈帧中创建形参 a/b,并赋值为另一个内存空间;
  • 方法内部修改的是 exchange 中内存中 a/b,与 main 的内存无关,因此 maina/b 不变。
  • 传递参数只传递了值。

方法重载(Overload)

方法重载指“在同一个类中,方法名相同但参数列表不同”的多个方法,用于实现“同一功能的不同输入场景”(如生成随机字符,支持“任意字符”“指定范围字符”“数字字符”)。

方法重载的规则

  1. 方法名必须相同
  2. 参数列表必须不同(至少满足以下一项):
    • 参数数量不同(如 randomCharacter() vs randomCharacter(char begin, char end));
    • 参数类型不同(如 add(int a, int b) vs add(double a, double b));
    • 参数顺序不同(如 show(int a, String b) vs show(String a, int b));
  3. 返回值类型可不同(不是区分重载的依据);
  4. 访问修饰符可不同
  5. 异常声明可不同

示例:随机字符生成的重载方法

package com.method_array;

/**
 * @Author Jing61
 */
public class MethodOverload {
    /**
     * 重载1:随机返回一个任意Unicode字符(无参数)
     * @return 随机字符
     */
    public static char randomCharacter() {
        // 调用重载方法,传入Unicode最小值和最大值
        return randomCharacter('\u0000', '\uffff'); 
    }
    
    /**
     * 重载2:随机返回指定范围内的字符(2个char参数)
     * @param begin 起始字符
     * @param end 结束字符
     * @return 随机字符
     */
    public static char randomCharacter(char begin, char end) {
        // 生成 [begin, end] 范围内的随机字符
        return (char)(Math.random() * (end - begin + 1) + begin);
    }
    
    /**
     * 重载3:随机返回0-9的数字字符(无参数,功能特定)
     * @return 随机数字字符
     */
    public static char randomDigit() {
        // 调用重载方法,传入数字字符的范围
        return randomCharacter('0', '9');
    }
    
    public static void main(String[] args) {
        System.out.println(randomCharacter());       // 任意字符
        System.out.println(randomCharacter('a', 'z'));// 小写字母
        System.out.println(randomDigit());           // 数字字符
        System.out.println(randomCharacter('A', 'Z'));// 大写字母
    }
}

说明:前个方法名均为 randomCharacter,第三个方法名为 randomDigit,调用重载方法,但参数列表不同,属于合法重载。

模块化编程

模块化编程是将复杂任务分解为多个“独立、功能单一”的模块(方法),每个模块仅负责一个特定功能,最终通过模块组合实现整体需求。

模块化编程的好处

  1. 可维护性:单个模块出错时,仅需定位和修改该模块,不影响其他部分;
  2. 可测试性:每个模块可单独测试,确保功能正确性;
  3. 可读性:模块功能单一,代码逻辑清晰,便于理解;
  4. 可重用性:模块可在不同场景中重复调用(如 isPrime 方法可用于任何素数判断场景)。

示例:打印指定数量的素数(模块化实现)

将功能拆分为“判断素数”和“打印素数”两个模块:

package com.method_array;

/**
 * @Author Jing61
 */
public class PrimeNumber {

    /**
     * 模块1:判断一个数是否为素数(功能:素数校验)
     * @param number 需要判断的数字
     * @return true=素数,false=非素数
     */
    public static boolean isPrime(int number) {
        if (number <= 1) return false; // 1及以下不是素数
        // 优化:只需判断到 sqrt(number)(减少循环次数)
        for (int i = 2; i <= Math.sqrt(number); i++) {
            if (number % i == 0) {
                return false; // 能被整除,非素数
            }
        }
        return true; // 无法被整除,是素数
    }

    /**
     * 模块2:打印指定数量的素数(功能:素数输出)
     * @param count 需要打印的素数个数
     */
    public static void printPrime(int count) {
        int primeCount = 0; // 已找到的素数个数
        int number = 2;     // 从2开始判断(最小的素数)
        
        while (primeCount < count) {
            if (isPrime(number)) { // 调用模块1判断素数
                System.out.println(number);
                primeCount++;
            }
            number++;
        }
    }

    public static void main(String[] args) {
        printPrime(10); // 调用模块2,打印10个素数
    }
}

自顶向下逐步求精

自顶向下逐步求精是一种编程思想:将大问题拆解为多个子问题,子问题再拆解为更小的问题,直到每个问题都能通过简单方法实现。核心是“先设计整体结构,再实现细节”,同时通过封装隐藏方法实现细节(用户无需知道方法内部逻辑,仅需关注“如何调用”)。

核心思想

  • 分离使用与实现:调用者只需知道方法的“输入(参数)”和“输出(返回值)”,无需关心内部代码;
  • 分治策略:大问题拆解为子问题,每个子问题对应一个方法;
  • 封装性:修改方法实现时,只要不改变“方法签名”(方法名+参数列表+返回值类型),调用者代码无需修改(如 System.out.println(),用户无需知道其实现,仅需调用)。

示例:打印指定年月的日历(自顶向下实现)

问题拆解

需实现“用户输入年月 → 打印该月日历”,拆解为以下子问题(每个子问题对应一个方法):

先把问题分解成两个子问题:读取用户输入和打印该月的日历。打印给定月份的日历问题可以分解成两个子问题:打印日历的标题和日历的主体。月历的标题由三部分组成:年月,虚线,每周7天的星期名称。需要通过表示月份的数字(例如:1)来确定该月的全称(例如:January),这个步骤可以抽取为getMonthName来完成。为了打印日历主体,需要知道这个月的第一天是星期几(抽取为getStartDay),以及该月有多少天(抽取为getNumberOfDaysInMonth)。计算一个月的第一天是星期几:假设知道1800年1月1日是星期三(START_DAY_FOR_JAN_1_1800 = 3),然后计算1800-01-01和日历月份的第一天之间相差的总天数(totalNumberOfDays)。因为每个星期有7天,所以日历月份第一天的星期就是(totalNumberOfDays + START_DAY_FOR_JAN_1_1800 ) % 7。这样getStartDay问题可以进一步细化为getTotalNumberOfDays。要计算总天数,需要知道该年是否为闰年以及每个月的天数,所以getTotalNumberOfDays可以继续细化成两个子问题:isLeapYear和getNumberOfDaysInMonth。

结构如下:

flowchart TD A[printCalendar<br/>主程序] --> B[readInput<br/>读取输入] B --> C[printMonth<br/>打印月份] C --> D[printMonthTitle<br/>打印月份标题] D --> E[getMonthName<br/>获取月份名称] C --> F[printMonthBody<br/>打印月份内容] F --> G[getStartDay<br/>获取开始星期] G --> H[getTotalNumberOfDays<br/>计算总天数] H --> I[getNumberOfDaysInMonth<br/>获取月份天数] I --> J[isLeapYear<br/>判断闰年] H --> J F --> I

完整代码实现

package com.method_array;

import java.util.Scanner;

/**
 * @Author Jing61
 * 功能:打印指定年月的日历
 */
public class PrintCalendar {
    public static void main(String[] args) {
        // 子问题1:读取用户输入(年和月)
        System.out.println("输入年份(例如2018):");
        Scanner input = new Scanner(System.in);
        int year = input.nextInt();
        System.out.println("输入月份(1——12):");
        int month = input.nextInt();
        input.close();

        // 子问题2:打印该月日历(调用核心方法)
        printMonth(year, month);
    }

    /**
     * 子问题2.1:打印月历(整合标题和主体)
     * @param year 年份
     * @param month 月份
     */
    public static void printMonth(int year, int month) {
        printMonthTitle(year, month); // 打印标题
        printMonthBody(year, month);  // 打印主体
    }

    /**
     * 子问题2.1.1:打印日历标题(月份名称+年份、星期缩写)
     * @param year 年份
     * @param month 月份
     */
    public static void printMonthTitle(int year, int month) {
        String monthName = getMonthName(month); // 获取月份英文名称
        System.out.println("         " + monthName + "  " + year);
        System.out.println("--------------------------------");
        System.out.println(" Sun Mon Tue Wed Thu Fri Sat "); // 星期标题
    }

    /**
     * 子问题2.1.2:打印日历主体(日期排列)
     * @param year 年份
     * @param month 月份
     */
    public static void printMonthBody(int year, int month) {
        int startDay = getStartDay(year, month); // 该月第一天是星期几(0=周日,6=周六)
        int numberOfDaysInMonth = getNumberOfDaysInMonth(year, month); // 该月总天数

        // 步骤1:打印第一天前的空格(例如第一天是周三,前面空3个位置)
        int i = 0;
        for (i = 0; i < startDay; i++) {
            System.out.print("    "); // 每个位置占4个字符(对齐)
        }

        // 步骤2:打印日期,每周换行
        for (i = 1; i <= numberOfDaysInMonth; i++) {
            System.out.printf("%4d", i); // 格式化输出(占4个字符)
            // 若当前日期是周六((i + startDay) % 7 == 0),换行
            if ((i + startDay) % 7 == 0) {
                System.out.println();
            }
        }
        System.out.println(); // 最后一行补换行
    }

    /**
     * 子问题3:将数字月份转为英文名称
     * @param month 数字月份(1-12)
     * @return 英文月份名称
     */
    public static String getMonthName(int month) {
        String monthName = "";
        switch (month) {
            case 1:  monthName = "January";   break;
            case 2:  monthName = "February";  break;
            case 3:  monthName = "March";     break;
            case 4:  monthName = "April";     break;
            case 5:  monthName = "May";       break;
            case 6:  monthName = "June";      break;
            case 7:  monthName = "July";      break;
            case 8:  monthName = "August";    break;
            case 9:  monthName = "September"; break;
            case 10: monthName = "October";   break;
            case 11: monthName = "November";  break;
            case 12: monthName = "December";  break;
        }
        return monthName;
    }

    /**
     * 子问题4:获取该月第一天是星期几(0=周日,6=周六)
     * @param year 年份
     * @param month 月份
     * @return 星期几(0-6)
     */
    public static int getStartDay(int year, int month) {
        final int START_DAY_FOR_JAN_1_1800 = 3; // 1800年1月1日是周三(3)
        // 计算1800年1月1日到目标年月的总天数
        int totalNumberOfDays = getTotalNumberOfDays(year, month);
        // 总天数 + 起始星期 → 取余7,得到目标月第一天的星期
        return (totalNumberOfDays + START_DAY_FOR_JAN_1_1800) % 7;
    }

    /**
     * 子问题4.1:计算1800年1月1日到目标年月的总天数
     * @param year 年份
     * @param month 月份
     * @return 总天数
     */
    public static int getTotalNumberOfDays(int year, int month) {
        int total = 0;

        // 步骤1:计算1800年到“目标年的前一年”的总天数
        for (int i = 1800; i < year; i++) {
            total += isLeapYear(i) ? 366 : 365; // 闰年366天,平年365天
        }

        // 步骤2:计算“目标年的1月”到“目标月的前一个月”的总天数
        for (int i = 1; i < month; i++) {
            total += getNumberOfDaysInMonth(year, i);
        }

        return total;
    }

    /**
     * 子问题5:判断年份是否为闰年
     * @param year 年份
     * @return true=闰年,false=平年
     */
    public static boolean isLeapYear(int year) {
        // 闰年规则:能被4整除但不能被100整除,或能被400整除
        return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
    }

    /**
     * 子问题6:获取指定月份的总天数
     * @param year 年份(判断2月需用到)
     * @param month 月份
     * @return 月份总天数(-1表示非法月份)
     */
    public static int getNumberOfDaysInMonth(int year, int month) {
        // 使用switch表达式(Java 14+),简洁返回天数
        return switch (month) {
            case 1, 3, 5, 7, 8, 10, 12 -> 31; // 31天的月份
            case 4, 6, 9, 11 -> 30;            // 30天的月份
            case 2 -> isLeapYear(year) ? 29 : 28; // 2月:闰年29天,平年28天
            default -> -1; // 非法月份(1-12外)
        };
    }
}

测试效果

输入 2024(年)和 2(月),输出结果:

输入年份(例如2018):
2024
输入月份(1——12):
2
         February  2024
--------------------------------
 Sun Mon Tue Wed Thu Fri Sat 
                   1   2   3 
  4   5   6   7   8   9  10 
 11  12  13  14  15  16  17 
 18  19  20  21  22  23  24 
 25  26  27  28  29 
posted @ 2025-10-30 11:11  Jing61  阅读(6)  评论(0)    收藏  举报