一、概述
一般可以将实现某一逻辑的程序语句集合放到一个方法中,如果在其他方法中想要执行这个逻辑,则可以通过调用的形式来实现,这样做,就使程序变得更加简短清晰,利于维护,提高了代码的可复用性。
Java 支持两种调用方法的方式,根据方法是否返回值来选择。
当程序调用一个方法时,程序的控制权交给了被调用的方法。当被调用方法的返回语句执行或者到达方法体闭括号时候交还控制权给程序。
当方法返回一个值的时候,方法调用通常被当做一个值
例如:
public class FunExample {
//没有返回值的方法,当传入的参数满足a<b时,打印"a小于b"
public void compare(int a, int b) {
if (a < b) {
System.out.println("a小于b");
}
}
//返回int类型值的方法,计算a与b的值之和,并返回结果
public int sum(int a, int b) {
int c;
c = a + b;
return c;
}
public void test(){
/*if (1 < 2) {
System.out.println("a小于b");
}*/
FunExample funExample = new FunExample();
funExample.compare(1, 2);
}
public static void main(String[] args) {
int c;
FunExample funExample = new FunExample();
/*if (1 < 2) {
System.out.println("a小于b");
}*/
//调用没有返回值的方法
funExample.compare(1, 2);
//调用存在返回值的方法
c = funExample.sum(1, 2);
System.out.println(c);
}
}
打印结果:
a小于b
3
将不同的逻辑,放到不同的方法中实现,在需要时,通过对象进行调用,这样,就不需要在每个要用到这个逻辑的地方去实现它,甚至不需要去考虑方法内是怎样实现的。
在上面的示例中,在main方法和test方法中,都需要比较两个整数的大小,这时将比较的逻辑放到一个方法中去实现,就不必在两个方法中都实现一次比较逻辑。
二、方法调用
在Java中,一个类中的方法可以互相调用;不同类中的方法也可以互相调用;方法也可以自己调用自己;
在同一类中时,各方法之间可以相互调用,其中静态方法可以通过类名直接调用,而非静态方法必须通过实例化的对象调用。
public class FunExample {
//静态方法
static void funA(){
FunExample funExample = new FunExample();
//在静态方法中调用静态方法
System.out.println("在静态方法中调用静态方法");
funExample.funB();
//静态方法可以直接使用类名调用
FunExample.funB();
//在静态方法中调用非静态方法
System.out.println("在静态方法中调用非静态方法");
funExample.funD();
//FunExample.funD(); //非静态方法使用类名直接调用会报错:无法从静态上下文中引用非静态 方法 funD()
}
//静态方法
static void funB(){
System.out.println("[静态方法B]");
}
//非静态方法
void funC(){
FunExample funExample = new FunExample();
//在非静态方法中调用静态方法
System.out.println("在非静态方法中调用静态方法");
funExample.funB();
//静态方法可以直接使用类名调用
FunExample.funB();
//在非静态方法中调用非静态方法
System.out.println("在非静态方法中调用非静态方法");
funExample.funD();
}
//非静态方法
void funD(){
System.out.println("[非静态方法D]");
}
public static void main(String[] args) {
FunExample funExample = new FunExample();
funExample.funA();
funExample.funC();
}
}
打印结果:
在静态方法中调用静态方法
[静态方法B]
[静态方法B]
在静态方法中调用非静态方法
[非静态方法D]
在非静态方法中调用静态方法
[静态方法B]
[静态方法B]
在非静态方法中调用非静态方法
[非静态方法D]
在不同类之间,一个方法是否可以调用另一个方法主要由访问控制修饰符来控制;
用在方法上的访问控制修饰符有如下几种:
- public:控制级别最低,对所有类可见;
- protected :对同一包内的类和所有子类可见;
- default :即缺省值,在方法头上不加任何访问控制修饰符时的默认值,在同一包内可见;
- private :控制级别最高,仅在同一类内可见;
例:
存在如下的项目结构:
类FunExample代码如下:
public class FunExample {
//public修饰的方法
public void funA(){
System.out.println("public修饰的方法");
}
//protected修饰的方法
protected void funB(){
System.out.println("protected修饰的方法");
}
//无访问控制修饰符的方法
void funC(){
System.out.println("无访问控制修饰符的方法");
}
//private修饰的方法
private void funD(){
System.out.println("private修饰的方法");
}
public static void main(String[] args) {
}
}
FunExample类中有四个方法,FunExampleA为同一包内的类,FunExampleB为不同包内的类,FunExampleC为不同包内FunExample的子类,在三个包内分别调用FunExample类中的四个方法;
public class FunExampleA {
public static void main(String[] args) {
FunExample funExample = new FunExample();
//public修饰的方法
funExample.funA();
//protected修饰的方法
funExample.funB();
//无访问控制修饰符的方法
funExample.funC();
//private修饰的方法
funExample.funD(); //此句会报错:'funD()' has private access in 'wjh.testA.FunExample'
}
}
public class FunExampleB {
public static void main(String[] args) {
FunExample funExample = new FunExample();
//public修饰的方法
funExample.funA();
//protected修饰的方法
funExample.funB(); //此句会报错:'funB()' has protected access in 'wjh.testA.FunExample'
//无访问控制修饰符的方法
funExample.funC(); //此句会报错:'funC()' is not public in 'wjh.testA.FunExample'. Cannot be accessed from outside package
//private修饰的方法
funExample.funD(); //此句会报错:'funD()' has private access in 'wjh.testA.FunExample'
}
}
public class FunExampleC extends FunExample {
public static void main(String[] args) {
//使用父类实例化对象并调用方法
FunExample funExample = new FunExample();
//public修饰的方法
funExample.funA();
//protected修饰的方法
funExample.funB(); //此句会报错:'funB()' has protected access in 'wjh.testA.FunExample'
//无访问控制修饰符的方法
funExample.funC(); //此句会报错:'funC()' is not public in 'wjh.testA.FunExample'. Cannot be accessed from outside package
//private修饰的方法
funExample.funD(); //此句会报错:'funD()' has private access in 'wjh.testA.FunExample'
//使用子类实例化对象并调用父类中的方法
FunExampleC funExampleC = new FunExampleC();
//public修饰的方法
funExampleC.funA();
//protected修饰的方法
funExampleC.funB();
//无访问控制修饰符的方法
funExampleC.funC(); //此句会报错:'funC()' is not public in 'wjh.testA.FunExample'. Cannot be accessed from outside package
//private修饰的方法
funExampleC.funD(); //此句会报错:'funD()' has private access in 'wjh.testA.FunExample'
}
}
从以上例子可以验证不同访问控制的方法在不同类之间的调用,如果是同一个包内的子类,和同一个包内的其他类效果是相同的;
需要注意的是,
不同包内的子类,在使用父类实例化的对象调用protected修饰的方法是会报错,而使用子类实例化的对象调用protected修饰的方法则不会报错。
如果protected修饰的方法是静态方法,那么在不同包下的子类中,可以通过父类直接调用,或在非静态方法中通过super关键字直接调用。
所以protected应该理解为,子类可以访问父类的protected方法,而在子类中父类对象不可以访问自己的protected方法。
三、方法之间的参数传递
方法调用通常会涉及到参数的传递,调用方将参数传入被调用方法,参数在被调用方法中参与程序的执行;
参数的传递方式分为两种,值传递和引用传递。
- 值传递:方法调用时,实参把它的值传递给对应的形式参数,形参只是用实参的值初始化自己的存储单元内容,是两个不同的存储单元,所以方法执行中形参值的改变不影响实参的值。
- 引用传递:也称为传地址。一般引用传递的参数是对象或数组,因为对象或数组对应的占内存中存储的是指向堆内存的地址,所以这时实参与形参指向同一个地址,在方法执行中,对形参的操作实际上就是对实参的操作,这个结果在方法结束后被保留了下来,所以方法执行中形参的改变将会影响实际参数。
举个简单的例子帮助理解
值传递:假设有小明拥有一份资料,小红想要这份资料,小明复印之后送给小红一份,在这之后不管小红在他拿到的资料上做任何修改,小明手里的资料内容都不会改变。
引用传递:小明收藏了一个学习网站,小红也想去这个网站上学习,小明将网站的地址分享给小红,在这之后不管谁修改了网站上的内容,小明和小红看到的内容都是修改后的。
下面通过实际代码来验证值传递和引用传递在Java中的效果
1.值传递
public class FunExample {
public void funA(int a) {
a = 5;
}
public static void main(String[] args) {
FunExample funExample = new FunExample();
int a = 0;
funExample.funA(a);
System.out.println(a);
}
}
输出结果:
0
上述例子中,在main方法中声明了一个基本类型int型变量a,将a的值传入方法funA(a)中,在funA中对形参a赋值=5,之后在main方法中打印实参a的值,仍然是0;
2.引用传递
(1)包装数据类型
public class FunExample {
public void funA(String a) {
a = "在funA方法中修改";
}
public static void main(String[] args) {
FunExample funExample = new FunExample();
String a = "在main方法中声明的String";
funExample.funA(a);
System.out.println(a);
}
}
打印结果:
在main方法中声明的String
在上述例子中,执行发现,即使是引用传递,但是在funA中修改a的值之后,main方法中的a还是没有改变;
结合之前学习的包装类型的基础知识:“包装类对象一经创建,其内容不可改变(重新赋值时会创建新的对象)”;
所以通过System.identityHashCode方法打印对象的内存地址来看:
public class FunExample {
public void funA(String a) {
System.out.println("funA:赋值前" + System.identityHashCode(a));
a = "在funA方法中修改";
System.out.println("funA:赋值后" + System.identityHashCode(a));
}
public static void main(String[] args) {
FunExample funExample = new FunExample();
String a = "在main方法中声明的String";
funExample.funA(a);
System.out.println("main:" + System.identityHashCode(a));
}
}
打印结果:
funA:赋值前856419764
funA:赋值后621009875
main:856419764
可以看到,在方法funA中对形参a重新赋值前,其指向的内存地址与实参a是相同的,而在重新赋值时,自动创建了新的对象;所以包装类型虽然是引用传递,但是并不能得到想象中的结果。
(2)对象类型参数
下面用StringBuffer类来进行验证,StringBuffer用来处理字符串,与String不同的是,StringBuffer类的对象能够被多次修改
例:
public class FunExample {
public void funA(StringBuffer a) {
//StringBuffer的append方法:在原字符串后拼接字符串
a.append("在funA中增加内容");
}
public static void main(String[] args) {
FunExample funExample = new FunExample();
StringBuffer a = new StringBuffer("在main方法中声明的String;");
funExample.funA(a);
System.out.println(a);
}
}
打印结果:
在main方法中声明的String;在funA中增加内容
可以看到,当参数类型为StringBuffer时,在funA中处理a的内容,main方法中的实参a的内容也会随之改变;
(3)数组类型参数
public class FunExample {
public void funA(char[] a, Character[] b) {
a[1] = 'd';
b[1] = 'D';
}
public static void main(String[] args) {
FunExample funExample = new FunExample();
char[] a = {'a', 'b', 'c'};
Character[] b = {'A', 'B', 'C'};
funExample.funA(a, b);
System.out.print("a: ");
for (char x :
a) {
System.out.print(x);
}
System.out.println();
System.out.print("b: ");
for (Character y :
b) {
System.out.print(y);
}
}
}
打印结果:
a: adc
b: ADC
上述示例中,在main方法中分别声明了一个char的数组和Character类型的数组,在funA对两个数组的值进行修改后,实参的值也都发生了改变,可以看出了,数组参数是一个引用类型的参数,和数组类型是基本数据类型还是包装类型无关。