方法的定义
什么是方法?
方法是完成某个功能的一组语句,通常将常用的功能写成一个方法
[访问控制符] [修饰符] 返回值类型 方法名(参数类型 形式参数,参数类型 形式参数,…) {
方法体
}
- 修饰符: public 、static 被称为修饰符(后续会详细讲解它们);
- 返回值类型: 用来说明该方法运算结果的类型。如果返回其他类型,编译就可能出错;
- 方法名: 它作为调用时引用方法的标识;
- 参数列表: 方法的参数个数可以是0个到多个,每个参数前面要声明参数的数据类型;
每个参数要用逗号分开。也可以一个参数都没有。 - 方法体: 它是一个语句块,执行特定的功能操作。对于有返回值类型的方法,方法体当中最后一个语句是return关键字,它的作用是把方法的执行(运算)结果返回到方法外部。
- return 表达式: 这里,进一步分析,return后面的表达式就是方法的返回值。需要注意表达式的类型,必须与方法头中声明的“返回类型”相匹配。
- 形式参数: 在方法被调用时用于接受外部传入的变量。
- 参数类型: 就是该形式参数的数据类型。
- 返回值: 方法在执行完毕后返回给调用它的程序的数据。
- 返回值类型: 方法要返回的结果的数据类型。
方法的分类
- 根据参数个数:
- 无参方法
- 有参方法
- 根据返回值类型:
- 有返回值的方法:
- 返回值为基本数据类型
- 返回值为引用数据类型
- 无返回值的方法:
- void
对于无返回值类型的方法,它不向本方法外部返回任何值。定义这样的方法时,声明方法返回类型的位置不能省略不写,而应该用关键字void来代替,即 “空”的意思。
- 有返回值的方法:
- 根据方法类型:
- 静态方法(static method)
与静态成员变量一样,属于类本身,在类装载的时候被装载到内存中,不自动进行销毁,会一直存在内存中,直到JVM关闭;
- 非静态方法(non-static method)
又称实例化方法,属于实例对象,实例化之后才会分配内存,必须通过类的实例来引用,当实例对象被JVM回收之后,也跟着消失
静态方法和实例方法的区别:
- 生命周期
静态方法的生命周期从进程创建时就开始,一直到进程结束,所以说静态方法是全局的,贯穿整个进程。
实例方法的生命周期,从实例化对象开始,一直到实例化对象被注销回收之后结束,所以实例方法的生命周期短于静态方法的生命周期,这也是实例方法中不能调用静态方法的原因。 - 调用方式
在外部调用静态方法时,可以使用 “类名.方法名”的方式,也可以使用“对象.方法名”的方式,也就是说调用静态方法时无需创建对象。
实例方法只能使用“对象.方法名”的方式。 - 访问限制
静态方法在访问本类的成员时,只允许访问静态成员(即静态变量和静态方法),而不允许访问实例成员变量和实例方法;而实例方法则无此限制。
实例成员变量是属于某个对象的,在静态方法执行时,并不一定存在该对象;同理,如果允许静态方法访问实例成员方法,就间接的可以访问实例成员变量,所以也不能访问实例成员方法;基于同样的道理,静态方法中也不能使用关键字 this。 - 执行顺序
当一个 class 文件被 ClassLoader load 进入 JVM 之后,方法指令保存在 Stack 中,此时 Heap 区并没有数据。然后程序计数器开始执行指令,如果是静态方法,直接依次执行指令代码,当然此时指令代码是不能访问 Heap 数据区的;如果是实例方法(实例方法有一个隐含的传入参数,该参数是 JVM 给它的,这个参数就是实例对象在 Stack 中的内存地址,因此实例方法才可以找到在 Heap 中属于自己的数据),则在调用前必须实例化该对象,在 Heap 中分配数据,并将 Stack 中的内存指针通过 JVM 的隐含参数传给实例方法。若不实例化直接调用,由于隐含参数没有值,会报错。
下面这个代码实例,方法体有两个return 语句,但是只有一个return语句能被执行。方法的返回类型可以是java中的任何数据类型:基本数据类型(4种整型、2种浮点型、字符型、布尔型共8种) 和引用数据类型(数组、类、接口)。
public class FunctionDemo {
public int absolut(int x) {
if (x >= 0) {
return x;
} else {
return -x;
}
}
}
方法定义示例:
public class Test {
public static void main(String[] args) {
// 在main方法中定义方法的做法是错误的
/*//定义一个方法
public static int getCount() {
}*/
}
//只要方法定义时声明了返回数据,那么方法体中return语句一定要得到执行的机会
public int getFirst(int[] arr) {
if(arr[0] > 0) {
return arr[0];
} else {
return 0;
}
}
/*
* 方法的定义
* 1.有返回值的方法:访问修饰符 [static] 数据类型 方法名(参数列表...) { //方法体 }
* 2.没有返回值的方法:访问修饰符 [static] void 方法名(参数列表...) { //方法体 }
* 3.不需要参数:访问修饰符 [static] 数据类型 方法名() { //方法体 }
*/
public static void printName(String name) {
System.out.println(name);
}
/*
* 需求:算出两个整数的和,并返回
*/
public static int sum(int firstNumber, int secondNumber) {
// 方法体
int result = firstNumber + secondNumber;
return result;
// return firstNumber + secondNumber;
}
/*
* 需求:接收一个整数,判断它是不是质数,并返回结果boolean类型,true表示是,false表示不是
*/
public static boolean isPrime(int source) {
// 套路:如果方法有返回值,那么先在方法体的第一行声明该返回值
boolean result = true;
/*
* 思路:拿着这个数从2开始除,一直除到比source小1,不管除到谁,只要余数为0,那么这个数就不是质数
*
* 6: 1,6 2,3
*
* 3: 1,3
*
* 100: 2-99
*/
for (int i = 2; i < source; i++) {
if(source % i == 0) {
result = false;
//循环不用继续,结束
break;
}
}
return result;
}
public boolean isPrime2(int source) {
// 套路:如果方法有返回值,那么先在方法体的第一行声明该返回值
boolean result = true;
/*
* 思路:拿着这个数从2开始除,一直除到比source小1,不管除到谁,只要余数为0,那么这个数就不是质数
*
* 6: 1,6 2,3
*
* 3: 1,3
*
* 100: 2-99
*/
for (int i = 2; i < source; i++) {
if(source % i == 0) {
result = false;
//循环不用继续,结束
break;
}
}
return result;
}
}
方法的调用
方法只有在被调用后才生效
方法的调用语法:
-
无参方法的调用
方法名( )
-
有参方法的调用
方法名(参数列表)
-
定义方法就是编写一段有特定功能的代码,在程序中使用同样功能的地方,没有必要重复编写同样的代码,只要调用定义好的方法就可以。可以实现代码的重用。简化了程序的编写和维护工作。
-
所谓调用方法,其实就是给方法的入口传入一些值(参数),然后在出口得到方法执行的结果(返回值)。给方法传入参数的过程,称为“传参”。
-
理解“实参”、“形参”,实际上,方法传参的过程就是把实参赋值给对应的形参的过程,并且实参和形参的数量、类型必须匹配。
-
使用方法时的注意问题:
- 形参必须注明数据类型
- 实参直接写,不需要类型声明
- return只能返回一次
- 遇到return语句,方法结束执行,后续语句不执行
- 方法的返回值,必须与方法声明中的返回值类型匹配
- 方法定义,不能写在main()中
- 方法是不能嵌套的
方法调用示例:
public class Test {
/**
* 方法的调用分两种情况:
*
* 1:static修饰的方法: 类名.方法名(实际参数)
* 2. 非static修饰的方法:
* ①先把方法所在的类实例化: Test t = new Test();
* ②然后调用:t.isPrime2(17);//对象名.方法名(实际参数)
* 3.没有返回值的方法:
* 注意:方法调用完不要声明变量接收方法的返回值即可,因为没有返回值
*/
public static void main(String[] args) {
//1.判断213是不是质数
boolean result = Test.isPrime(17);
System.out.println(result);
//2.求出23和34的和
int tmp = Test.sum(23, 34);
System.out.println(tmp);
//3.判断17是不是质数,要求调用非static修饰的isPrime方法
Test t = new Test();
boolean result2 = t.isPrime2(17);
System.out.println("17是质数吗:"+result2);
//4.调用没有返回值的方法
Test.printName("telangpu");
}
}
方法的重载(overload)
方法的重载就是在同一个类中允许同时存在一个以上同名的方法
方法重载的规则:
- 方法名称相同;
- 被重载的方法必须改变参数列表(参数个数或类型不一样);
- 被重载的方法可以改变返回类型;
- 被重载的方法可以改变访问修饰符;
- 被重载的方法可以声明新的或更广的检查异常;
- 方法能够在同一个类中或者在一个子类中被重载。
- 无法以返回值类型作为重载函数的区分标准。
方法的重载示例:
/*
* 方法的重载就是在同一个类中允许同时存在一个以上同名的方法
* 这种情况的出现是有条件的:
*
* 1.方法名相同
* 2.一个不同,方法的参数必须不同:参数个数不同 or 参数类型不同
*/
public class Test {
public void add(int a) {
}
public void add(double b) {
}
public void add(float f) {
}
public void add(int a, int f) {
}
public int add(int c, int d, int e) {
return 0;
}
/*//这个方法定义在这是会编译报错的,因为不能根据方法返回值类型来重载方法
public double add(int c, int d, int e) {
return 0L;
}*/
}
方法的重写(override)
- 重写是子类对父类的允许访问的方法的实现过程进行重新编写,返回值和形参都不能改变。即 外壳不变,核心重写!
- 重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。
- 重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常。例如: 父类的一个方法申明了一个检查异常 IOException,但是在重写这个方法的时候不能抛出 Exception 异常,因为 Exception 是 IOException 的父类,只能抛出 IOException 的子类异常。
在面向对象原则里,重写意味着可以重写任何现有方法。实例如下:
class Animal {
public void move() {
System.out.println("动物可以移动");
}
}
class Dog extends Animal {
public void move() {
System.out.println("狗可以跑和走");
}
}
public class TestDog {
public static void main(String args[]) {
// Animal 对象
Animal a = new Animal();
// Dog 对象
Animal b = new Dog();
// 执行 Animal 类的方法
a.move();
//执行 Dog 类的方法
b.move();
}
}
在上面的例子中可以看到,尽管 b 属于 Animal 类型,但是它运行的是 Dog 类的 move方法。这是由于在编译阶段,只是检查参数的引用类型。然而在运行时,Java 虚拟机(JVM)指定对象的类型并且运行该对象的方法。
因此在上面的例子中,之所以能编译成功,是因为 Animal 类中存在 move 方法,然而运行时,运行的是特定对象的方法。
看以下代码:
class Animal {
public void move() {
System.out.println("动物可以移动");
}
}
class Dog extends Animal {
public void move() {
System.out.println("狗可以跑和走");
}
public void bark() {
System.out.println("狗可以吠叫");
}
}
public class TestDog {
public static void main(String args[]) {
// Animal 对象
Animal a = new Animal();
// Dog 对象
Animal b = new Dog();
// 执行 Animal 类的方法
a.move();
// 执行 Dog 类的方法
b.move();
b.bark();
}
}
该程序将抛出一个编译错误,因为 b 的引用类型 Animal 没有 bark 方法。
方法的重写规则:
- 参数列表必须完全与被重写方法的相同。
- 返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类(java5 及更早版本返回类型要一样,java7 及更高版本可以不同)。
- 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected。
- 父类的成员方法只能被它的子类重写。
- 声明为 final 的方法不能被重写。
- 声明为 static 的方法不能被重写,但是能够被再次声明。
- 子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法。
- 子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。
- 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
- 构造方法不能被重写。
- 如果不能继承一个方法,则不能重写这个方法。
Super 关键字的使用
当需要在子类中调用父类的被重写方法时,要使用 super 关键字。
使用示例:
class Animal{
public void move(){
System.out.println("动物可以移动");
}
}
class Dog extends Animal{
public void move() {
// 应用super类的方法
super.move();
System.out.println("狗可以跑和走");
}
}
public class TestDog {
public static void main(String args[]) {
// Dog 对象
Animal b = new Dog();
//执行 Dog类的方法
b.move();
}
}
重写与重载之间的区别
区别点 | 重载方法 | 重写方法 |
---|---|---|
参数列表 | 必须修改 | 一定不能修改 |
返回类型 | 可以修改 | 一定不能修改 |
异常 | 可以修改 | 可以减少或删除,一定不能抛出新的或者更广的异常 |
访问 | 可以修改 | 一定不能做更严格的限制(可以降低限制) |
总结
方法的重写(Overriding)和重载(Overloading)是java多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载可以理解成多态的具体表现形式。
- 方法重载是一个类中定义了多个方法名相同,而他们的参数的数量不同或数量相同而类型和次序不同,则称为方法的重载(Overloading)。
- 方法重写是在子类存在方法与父类的方法的名字相同,而且参数的个数与类型一样,返回值也一样的方法,就称为重写(Overriding)。
- 方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。