方法的定义及使用
方法在很多地方被称为函数(在Java中的英文单词是Method,而在其他语言中的英语单词是Function),方法是一段可以被重复执行的代码块。
1.方法的基本概念
方法的主要功能是封装可以执行的一段代码,这样不仅可以进行重复调用,更可以方便的实现代码的维护,而本次使用的方法定义语法如下。
public static 返回值类型 方法名称(参数类型 参数变量,....){
方法体 (本方法要执行的若干操作);
[return[返回值];]
}
在方法的定义格式中,发现其中有一个返回值类型,指的是这个方法的返回结果。对于此返回值的类型可以有以下两种。
·直接设置Java中的数据类型(基本数据类型,引用数据类型),如果方法设置了返回值,那么必须使用return语句返回与数据类型对应的数据。
·方法没有返回值void,可以不使用return返回内容,但是可以使用return结束方法调用。
(注意:Java中方法的定义要求很多,本次给出的方法第一格式有一个使用限制:“定义在主类中,并且由主方法直接调用。”)
例如:定义一个没有参数返回值的方法
public class TestDemo {
public static void main(String args[]) {
printInfo(); // 直接调用方法
printInfo(); // 直接调用方法
}
/**
* 信息输出操作
*/
public static void printInfo() { // 定义没有参数,没有返回值的方法
System.out.println("*********************");
System.out.println("* 123 *");
System.out.println("*********************");
}
}
执行结果:
*********************
* 123 *
*********************
*********************
* 123 *
*********************
*本程序首先在TeseDemo主类中定义了一 个printInfo()方法,此方法主要进行内容的输出,所以在方法
*声明返回值时使用了void,然后在主方法中调用了两次 printInfo()方法。
-
注意:方法命名规范。
如果要在程序中定义方法,Java的命名规范为:第一个单词的首字母小写,之后每个单词的首字母大写,例如: printInfo()、 getMessage()。 -
如何判断需要定义方法?方法是一段可以被重复调用的代码段,那么什么时候该把这些代码段封装为方法,有没有要求?
在开发中将那些代码封装为方法实际上并没有一个严格的定义标准,更多的时候往往是依靠开发者个人经验进行的。如果是初学者应该先以完成功能为主,而后再更多地考虑代码结构化的合理性。但是在很多情况下如果在开发中发现一直都在进行部分代码的“复制一粘贴”操作,那么就应该考虑将这些代码封装为方法以进行重复调用。
定义一个有参数无返回值的方法
public class TestDemo {
public static void main(String args[]) {
pay(10.0); // 调用方法
pay(-10.0); // 调用方法
}
/**
* 定义一个支付的操作方法,如果支付金额大于0则正常支付,否则会输出错误提示信息
* @param money 要支付的金额
*/
public static void pay(double money) { // 购买支付操作
if (money > 0.0) { // 现在已经给钱
System.out.println("可以进行支付!");
} else { // 不能够支付
System.out.println("你穷疯了,没钱还买东西!");
}
}
}
程序执行结果:
可以进行支付!(“pay(10.0)”调用执行)
你穷疯了,没钱还买东西!(“pay(-10.0)”调用执行)
定义有返回值有参数的方法
public class TestDemo {
public static void main(String args[]) {
int result = add(10, 20); // 方法的返回值可以进行接收
System.out.println("计算结果:" + result);
System.out.println("计算结果:" + add(50, 60)); // 也可以直接将方法返回值进行输出
}
/**
* 实现数据的加法操作
* @param x 操作数字一
* @param y 操作数字二
* @return 返回两个数字的加法计算结果
*/
public static int add(int x, int y) { // 有参数有返回值的方法
return x + y; // 返回加法计算结果
}
}
程序执行结果:
计算结果:30
计算结果:110
/**
* 本程序在主类中定义了一一个add()方法,而后此方法接收两个int 型的变量,执行加法计算后会将计算的
* 结果返回给方法的调用处,由于方法本身存在返回值,所以可以接收返回值,或者直接进行返回值的输出。
* /
以上是方法在实际开发中的3种基本定义形式,但是在这里需要提醒读者的是,如果在方法中执行return语句,那么就表示其之后的代码不再执行而直接结束方法调用。如果此时方法有返回值声明,那么必须返回相应类型的数据;如果没有返回值声明,则可以直接编写return。 而此类操作一般都会结合分支判断一起使用。
利用return结束方法调用
Public class TestDemo {
public static void main(String args[]) {
set(100); // 正常执行输出
set(3); // 满足方法判断条件,会中断输出操作
set(10); // 正常执行输出
}
/**
* 定义一个设置数据的操作方法,如果该数据为3将无法设置
* @param x 要设置的数据内容
*/
public static void set(int x) { // 方法声明为void
if (x == 3) { // 判断语句
return; // 方法后面的内容不执行了
}
System.out.println("x = " + x);
}
}
程序执行结果:
x=100
x=10
/**
*本程序定义的set()方法上使用了void声明,所以此类方法中想要结束调用,可以直接编写return语句
*当传入的参数为3时,符合方法结束调用的条件,所以后面的输出不在执行。
*/
2.方法的重载
方法的重载是指方法名相同,参数类型或个数不同,调用的时候将会按照传递的参数类型和个数完成不同方法体的执行。
如果有一个方法名称,有可能要执行多想操作,例如:一个add()方法,它可能执行两个整数相加,也可能执行三个整数相加,或者执行两个小数相加,那么在这种情况下,很明显,一个方法体很定无法满足要求,需要定义为add()方法定义多个不同的功能实现,所以此时就需要方法重载概念的支持。
观察下面的方法重载
public class TestDemo {
public static void main(String args[]) {
// 方法重载之后执行语句时会根据传入参数的类型或个数的不同调用不同的方法体
System.out.println("两个整型参数:" + add(10, 20));
System.out.println("三个整型参数:" + add(10, 20, 30));
System.out.println("两个浮点型参数:" + add(10.2, 20.3));
}
/**
* 实现两个整型数字的加法计算操作
* @param x 操作数字一
* @param y 操作数字二
* @return 两个整型数据的加法计算结果
*/
public static int add(int x, int y) { // add()方法一共被重载三次
return x + y;
}
/**
* 实现三个整型数字的加法计算操作
* @param x 操作数字一
* @param y 操作数字二
* @param z 操作数字三
* @return 三个整型数据的加法计算结果
*/
public static int add(int x, int y, int z) { // 与之前的add()方法的参数个数不一样
return x + y + z;
}
/**
* 实现两个小数的加法计算操作
* @param x 操作数字一
* @param y 操作数字二
* @return 两个小数的加法计算结果
*/
public static double add(double x, double y) { // 与之前的add()方法的参数类型不一样
return x + y;
}
}
程序执行结果:
两个整形参数:30
三个整形参数:60
两个浮点型参数:30.5
/**
*本程序在类中一共定义了3个add()方法,但这3个ad0方法的参数个数以及数量完全不同,所以就证明此时add()
*方法已经被重载了。而在调用方法时,虽然方法的调用名称相同,但是会根据其声明的参数个数或类型执行不
*同的方法体。
*/
方法重载的概念本身很容易理解,但对于方法重载有以下两点说明
①在进行方法重载是一定要考虑到参数类型统一,虽然可以实现重载方法返回不同类型的操作,但是从标准的开发来讲,建议所有重载后的方法使用同一种返回值类型。
②方法重载的时候重点是根据参数类型及个数来区分不同的方法,而不是依靠返回值的不同确定的。
3.方法的调用
1.方法调用的形式
如果在被调用方法所在类的外部调用方法,形式为:
对象名.方法名([实际参数列表])
类名.方法名([实际参数列表])
如果在被调用方法所在的类中调用方法,则调用形式为:
[this.]方法名([实际参数列表])
大多数情况下,关键字this可以省略。
实际参数简称实参,其作用是向形参传递数据。实参和形参的个数要相等,对应位置上的数据类型要相容,即数据类型相同或者实参可以做自动类型转换,转换成形参类型。实参可以是0个或多个变量或表达式,如果超过一个,需要用逗号分隔。
在同一个方法调用方法示例
public class CallMethod {
public void Show() {
System.out.println("Show method is referenced.");
}
public void CallOther() {
Show(); //调用Show()方法,也可以写成this.show()
}
public static void main(String args[]) {
CallMethod ob=new CallMethod();
ob.CallOther();
}
}
程序运行结果:
Show method is referenced.
在程序中,CallOther()方法和Show()方法处在同一个类中,所以调用后者时,直接使用方法名即可。但main方法是一个静态方法,它由系统调用。系统在调用它的时候,并没有创建一个CallMethod的对象,而CallOther()方法和Show()方法都是实例方法,他们在被调用时都必须有对象存在。所以必须现在main()中创建一个对象ob,才能调用这两个方法。从这一点来看,main()方法虽然处在CallMethod类的内部,但它的表现却如同在类的外部一样。
CallOther()本身是实例方法,它在被执行时,一定是有对象存在的,所以它才能直接调用Show()方法。
外部方法调用示例
CallMethod.java
public class CallMethod {
public void Show() {
System.out.println("Show method is referenced.");
}
public void CallOther() {
Show(); //调用Show()方法,也可以写成this.show()
}
}
CallOther.java
public class CallOther {
public static void main(String args[]) {
CallMethod ob=new CallMethod();
ob.CallOther();
}
}
程序运行结果:
Show method is referenced.
在CallOther类中,还可以调用Show()方法,形式还是ob.Show()。
2.方法调用的参数
在调用方法时,系统会将实参的值按照对应位置关系一个一个传递给形参,即第一个实参传递给第一个形参,第二个实参传递给第二个形参,以此类推,这个过程不考虑形参和实参的名字。
由于在Java中存在两种类型的数据:基本类型和复合类型(引用类型)。这两种类型的数据作为参数传递时,是有区别的。下面分别介绍这两种情况。
(1)基本类型作为参数。
当方法的参数是基本类型(包括整型、浮点型和布尔型)时,它是通过传值方式进行调用的。实参是一个可求值的表达式时,它所求出来的值的类型与形参的类型相同,或使表达式的类型按Java 类型转换规则达到形参的数据类型,实现表达式值到形参的单值传递。实参是一个变量时,实现实参到形参的单向传递。此时方法内部可以修改形参的值,但这种修改不会影响到对应的实参的值。
直观来看,传值过程相当于一个赋值过程,将实参的值赋给形参,而不能将形参赋给实参。它们发生联系只在调用的那一瞬间,以后两者之间再无关系。
单向传值示例
假如一个方法试图将一个参数值增加至原来的三倍,可能会编写如下程序:
public class PassValue {
static void TryChange(int n) {
n=n*3;
}
public static void main(String[] args) {
int i=10;
System.out.println("调用TryChange方法之前,i="+i);
TryChange(i);
System.out.println("调用TryChange方法之后,i="+i);
}
}
程序运行结果:
调用TryChange方法之前,i=10
调用TryChange方法之后,i=10
当调用TryChange()方法时,将实参 i 传给形参 n,尽管在TryChange()方法中改变了形参 n 的值,但对实参 i 并没有影响。方法结束后,n 不再使用。从这个例子还可以看出,形参实际上是一个局部变量,她的作用域仅限于定义它的方法体内部。
对于按传值传递的参数,如果需要在方法调用以后修改参数的值,可以利用返回值实现。上述程序修改为:
public class PassValue {
static int TryChange(int n) {
n=n*3;
return n;
}
public static void main(String[] args) {
int i=10;
System.out.println("调用TryChange方法之前,i="+i);
i=TryChange(i); //强制赋值
System.out.println("调用TryChange()方法之后,i="+i);
}
}
程序运行结果:
调用TryChange方法之前,i=10
调用TryChange方法之后,i=30
在程序中,通过把修改以后的参数 n 的值返回,来为变量 i 赋值,强制修改按值传递参数的值,从而达到修正参数值的目的。
单向传值可以防止在无意的情况下改变实参的值,起到了降低程序间数据耦合度的作用。但在某些情况下,单向传值却会阻碍某些功能的实现。例如,要写一个方法实现两个参数交换值的功能,很容易想到用下面的方法:
public void swap(int x, int y){
int t=x;
x=y;
y=t;
}
然后这样来调用它:
swap(x,y);
调用过后,会发现,x 和,y 的值没有任何改变。因为在方法swap()中交换的只是形参 x 和 y 的值,这对于实参 x 和 y 来说,没有任何影响。实际上,在Java中,没有简单的方法能够实现上述交换两个基本变量的值,而只能把上面这段代码写在需要交换的地方。
(2) 复合类型作为参数。
如果形式参数不是基本类型,而是复合(引用)类型,例如数组、对象等那么在调用相应的方法时,系统会将该实参的地址值传递给形参。这时实参和形参指向了同个内存单元, 其中任何一个变量值的改变都会对另外一个变量的值产生影响。
对象的传值过程, 其实是借用了C++中指针传值的方法,造成的效果也完全相同。下面这个例子展示了对象传值的效果。
对象传值示例。
public class PassObject {
public static void main(String[] args) {
TestClass p=new TestClass(10); //初始化g为10
System.out.println("调用CallByReference方法之前p.g="+p.g);
CallByReference(p);
System.out.println("调用CallByReference方法之后p.g="+p.g);
}
static void CallByReference(TestClass ob) {
ob.Change(ob.g);
}
}
class TestClass{
int g;
public TestClass(int n) {
g=n;
}
public void Change(int n) {
g=n*3;
}
}
程序运行结果:
调用CallByReference方法之前p.g=10
调用CallByReference方法之后p.g=30
在程序中定义了一个TestClass类,有一个 int 型的成员 g ,类的Change()方法使 g 的值增为原始赋值的 3 倍。主方法中先定义一个TestClass类的对象 p ,并将成员 g 的值初始化为 10。然后调用 方法CallByReference(),它的形参 ob 接受 p 的值,他们将共用一个对象实体。在Change()中改变了 ob 的 g 值,这一改变,对 p 也是有效的。程序的输出结果也证明了这一点。
数组传值示例
public class PassArray {
public static void InitArray(int[] array) {
for (int i=0;i<array.length;i++)
array[i]=i;
}
public static void main(String[] args) {
int[] a=new int[10];
int s=0;
InitArray(a);
for(int i=0;i<a.length;i++)
s+=a[i];
System.out.println("s="+s);
}
}
程序运行结果:
s=45
在该程序中,在InitArray()方法内部被修改了数组array的值以后,实参数组 a 的值也会发生改变,间接实现了返回值的效果。
4.方法的递归调用
递归调用是一种特殊的调用方式,指的是方法自己调用自己的形式,在进行递归操作时必须满足以下两个条件。
1)必须有结束条件;
2)每次调用时都需要改变传递的参数。
关于递归:
递归调用是我们迈向数据结构开发的第一步,但是如果想把递归操作掌握熟练,需要大量的代码积累才可能写出合理的代码。换个角度说,如果在标准的项目应用开发中,是很难写上递归操作的。之所以在开发中避免过多的使用递归是因为如果处理不得当,就可能出现内存溢出的问题。
递归调用
public class TestDemo {
public static void main(String args[]) {
System.out.println(sum(100)); // 1 - 100累加
}
/**
* 数据的累加操作,传入一个数据累加操作的最大值,而后每次进行数据的递减,一直累加到计算数据为1
* @param num 要进行累加的操作
* @return 数据的累加结果
*/
public static int sum(int num) { // 最大的内容
if (num == 1) { // 递归的结束调用
return 1; // 最终的结果返回了1
}
return num + sum(num - 1); // 递归调用
}
}
程序执行结果:
5050
本程序使用递归操作进行了数字的累加操作,并且当传递的参数为1时,直接返回数字1。本程序的流程简单分析如下:
第一次调用:return 100 + sum(99);
第二次调用:return 100 + 99 + sum(98)
倒数第二次调用:return 100 + 99 + … + 3 + sum(2);
最后一次调用:return 100 + 99 + …+ 3 + 2 + 1。