ZetCode

Java 方法

最后修改于 2024 年 1 月 27 日

本文介绍 Java 方法。

在面向对象编程中,我们使用对象。对象是程序的基本构建块。对象由数据和方法组成。方法改变所创建对象的状态。它们是对象的动态部分;数据是静态部分。

Java 方法定义

方法是一个包含一系列语句的代码块。方法必须在类中声明。良好的编程习惯是让方法只执行一个特定任务。方法为程序带来模块化。正确使用方法可以带来以下优点:

Java 方法特性

方法的基本特性包括:

方法的访问级别由访问修饰符控制。它们设置方法的可见性。它们确定谁可以调用该方法。方法可以向调用者返回值。如果我们的方法返回值,我们声明其数据类型。否则,我们使用 void 关键字来表明我们的方法不返回任何值。

方法参数用括号括起来,并用逗号分隔。空括号表示该方法不需要参数。方法块用 {} 字符括起来。该块包含在方法被调用时执行的一个或多个语句。拥有一个空方法块是合法的。

Java 方法签名

方法签名是 Java 编译器对方法的唯一标识。签名由方法名称以及每个形式参数的类型和种类(值、引用或输出)组成。方法签名不包括返回类型。

Java 方法名称

任何合法字符都可以用于方法名称。按照惯例,方法名称以小写字母开头。方法名称是动词或动词后跟形容词或名词。每个后续单词以大写字符开头。以下是 Java 中方法的典型名称:

Java 方法示例

我们从一个简单的示例开始。

com/zetcode/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 方法参数

参数是传递给方法的值。方法可以接受一个或多个参数。如果方法使用数据,我们必须将数据传递给方法。这是通过在括号内指定它们来完成的。在方法定义中,我们必须为每个参数提供名称和类型。

com/zetcode/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 变量。

可变数量的参数

一个方法可以接受可变数量的参数。为此,我们使用省略号。

com/zetcode/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

按值传递参数

在 Java 中,参数始终按值传递给方法。当我们传递原始类型时,值的副本被发送到方法。对于对象,引用的副本被传递给方法。

Java 不支持像 C# 或 C++ 那样按引用传递参数。

com/zetcode/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 中的方法重载

方法重载允许创建几个具有相同名称但在输入类型上不同的方法。

方法重载有什么好处?Qt 库给出了一个很好的使用示例。QPainter 类有三种方法来绘制矩形。它们的名称是 drawRect,它们的参数不同。一个接受对浮点矩形对象的引用,另一个接受对整数矩形对象的引用,最后一个接受四个参数:xywidthheight

如果 Qt 开发所用的语言 C++ 没有方法重载,那么该库的创建者将不得不像 drawRectRectFdrawRectRectdrawRectXYWH 这样命名方法。使用方法重载的解决方案更优雅。

com/zetcode/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 递归

在数学和计算机科学中,递归是一种定义方法的方式,其中所定义的方法在其自身的定义中被应用。换句话说,递归方法调用自身来完成其工作。递归是一种广泛使用的方法来解决许多编程任务。可以使用递归解决的每个问题也可以使用迭代来解决。

一个典型的例子是计算阶乘。

com/zetcode/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 方法作用域

在方法内部声明的变量具有方法作用域。名称的作用域是指程序中可以引用由该名称声明的实体,而无需限定该名称的区域。在方法内部声明的变量具有方法作用域。它也称为局部作用域。该变量仅在此特定方法中有效。

com/zetcode/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

在方法内部定义的变量具有局部/方法作用域。如果局部变量与实例变量具有相同的名称,则它会遮蔽实例变量。实例变量仍然可以使用 this 在方法内部访问。

com/zetcode/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)只需通过其名称即可访问。实例变量可以使用 this 来引用。

$ java com.zetcode.Shadowing
1
3

Java 静态方法

静态方法在没有对象实例的情况下被调用。要调用静态方法,我们使用类的名称和点运算符。静态方法只能使用静态变量。静态方法通常用于表示不响应对象状态而改变的数据或计算。一个例子是数学库,它包含用于各种计算的静态方法。

我们使用 static 关键字来声明静态方法或静态变量。如果没有静态修饰符,则该方法被称为实例方法。我们不能在静态方法中使用 this 关键字;它只能在实例方法中使用。

com/zetcode/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 隐藏方法

对于静态方法,派生类中具有与基类中相同签名的方法会隐藏基类中的方法。要调用的方法在编译时确定。此过程称为早期静态绑定

com/zetcode/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();
    }
}

我们有两个类:DerivedBaseDerived 类继承自 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

Java 重写方法

当我们在派生类中创建一个具有与基类中的实例方法相同签名和返回类型的实例方法时,就会发生重写。要执行的方法在运行时确定。在运行时确定要执行的方法称为延迟动态绑定

我们可能想使用 @Override 注释,该注释指示编译器我们打算重写超类中的方法。它有助于防止一些编程错误。

com/zetcode/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();
        }
    }
}

我们创建一个 BaseDerived 对象的数组。我们遍历该数组并在所有对象上调用 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() };

在这里,我们创建一个 BaseDerived 对象的数组。请注意,我们在数组声明中使用了 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 关键字,可以调用重写的方法。

com/zetcode/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();
    }
}

在此示例中,我们使用 super 调用 Base 类的 showInfo

public void showBaseInfo() {

    super.showInfo();
}

这里我们调用直接父类的showInfo方法。

Java final 方法

一个 final 方法不能被派生类覆盖或隐藏。 这用于防止子类更改可能对类的功能或一致性至关重要的方法而导致意外行为。

com/zetcode/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 类中有一个 final 方法 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 覆盖了 final 方法 f2。

d.f2();

由于无法覆盖 final 方法,因此上面的行将调用 Base 类的 f2 方法。

$ java com.zetcode.FinalMethods
f1 of the Base
f2 of the Base
f1 of the Derived
f2 of the Base

来源

Java 定义方法 - 教程

在本文中,我们介绍了 Java 方法。

作者

我叫 Jan Bodnar,是一位充满热情的程序员,拥有丰富的编程经验。 自 2007 年以来,我一直在撰写编程文章。 迄今为止,我已经撰写了 1,400 多篇文章和 8 本电子书。 我拥有超过十年的编程教学经验。

列出所有Java教程