原文:http://zetcode.com/lang/java/methods/
在本教程的这一部分中,我们讨论 Java 方法。
在面向对象的编程中,我们使用对象。 对象是程序的基本构建块。 对象由数据和方法组成。 方法更改创建的对象的状态。 它们是对象的动态部分。 数据是静态部分。
Java 方法定义
方法是包含一系列语句的代码块。 方法必须在类中声明。 好的编程习惯是方法仅执行一项特定任务。 方法为程序带来了模块化。 正确使用方法具有以下优点:
- 减少代码重复
- 将复杂的问题分解成更简单的部分
- 提高代码的清晰度
- 重用代码
- 信息隐藏
Java 方法的特点
方法的基本特征是:
- 访问权限
- 返回值类型
- 方法名称
- 方法参数
- 括号
- 语句块
方法的访问级别由访问修饰符控制。 他们设置方法的可见性。 他们确定谁可以调用该方法。 方法可以将值返回给调用方。 如果我们的方法返回值,则声明其数据类型。 如果不是,则使用void
关键字指示我们的方法不返回任何值。 方法参数用括号括起来,并用逗号分隔。 空括号表示该方法不需要任何参数。 方法块周围带有{}
字符。 该块包含一个或多个在调用方法时执行的语句。 拥有一个空的方法块是合法的。
Java 方法签名
方法签名是 Java 编译器方法的唯一标识。 签名包含一个方法名称,以及每个形式参数的类型和种类(值,引用或输出)。 方法签名不包括返回类型。
Java 方法名称
可以在方法名称中使用任何合法字符。 按照约定,方法名称以小写字母开头。 方法名称是动词或动词,后跟形容词或名词。 随后的每个单词都以大写字母开头。 以下是 Java 中方法的典型名称:
excecute
findId
setName
getName
checkIfValid
testValidity
Java 方法示例
我们从一个简单的例子开始。
ShowInfoMethod.java
package com.zetcode;
class Base {
public void showInfo() {
System.out.println("This is Base class");
}
}
public class ShowInfoMethod {
public static void main(String[] args) {
Base bs = new Base();
bs.showInfo();
}
}
我们有一个showInfo()
方法,它打印其类的名称。
class Base {
public void showInfo() {
System.out.println("This is Base class");
}
}
每个方法必须在一个类中定义。 它必须有一个名字。 在我们的情况下,名称为showInfo
。 方法名称之前的关键字是访问说明符和返回类型。 括号跟随方法的名称。 它们可能包含方法的参数。 我们的方法没有任何参数。
public static void main(String[] args) {
...
}
这是main()
方法。 它是每个控制台或 GUI Java 应用的入口。 该方法采用参数的字符串数组。
Base bs = new Base();
bs.showInfo();
我们创建Base
类的实例。 我们在对象上调用showInfo()
方法。 我们说该方法是一个实例方法,因为它需要调用一个实例。 通过指定对象实例,成员访问运算符(点),方法名称,来调用该方法。
Java 方法参数
参数是传递给方法的值。 方法可以采用一个或多个参数。 如果方法使用数据,则必须将数据传递给方法。 这是通过在括号内指定它们来完成的。 在方法定义中,我们必须为每个参数提供名称和类型。
Addition.java
package com.zetcode;
class AddValues {
public int addTwoValues(int x, int y) {
return x + y;
}
public int addThreeValues(int x, int y, int z) {
return x + y + z;
}
}
public class Addition {
public static void main(String[] args) {
AddValues a = new AddValues();
int x = a.addTwoValues(12, 13);
int y = a.addThreeValues(12, 13, 14);
System.out.println(x);
System.out.println(y);
}
}
在上面的示例中,我们有一个AddValues
类,它具有两种方法。 其中一个带有两个参数,另一个带有三个参数。
public int addTwoValues(int x, int y) {
return x + y;
}
addTwoValues()
方法采用两个参数。 这些参数具有int
类型。 该方法还向调用者返回一个整数。 我们使用return
关键字从方法中返回一个值。
public int addThreeValues(int x, int y, int z) {
return x + y + z;
}
addThreeValues()
与先前的方法类似,但是它带有三个参数。
int x = a.addTwoValues(12, 13);
我们将AddValues
对象的addTwoValues()
方法调用。 它有两个值。 这些值将传递给方法。 该方法返回一个分配给x
变量的值。
可变数量的参数
从 Java 5 开始,方法可以采用可变数量的参数。 为此,我们使用省略号。
SumOfValues.java
package com.zetcode;
public class SumOfValues {
public static int sum(int...vals) {
int sum = 0;
for (int val : vals) {
sum += val;
}
return sum;
}
public static void main(String[] args) {
int s1 = sum(1, 2, 3);
int s2 = sum(1, 2, 3, 4, 5);
int s3 = sum(1, 2, 3, 4, 5, 6, 7);
System.out.println(s1);
System.out.println(s2);
System.out.println(s3);
}
}
我们创建一个sum()
方法,该方法可以使用可变数量的参数。 该方法计算传递给该方法的整数之和。
int s1 = sum(1, 2, 3);
int s2 = sum(1, 2, 3, 4, 5);
int s3 = sum(1, 2, 3, 4, 5, 6, 7);
我们多次调用sum()
方法。 在每种情况下,我们都将不同数量的参数传递给该方法。
public static int sum(int...vals) {
...
}
sum()
方法可以采用可变数量的整数值。 所有值都添加到数组中。
int sum = 0;
for (int val : vals) {
sum += val;
}
return sum;
我们计算值的总和,然后返回计算出的总和。
$ java com.zetcode.SumOfValues
6
15
28
这是com.zetcode.SumOfValues
示例的输出。
通过值传递参数
在 Java 中,参数总是按值传递给方法。 当我们传递原始类型时,值的副本将发送到方法。 如果是对象,则将引用的副本交给这些方法。
Java 不支持通过引用传递参数,例如 C# 或 C++ 。
PassByValue.java
package com.zetcode;
class Cat {}
class Dog {}
public class PassByValue {
private static void tryChangeInteger(int x) {
x = 15;
}
private static void tryChangeObject(Object o) {
Dog d = new Dog();
o = d;
}
public static void main(String[] args) {
int n = 10;
tryChangeInteger(n);
System.out.println(n);
Cat c = new Cat();
tryChangeObject(c);
System.out.println(c.getClass());
}
}
该示例表明,不可能在方法内部更改基本类型的值和对对象的引用。
private static void tryChangeInteger(int x) {
x = 15;
}
传递的变量的值将复制到局部变量x
。 为x
变量分配新值不会影响外部变量。
private static void tryChangeObject(Object o) {
Dog d = new Dog();
o = d;
}
同样适用于对象。 我们正在传递方法引用的副本。 o
是引用Dog
对象的局部变量。 tryChangeObject()
外部定义的对象不受影响。
int n = 10;
tryChangeInteger(n);
System.out.println(n);
我们定义n
变量并将其传递给tryChangeInteger()
方法。 稍后,我们打印它以检查它是否被修改。
Cat c = new Cat();
tryChangeObject(c);
System.out.println(c.getClass());
我们定义一个Cat
对象,并将其传递给tryChangeObject()
方法。
$ java com.zetcode.PassByValue
10
class com.zetcode.Cat
从输出中我们可以看到,原始值和对象都没有被修改。
Java 中的方法重载
方法重载允许创建多个名称相同但输入类型不同的方法。
方法重载有什么好处? Qt5 库提供了一个很好的用法示例。 QPainter
类具有三种绘制矩形的方法。 它们的名称为drawRect()
,其参数不同。 一个引用一个浮点矩形对象,另一个引用一个整数矩形对象,最后一个引用四个参数:x
,y
,width
和height
。 如果开发 Qt 的 C++ 语言没有方法重载,则库的创建者必须将其命名为drawRectRectF()
,drawRectRect()
和drawRectXYWH()
之类的方法。 方法重载的解决方案更为优雅。
Overloading.java
package com.zetcode;
class Sum {
public int getSum() {
return 0;
}
public int getSum(int x) {
return x;
}
public int getSum(int x, int y) {
return x + y;
}
}
public class Overloading {
public static void main(String[] args) {
Sum s = new Sum();
System.out.println(s.getSum());
System.out.println(s.getSum(5));
System.out.println(s.getSum(5, 10));
}
}
我们有三种方法setSum()
。 它们的输入参数不同。
public int getSum(int x) {
return x;
}
这一个参数。
System.out.println(s.getSum());
System.out.println(s.getSum(5));
System.out.println(s.getSum(5, 10));
我们调用这三种方法。 所有方法都具有相同的名称。 编译器根据方法输入知道要调用的方法。
$ java com.zetcode.Overloading
0
5
15
这就是我们运行示例时得到的。
Java 递归
在数学和计算机科学中,递归是一种定义方法的方法,其中所定义的方法在其自己的定义内应用。 换句话说,递归方法会调用自身来完成其工作。 递归是解决许多编程任务的一种广泛使用的方法。 使用递归解决的每个问题也可以通过迭代解决。
一个典型的例子是阶乘的计算。
Recursion.java
package com.zetcode;
public class Recursion {
static int factorial(int n) {
if (n == 0) {
return 1;
} else {
return n * factorial(n - 1);
}
}
public static void main(String[] args) {
System.out.println(factorial(6));
System.out.println(factorial(15));
}
}
在此代码示例中,我们计算两个数字的阶乘。
return n * factorial(n - 1);
在阶乘方法的主体内部,我们将阶乘方法称为经过修改的参数。 该函数调用自身。 这是递归算法的本质。
$ java com.zetcode.Recursion
720
2004310016
这些就是结果。
Java 方法作用域
在方法内部声明的变量具有方法作用域。 名称的作用域是程序的区域,在该区域中可以引用名称声明的实体,而无需使用名称限定。 在方法内部声明的变量具有方法作用域。 它也称为本地作用域。 该变量仅在此特定方法中有效。
MethodScope.java
package com.zetcode;
class Test {
int x = 1;
public void exec1() {
System.out.println(this.x);
System.out.println(x);
}
public void exec2() {
int z = 5;
System.out.println(x);
System.out.println(z);
}
}
public class MethodScope {
public static void main(String[] args) {
Test ts = new Test();
ts.exec1();
ts.exec2();
}
}
在此示例中,我们在实例方法外部定义了x
变量。 该变量具有类作用域。 它在Test
类的定义内的任何地方都有效,例如大括号之间。
public void exec1() {
System.out.println(this.x);
System.out.println(x);
}
x
变量(也称为x
字段)是一个实例变量。 可通过this
关键字进行访问。 它在exec1()
方法中也有效,并且可以用其裸名引用。 这两个语句都引用相同的变量。
public void exec2() {
int z = 5;
System.out.println(x);
System.out.println(z);
}
也可以在exec2()
方法中访问 x 变量。 z
变量在exec2()
方法中定义。 它具有方法范围。 仅在此方法中有效。
$ java com.zetcode.MethodScope
1
1
1
5
这是com.zetcode.MethodScope
程序的输出。
在方法内部定义的变量具有本地/方法范围。 如果局部变量与实例变量具有相同的名称,则它会遮盖实例变量。 实例变量仍然可以通过this
在方法内部访问。
Shadowing.java
package com.zetcode;
class Test {
int x = 1;
public void exec() {
int x = 3;
System.out.println(this.x);
System.out.println(x);
}
}
public class Shadowing {
public static void main(String[] args) {
Test t = new Test();
t.exec();
}
}
我们声明一个实例变量x
。 我们在exec()
方法中声明了另一个x
变量。 这两个变量具有相同的名称,但是它们没有冲突,因为它们位于不同的作用域中。
System.out.println(this.x);
System.out.println(x);
变量的访问方式不同。 在方法内部定义的x
变量,也称为局部变量x
,仅通过其名称即可访问。 可以使用this
来引用实例变量。
$ java com.zetcode.Shadowing
1
3
这是com.zetcode.Shadowing
示例的输出。
Java 静态方法
在没有对象实例的情况下调用静态方法。 要调用静态方法,我们使用类的名称和点运算符。 静态方法只能使用静态变量。 静态方法通常用于表示不会随对象状态变化的数据或计算。 数学库是一个示例,其中包含用于各种计算的静态方法。
我们使用static
关键字来声明静态方法或静态变量。 如果不存在静态修饰符,则该方法称为实例方法。 我们不能在静态方法中使用this
关键字; 它只能在实例方法中使用。
StaticMethod.java
package com.zetcode;
class Basic {
static int id = 2321;
public static void showInfo() {
System.out.println("This is Basic class");
System.out.format("The Id is: %d%n", id);
}
}
public class StaticMethod {
public static void main(String[] args) {
Basic.showInfo();
}
}
在我们的代码示例中,我们定义了静态ShowInfo()
方法。
static int id = 2321;
静态方法只能使用静态变量。 静态变量不适用于实例方法。
public static void showInfo() {
System.out.println("This is Basic class");
System.out.format("The Id is: %d%n", id);
}
这是我们的静态ShowInfo()
方法。 它与静态id
成员一起使用。
Basic.showInfo();
要调用静态方法,我们不需要对象实例。 我们通过使用类的名称和点运算符来调用该方法。
$ java com.zetcode.StaticMethod
This is Basic class
The Id is: 2321
这是示例的输出。
Java 隐藏方法
如果是静态方法,则派生类中具有与基类相同签名的方法会将其隐藏在基类中。 在编译时确定要调用的方法。 该过程称为早期或静态绑定。
Hiding.java
package com.zetcode;
class Base {
public static void showInfo() {
System.out.println("This is Base class");
}
}
class Derived extends Base {
public static void showInfo() {
System.out.println("This is Derived class");
}
}
public class Hiding {
public static void main(String[] args) {
Base.showInfo();
Derived.showInfo();
}
}
我们有两个类:Derived
和Base
。 Derived
类继承自Base
类。 两者都有一种称为showInfo()
的方法。
class Derived extends Base {
public static void showInfo() {
System.out.println("This is Derived class");
}
}
Derived
类的静态类方法showInfo()
隐藏了Base
类的showInfo()
方法。
Base.showInfo();
Derived.showInfo();
我们为这两个类都调用showInfo()
方法。 每个类都调用自己的方法。
$ java com.zetcode.Hiding
This is Base class
This is Derived class
这是com.zetcode.Hiding
示例的输出。
Java 覆盖方法
当我们创建派生类的实例方法具有与基类中的实例方法相同的签名和返回类型时,将发生覆盖。 在运行时确定要执行的方法。 确定将在运行时执行的方法称为或动态绑定。
我们可能想要使用@Override
注解,该注解指示编译器我们打算覆盖超类中的方法。 它有助于防止某些编程错误。
Overriding.java
package com.zetcode;
class Base {
public void showInfo() {
System.out.println("This is Base class");
}
}
class Derived extends Base {
@Override
public void showInfo() {
System.out.println("This is Derived class");
}
}
public class Overriding {
public static void main(String[] args) {
Base[] objs = { new Base(), new Derived(), new Base(),
new Base(), new Base(), new Derived() };
for (Base obj : objs) {
obj.showInfo();
}
}
}
我们创建Base
和Derived
对象的数组。 我们遍历数组并在所有数组上调用showInfo()
方法。
@Override
public void showInfo() {
System.out.println("This is Derived class");
}
在这里,我们将覆盖Base
类的showInfo()
方法。
Base[] objs = { new Base(), new Derived(), new Base(),
new Base(), new Base(), new Derived() };
在这里,我们创建Base
和Derived
对象的数组。 请注意,我们在数组声明中使用了Base
类型。 Derived
类可以转换为Base
类,因为它继承自它。 相反的说法是不正确的。 在一个数组中具有不同对象的唯一方法是使用所有对象都共享的类型。
for (Base obj : objs) {
obj.showInfo();
}
我们遍历数组,并在数组中的所有对象上调用showInfo()
。 在运行时确定要调用的方法。
$ java com.zetcode.Overriding
This is Base class
This is Derived class
This is Base class
This is Base class
This is Base class
This is Derived class
这是输出。
使用super
关键字,可以调用重写方法。
Overriding2.java
package com.zetcode;
class Base {
public void showInfo() {
System.out.println("This is Base class");
}
}
class Derived extends Base {
@Override
public void showInfo() {
System.out.println("This is Derived class");
}
public void showBaseInfo() {
super.showInfo();
}
}
public class Overriding2 {
public static void main(String[] args) {
Derived d = new Derived();
d.showBaseInfo();
}
}
在示例中,我们将Base
类的showInfo()
与super
一起调用。
public void showBaseInfo() {
super.showInfo();
}
在这里,我们称为直接父级的showInfo()
方法。
Java final
方法
final
方法不能被派生类覆盖或隐藏。 这用于防止子类的意外行为更改可能对类的功能或一致性至关重要的方法。
FinalMethods.java
package com.zetcode;
class Base {
public void f1() {
System.out.println("f1 of the Base");
}
public final void f2() {
System.out.println("f2 of the Base");
}
}
class Derived extends Base {
@Override
public void f1() {
System.out.println("f1 of the Derived");
}
// @Override
// public void f2() {
//
// System.out.println("f2 of the Derived");
// }
}
public class FinalMethods {
public static void main(String[] args) {
Base b = new Base();
b.f1();
b.f2();
Derived d = new Derived();
d.f1();
d.f2();
}
}
在此示例中,我们在Base
类中具有最终方法f2()
。 此方法不能被覆盖。
public final void f2() {
System.out.println("f2 of the Base");
}
f2()
方法被声明为final
。 不可能超载。
@Override
public void f1() {
System.out.println("f1 of the Derived");
}
在Derived
类中,我们可以覆盖Base
类的f1()
方法。 我们还使用@Override
注解通知编译器我们正在重写方法。
// @Override
// public void f2() {
//
// System.out.println("f2 of the Derived");
// }
这些行带有注释,因为否则代码示例将无法编译。 编译器将给出以下错误:线程main
中的异常java.lang.VerifyError
:com.zetcode.Derived
类将覆盖最终方法f2
。
d.f2();
由于不可能覆盖最终方法,因此以上行将调用Base
类的f2()
方法。
$ java com.zetcode.FinalMethods
f1 of the Base
f2 of the Base
f1 of the Derived
f2 of the Base
这是输出。
在 Java 教程的这一部分中,我们介绍了方法。