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; // 返回累加结果
}
}
说明:a 和 b 是形参(方法定义时的参数),调用时传入的 1/100、10/100 等是实参(方法调用时的具体值)。
方法的声明与调用
方法声明语法
修饰符 返回值类型 方法名(参数列表) {
// 方法体:具体业务逻辑
return 返回值; // 若返回值类型为 void,无需 return(或写 return;)
}
- 修饰符:常见为
public static(公共静态方法,可直接通过类名调用); - 返回值类型:方法执行后返回的数据类型(如
int/String),无返回值时用void; - 方法名:遵循“小驼峰命名法”(如
sum、getUserName); - 参数列表:
数据类型 参数名, 数据类型 参数名(可无参数,如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 中方法的执行依赖栈内存,遵循“先进后出”原则:
- 程序启动时,
main方法先入栈,占据一块内存空间; - 调用其他方法(如
sum)时,该方法入栈,在栈顶执行; - 方法执行完毕后,出栈并释放内存,回到调用它的方法(如
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/b在main栈帧中占据内存; - 调用
exchange时,会在exchange栈帧中创建形参a/b,并赋值为另一个内存空间; - 方法内部修改的是
exchange中内存中a/b,与main的内存无关,因此main中a/b不变。 - 传递参数只传递了值。
方法重载(Overload)
方法重载指“在同一个类中,方法名相同但参数列表不同”的多个方法,用于实现“同一功能的不同输入场景”(如生成随机字符,支持“任意字符”“指定范围字符”“数字字符”)。
方法重载的规则
- 方法名必须相同;
- 参数列表必须不同(至少满足以下一项):
- 参数数量不同(如
randomCharacter()vsrandomCharacter(char begin, char end)); - 参数类型不同(如
add(int a, int b)vsadd(double a, double b)); - 参数顺序不同(如
show(int a, String b)vsshow(String a, int b));
- 参数数量不同(如
- 返回值类型可不同(不是区分重载的依据);
- 访问修饰符可不同;
- 异常声明可不同。
示例:随机字符生成的重载方法
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,调用重载方法,但参数列表不同,属于合法重载。
模块化编程
模块化编程是将复杂任务分解为多个“独立、功能单一”的模块(方法),每个模块仅负责一个特定功能,最终通过模块组合实现整体需求。
模块化编程的好处
- 可维护性:单个模块出错时,仅需定位和修改该模块,不影响其他部分;
- 可测试性:每个模块可单独测试,确保功能正确性;
- 可读性:模块功能单一,代码逻辑清晰,便于理解;
- 可重用性:模块可在不同场景中重复调用(如
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。
结构如下:
完整代码实现
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

浙公网安备 33010602011771号