一、Java中的方法
1、方法的概念
在Java中,从面向对象的哲学来说,方法是对象所具有的行为。从方法本身来说,方法是用来完成某种特定功能的、相对独立的程序段,与其他语言中的子程序、函数等概念相当。方法一旦声明,可以在不同程序段中多次使用。
2、为什么要使用方法
通过使用方法,可以:
- 实现对象的行为,使程序符合面向对象的哲学;
- 使程序变的简单而清晰;
- 提高代码的重用性;
- 可以提高程序开发的效率;
- 有利于程序的维护。
二、方法的声明
Java中,声明一个方法的语法如下:
[修饰符] 返回值类型 方法名称([参数列表]){
// 方法体
}
说明:
- 声明方法的语句中包括方法头(方法声明)和方法体两部分。其中方法头(方法声明)确定方法的名称,形式参数的名称、类型和顺序,返回值的类型和方法的访问权限。方法体由括在花括号内的语句组成,这些语句实现方法的功能。
- 方法的修饰符是可选的,最常用的修饰符是public,表示方式是公开的。关于修饰符的详细内容将在以后的章节中介绍。
- 返回值类型反映方法完成其功能后返回的运算结果的数据类型。如果方法没有返回值,使用void关键字声明。
- 方法名称符合标识符命名规范,并遵守约定,使用动词或动宾短语,见名知意,符合驼峰式命名法。
- 方法的参数列表指定在调用该方法时,应该传递的参数的顺序、个数和数据类型。参数列表中可以包含若干个参数(没有、一个或多个),相邻的两个参数之间用逗号(,)隔开。
- 方法声明中的方法头(方法声明),对于调用方法的开发者来说,便可以认为是API,即应用程序编程接口。
观察下面的示例:
public class MathUtils {
/**
* 将两个整数相乘并打印结果
* @param num1
* @param num2
*/
public void multipAndPrint(int num1, int num2) {
int result = num1 * num2;
System.out.printf("%d x %d = %d",num1, num2, result);
}
/**
* 将两个整数相加并返回结果
* @param num1
* @param num2
* @return
*/
public int add(int num1, int num2) {
return num1 + num2;
}
}
说明:
- 该示例中声明了一个类,名叫
MathUtils
,该类中声明了两个方法。 multiplyAndPrint(int num1, int num2)
方法可以将两个整数相乘并打印结果。add(int num1, int num2)
方法可以将两个整数相加并返回结果,注意该方法中的return语句,对于有返回值的方法,方法体中通过return语句来返回值,详细内容将在后面的内容中介绍。
三、方法的调用
调用方法,即执行方法。发出调用的方法称为主调方法,被调用的方法称为被调方法。方法调用一般情况下由 对象.
操作符完成,格式语法如下:
对象.方法名([参数1,参数2,...,参数n])
说明:
- 参数的个数及数据类型应与被调方法的参数列表相对应;
- 当被调方法有返回值的时候,通常应在主调方法中定义变量并存储该返回值;
- 有些方法可以使用类名直接调用。
仔细观察下面的示例:
public class Test01 {
public static void main(String[] args) {
// 实例化一个MathUtils类的对象,并赋值给MathUtils类型的变量mu
MathUtils mathUtils = new MathUtils();
// 通过mathUtils调用multiplyAndPrint(int num1, int num2)方法
mathUtils.multipAndPrint(6, 8);
// 通过mu调用add(int num1, int num2)方法,并赋值给变量result
int result = mathUtils.add(7, 8);
// 打印result
System.out.println("\n" + result);
}
}
四、方法的返回值
方法的返回值即被调方法在调用后返回给主调方法的数据。
通常方法的设计应当遵循功能单一的原则,即一个方法只做一件简单而明确的事。
修改后的MathUtils
类源码:
public class MathUtils2 {
/**
* 将两数向乘并返回其结果
* @param num1
* @param num2
* @return 返回两数相乘的结果
*/
public int multiply(int num1, int num2) {
int result = num1 * num2;
return result;
}
/**
* 将两数相加并返回其结果
* @param num1
* @param num2
* @return 返回两数相加的结果
*/
public int add(int num1, int num2) {
int result = num1 + num2;
return result;
}
}
说明:
-
方法需要主调方法返回值时,方法声明中要明确返回值类型, 方法体中通过
return
语句来返回值,语法格式如下:return [表达式];
-
当调用方法时,方法的返回值就是
return
后面的表达式的值,返回值类型必须与方法声明的返回值类型一致; -
被调方法只能给主调方法返回一次数据;
-
在方法执行过程中,一旦执行了
return
语句,方法结束执行并返回; -
在返回值类型声明为
void
的方法中,可以使用return
语句结束方法执行。
五、方法的参数
1、方法传参
方法的参数即调用方法时主调方法依据被调方法声明的参数列表传递给被调方法的数据。
调用方法时,主调方法中传入的参数称之为实际参数(实参),被调方法中用来接收数据的参数称之为形式参数(形参)。
- 仔细观察下面已修改的MathUtils类
public class MathUtils {
/**
* 两数相乘的方法
* @param num1
* @param num2
* @return 返回两数相乘的结果
*/
public int multiply(int num1, int num2) {
int result = num1 * num2;
return result;
}
/**
* 两数相加的方法
* @param num1
* @param num2
* @return 返回两数相加的结果
*/
public int add(int num1, int num2) {
int result = num1 + num2;
return result;
}
/**
* 求两数最大公约数的方法
* @param num1
* @param num2
* @return 返回两数的最大公约数
*/
public int getGCD(int num1, int num2) {
while(true) {
if(num1 < num2) {
int tem = num1;
num1 = num2;
num2 = tem;
}
int remainder = num1 % num2;
if(remainder == 0) {
return num2;
}else {
num1 = num2;
num2 = remainder;
}
}
}
/**
* 求两数的最小公倍数
* 思路:最小公倍数 = num1 x num2 / 最大公约数
* @param num1
* @param num2
* @return 返回两数的最小公倍数
*/
public int getLCM(int num1, int num2) {
int GCD = getGCD(num1, num2);
int LCM = (num1 * num2) / GCD;
return LCM;
}
}
- Test类源码
public class Test {
public static void main(String[] args) {
// 实例化一个MathUtils类的对象,并赋值给MathUtils类型的变量mu
MathUtils mu = new MathUtils();
// 定义两个int类型的变量num1和num2,分别存储数字319和377
int num1 = 15;
int num2 = 18;
// 通过mu调用add(int num1, int num2)方法,并赋值给变量sum
// 注意方法传参时,实参的顺序、类型、数量均要与方法的参数列表相同
int sum = mu.add(num1, num2);
// 打印两数之和;
System.out.printf("%d + %d = %d \n",num1,num2,sum);
// 通过mu调用getGCD(int num1, int num2)方法,并赋值给变量gcd
// 注意方法传参时,实参的顺序、类型、数量均要与方法的参数列表相同
int gcd = mu.getGCD(num1, num2);
// 打印两数的最大公约数
System.out.printf("%d 和 %d 的最大公约数是:%d \n",num1,num2,gcd);
// 通过mu调用getLCM(int num1, int num2)方法,并赋值给变量lcm
// 注意方法传参时,实参的顺序、类型、数量均要与方法的参数列表相同
int lcm = mu.getLCM(num1, num2);
// 打印两数的最小公倍数
System.out.printf("%d 和 %d 的 最小公倍数为:%d",num1, num2, lcm);
}
}
-
结果显示为:
15 + 18 = 33 15 和 18 的最大公约数是:3 15 和 18 的 最小公倍数为:90
说明:
- MathUtils类中新声明了方法
getGCD(int num1, int num2)
,用来计算两个整数的最大公约数(Greatest Common Divisor),使用了辗转相除法。 - Test类的
main()
方法中实例化一个MathUtils
类的对象,并赋值给MathUtils
类型的变量mu
,通过mu
调用了add(int num1, int num2)
和getGCD(int num1, int num2)
,方法传参时,实参的顺序、数据类型、数量均要与方法的参数列表相同。 - 另外,当被调用的方法不需要传参数时,方法声明时和调用时方法名后的()不能省略。参数列表的设计要根据实际情况来定,参数太多,调用不便;参数太少,方法不灵活。
2、方法传参时基本数据类型和引用数据类型的内存变化
仔细观察下面的示例:
- 修改后的MathUtils类的部分源码:
public class MathUtils {
/**
* 计算一个int类型整数的平方,并赋值回原变量
* @param num 需要计算平方的整数
*/
public void square(int num) {
num = num * num;
}
/**
* 计算一个int类型数组中每个元素的平方,并赋值回原元素
* @param nums 需要计算元素平方的数组
*/
public void square(int[] nums) {
for (int i = 0; i < nums.length; i++) {
nums[i] = nums[i] * nums[i];
}
}
}
- Test类的源码
public class Test {
public static void main(String[] args) {
// 实例化一个MathUtils类的对象,并赋值给MathUtils类型的变量mu
MathUtils mu = new MathUtils();
// 通过mu调用square(int num)方法,方法中对形参计算平方并赋值
// 注意该方法并没有返回值,观察方法中形参的改变是否会影响实参
int num = 3;
mu.square(num);
System.out.println("num = " + num);
// 通过mu调用square(int[] nums)方法,方法中对形参中的每个元素计算平方并赋值
// 注意该方法并没有返回值,观察方法中形参的改变是否会影响实参
int[] nums = {3};
mu.square(nums);
System.out.println("nums[0] = " + nums[0]);
}
}
说明:
- 方法传参即是一个变量赋值的过程。
- 代码
System.out.println("num = " + num)
执行时,输出结果为3,被调方法中的形参发生变化时,主调方法中的实参并没有受影响。图示如下:
- 代码
System.out.println("nums[0] = " + nums[0])
执行时,输出结果为9,被调方法中的形参发生变化时,主调方法中的实参也发生了变化。图示如下:
3、方法的可变参数
设计方法时如果参数的类型可以确定,但个数不确定可以使用可变参数。
- 仔细观察下面的示例:
修改后的MathUtils类的部分源码
public class MathUtils {
/**
* 将若干个整数相加并返回结果
* @param nums
* @return 若干个整数相加的结果
*/
public int add(int ... nums) {
int sum = 0;
// 参数num其实是一个int类型的数组
if(nums instanceof int[]) { //instanceof运算符用来判断对象是否时特定类的一个实例;
System.out.println("nums是一个int类型的数组");
}
// 循环累加
for (int num : nums) {
sum += num;
}
return sum;
}
}
- Test类的源码
public class Test {
public static void main(String[] args) {
// 实例化一个MathUtils类的对象,并赋值给MathUtils类型的变量mu
MathUtils mu = new MathUtils();
// 通过mu调用add(int ... nums)方法,累加若干个整数并返回结果
// 该方法声明时使用了可变参数,传参时参数的个数不受限制
int sum1 = mu.add(1, 3, 5, 7, 9, 11);
System.out.println("sum1 = " + sum1);
// 可变参数其实就是数组
int[] nums = {2, 4, 6, 8, 10};
int sum2 = mu.add(nums);
System.out.println("sum2 = " + sum2);
}
}
-
运行结果:
nums是一个int类型的数组 sum1 = 36 nums是一个int类型的数组 sum2 = 30
说明:
- 可变参数其实就是数组。上例中
instanceof
运算符用来判断对象是否是特定类的一个实例。 - 参数列表中只能有一项可变参数,且必须位于参数列表的最后。
六、方法重载
在一个类中,多个方法具有相同的方法名称,但却具有不同的参数列表,与返回值无关,称作方法重载(overload)。
- 仔细观察下面的示例:
修改后的MathUtils
类的部分源码
public class MathUtils {
/**
* 将两个整数相乘并返回其结果
* @param num1
* @param num2
* @return 两整数相乘的结果
*/
public int multiply(int num1,int num2) {
return num1 * num2;
}
/**
* 将两个浮点数相乘并返回其结果
* @param num1
* @param num2
* @return 两个浮点数相乘的结果
*/
public double multiply(double num1, double num2) {
return num1 * num2;
}
/**
* 将两个整数相加并返回其结果
* @param num1
* @param num2
* @return 两个整数相加的结果
*/
public int add(int num1, int num2) {
return num1 + num2;
}
/**
* 将两个浮点数相加并返回其结果
* @param num1
* @param num2
* @return 两个浮点数相加的结果
*/
public double add(double num1, double num2) {
return num1 + num2;
}
}
- Test类的源码
public class Test {
public static void main(String[] args) {
// 实例化一个MathUtils类的对象,并赋值给MathUtils类型的变量mu
MathUtils mu = new MathUtils();
// 调用multiply(int num1, int num2)方法
int result1 = mu.multiply(3, 6);
System.out.println("result1 = " + result1);
// 调用multiply(double num1, double num2)方法
double result2 = mu.multiply(3.14, 6.28);
System.out.println("result2 = " + result2);
// 调用add(int num1, int num2)方法
int result3 = mu.add(3, 6);
System.out.println("result3 = " + result3);
// 调用add(double num1, double num2)方法
double result4 = mu.add(3.14, 6.28);
System.out.println("result4 = " + result4);
}
}
-
运行结果:
result1 = 18 result2 = 19.7192 result3 = 9 result4 = 9.42
说明:
-
参数列表不同,指的是参数个数、参数数据类型、参数顺序不同;
// 以下是正确的方法重载的例子: public double area(double a); public double area(double a, int b); public double area(int a, double b); // 以下是错误的方法重载的例子: public int calc(int a, int b); public void calc(int x, int y);
-
两个
calc()
方法虽然返回值类型和参数名称不同,但参数个数、类型和顺序完全相同,也就是说它们的参数列表是相同的。 -
调用方法时,方法名和传入的参数共同决定了具体调用了哪个方法。
七、构造方法
类中有一种特殊的方法,其方法名与类名相同,没有返回值类型,这种方法叫构造方法,也叫构造函数或构造器。
- 仔细观察下面的示例:
public class Student {
// 声明成员变量
String name; // 姓名
int age; // 年龄
String stuNo; // 学号
// 声明方法
/**
* 没有参数的构造方法
*/
public Student() {
System.out.println("Student类无参数的构造方法被调用");
}
/**
* 含有一个参数的构造方法
* @param name
*/
public Student(String name) {
this.name = name;
System.out.println("Student类含有参数name的构造方法被调用");
}
/**
* 含有三个参数的构造方法
* @param name
* @param age
* @param stuNo
*/
public Student(String name, int age, String stuNo) {
this.name = name;
this.age = age;
this.stuNo = stuNo;
System.out.println("Student类含有参数name、age、studentNo的构造方法被调用");
}
/**
* 自我介绍的方法
*/
void introduce() {
System.out.printf("大家好,我是%s,我今年%d岁,我的学号是:%s", name, age, stuNo);
}
}
说明:
- 构造方法主要用来在创建对象时为对象的成员变量赋初始值。
- 构造方法不允许使用对象调用,只有在用
new
运算符实例化对象时才会被自动调用。 - 如果类中不显示声明构造方法,系统会为类默认提供无参数的构造方法。如果自定义了构造方法,系统默认的构造方法就不存在了。
- 一个类可以声明多个构造方法,即构造方法的重载,满足方法重载的要求即可。
- 上例中使用了
this
关键字,在Java中,this
可以引用当前类的对象,关于该关键字,将在后面的章节中展开介绍。
八、变量的作用域与生命周期
1、变量的作用域
-
变量的作用域就是指一个变量定义后,在程序的什么地方能够使用;
-
在Java中,一对大括号
{ }
包含的区域,也被称之为一个代码块(语句块)。使用大括号{ }的地方有:类声明、方法声明、方法中的循环体、分支条件后的语句等,一个变量的作用域只被限制在该变量声明所在的代码块中(也就是离该变量的声明语句最近的大括号内)。 -
方法中声明的变量,称为局部变量,方法的形式参数也是方法的局部变量。局部变量只能在当前方法中使用 ;在判断代码块中声明的变量只能在当前判断代码块中使用,当前判断代码块之外不能正常使用;对循环代码块也是一样。
-
声明在类中,但在方法之外的变量称为成员变量。被
static
关键字修饰的成员变量在整个类声明里的成员方法中都可以使用;没有被static关键字修饰的成员变量在整个类声明里没有被static关键字修饰的成员方法中都可以使用。有时在一个方法中,会遇到局部变量和成员变量同名的情况,此时,直接使用变量名其实是在使用局部变量,如果要使用成员变量,需要this.成员变量名
。
2、变量的生命周期
- 变量的生命周期是指变量什么时候分配内存,什么时候从内存中回收。
- 对于局部变量,会在方法或语句块执行的时候创建(在栈中分配内存),当他们执行完成后,变量随之被销毁。另外,局部变量没有默认值, 所以局部变量被申明后,必须经过初始化,才可以使用。
- 对于没有被
static
关键字修饰的成员变量,会在对象创建的时候创建,在对象被销毁的时候销毁(在堆中分配内存)。另外,成员变量在创建时具有默认值,数值型变量的默认值是0(整型是0,浮点型是0.0),字符型变量的默认值是’\u0000’,布尔型变量的默认值是false,引用类型变量的默认值是null。