为什么我要重载方法?

38

我在我的书中发现了两个重载方法的例子,但它并没有清楚地解释为什么它是有用的:

package keepo;
public class Main{
    public static void main(String [] args)
    {
        int newScore = calculateScore("Tim", 500);
        System.out.println("New Score is" + newScore);
        calculateScore(75);

    }
    public static int calculateScore(String playerName, int score){
        System.out.println("Player" + playerName +"Has score" + score);
        return score * 1000;
    }
    public static int calculateScore(int score){
        System.out.println("Unnamed player scored" + score + "points");
        return score * 1000;
    }
}
这很简单,但老实说,在这里进行方法重载似乎相当无用,只是为了做而做。
书中的下一个例子使用了方法重载,这似乎更有用,因为该程序将英尺转换为厘米,并且有一种方法可以输入英尺和英寸,以及一种方法可以输入英寸。但是,对于此操作,制作两个单独的方法同样容易。
话虽如此,这样做有什么真正的好处吗? (我阅读了此文,但并不完全满意。制作新方法似乎同样容易。)

14
请看InputStream.read获取一些示例......或者是PrintWriter.println - Jon Skeet
3
这是“不同的方法”,只是一种工具,让你不必为它们发明独特的名称。例如,String.valueOf(int)和String.valueOf(float)看起来会像是String.valueOfInt(int)和String.valueOfFloat(float) - 这只是程序员不必要的工作,需要发明和记住这些名称。 - eckes
给定的示例有点牵强。在现实世界的场景中,更可能会为每个玩家分配一个默认名称,因为玩家“未命名”的可能性很混乱。然而,有很多真实世界的好的重载示例。一个很好的例子可能是list.add(e),它本质上等同于list.add(list.size(), e)。大多数情况下我们使用前者,但有时我们需要指定索引。 - Radiodef
可能是为什么要使用接口,多重继承与接口,接口的好处?的重复问题。 - HopefullyHelpful
我建议远离方法重载。这是我的一篇博客文章,详细阐述了为什么:方法重载的隐藏危险 - yegor256
6个回答

64

我认为,如果你谈论函数/方法重载的真正好处,这是你无法避免的,那么就像你在问题中指出的那样,你将找不到任何好处。

但它有什么用呢?我们来考虑这个例子。

假设我正在制作一个应用程序,通过姓名查找一个人,我声明并定义了一个方法。

public Person[] findPerson(String name)

现在我们有一个需求,需要通过一个人的出生日期来查找他,因此需要引入一个新的方法。

public Person[] findPerson_byDOB(Date date)

假设这种情况持续下去,我的应用程序会有这么多方法。

public Person[] findPerson(String name)
public Person[] findPerson_byDOB(Date date)
public Person[] findPerson_byAddress(Address address)
public Person[] findPerson_bySSN(SSN ssn)
public Person[] findPerson_byDepartment(Department department)
public Person[] findPerson_byMobile(Mobile mobile)

这只是其中的一部分;当我们被要求介绍多个参数时,它可以继续。

public Person[] findPerson_byMobileAndAddress(Mobile mobile, Address address)
public Person[] findPerson_byAddressAndDepartment(Address address, Department department)
public Person[] findPerson_byDOBAndDepartment(DOB dob, Department, department)
public Person[] findPerson_byAddressAndDOB(Address address, DOB dob)

还有许多许多...

虽然这可能有些夸张,但相信我,在制作实际的行业级应用程序时,我们可能会遇到像这样数百个方法的情况,最终我们将需要一个这些方法的目录,以了解它们的实际用途。

当我们不得不使用所有这些方法的名称时,实际上是一场噩梦。

然而,当所有参数都不同时,我们可以给函数相同的名称,这样真的很容易记住。

public Person[] findPerson(String name)
public Person[] findPerson(Date date)
public Person[] findPerson(Address address)
public Person[] findPerson(SSN ssn)
public Person[] findPerson(Department department)
public Person[] findPerson(Mobile mobile)
public Person[] findPerson(Mobile mobile, Address address)
public Person[] findPerson(Address address, Department department)
public Person[] findPerson(DOB dob, Department, department)
public Person[] findPerson(Address address, DOB dob)

正如David在他的回答中所指出的,我们都知道如何获得整数的String值;可能我们在某个地方读到过这个方法。

static String.valueOf(new Integer(1));

但是你知道还有多少其他方法使用相同的名称进行了重载吗?

static String.valueOf(boolean b)
static String.valueOf(char c)
static String.valueOf(char[] data)
static String.valueOf(double d)
static String.valueOf(float f)
static String.valueOf(int i)
static String.valueOf(long l)
static String.valueOf(Object obj)

好处是你不必记住它们所有。你甚至不需要猜测,因为它一直是相同的名称。


编辑 按照Namnodorel的建议进行

考虑一下PrintStream类的这个重载方法。

void println()
void println(boolean x)
void println(char x)
void println(char[] x)
void println(double x)
void println(float x)
void println(int x)
void println(long x)
void println(Object x)
void println(String x)

如果我们不得不写出以下这种阅读难度较高的句子,试想一下它将会是怎样的:

Just think about the readability if we had to write:
void println_emptyLine()
void println_boolean(boolean x)
void println_character(char x)
void println_characterArray(char[] x)
void println_double(double x)
void println_float(float x)
void println_integer(int x)
void println_long(long x)
void println_object(Object x)
void println_string(String x)

2
另一个常见的例子是System.out.println()。 - Namnodorel
3
好的!所以我想它更多是用于组织和清晰度的工具吧? - Muntasir Alam
1
是的,我们可以这么说! - Raman Sahasi
public Person[] findPerson(Mobile mobile) 这段代码重复了两次,那应该会编译错误吧? - Izkata
@Vld 我已经很久没有使用Java了;据我所知,它与Python一样使用最后一个定义,而不是实际上出错(如果是这种情况,在创建大量重载方法时要小心)。 - Izkata
显示剩余5条评论

22

当您需要使用不同类型的值执行相同操作时,重载很有用。

Math类提供了一个完美的例子 - 它按类型分组重载函数 - 四个abs,四个min,四个max等等:

int max(int a, int b) {...}
float max(float a, float b) {...}
long max(long a, long b) {...}
double max(double a, double b) {...}

如果不使用过载处理方式,你需要将类型“编码”到方法的名称中,例如Math.intMax(a, b),这会影响用户代码的可读性。


@cresjoy 请查看访问修饰符 - Sinkingpoint
额外的好处是:假设您编写了自己的类,并且由于某种原因定义该类的最大值是有意义的。由于重载,现在您只需编写 myObject max(myObject a, myObject b),而无需为 max 想出一个新名称。在没有重载的语言中,就像“糟糕,那个名称已经被占用了”。 - cadolphs

2
简而言之,重载只是Java(以及其他一些语言例如C++或C#)提供的一种可能性,允许开发人员为同一个方法/函数名称创建多个方法。为什么?因为方法命名很重要,应该传达它的行为。所以,如果两个方法具有相同的行为(例如将其转换为字符串),但其中一个使用长整型作为输入,而另一个使用整型作为输入,为什么要有不同的方法名称呢?
    String myString = String.valueOf(new Integer(1));
    String myOtherString = String.valueOf(new Long(2));


意图和目标是相同的,只是输入参数变化了。

当重载有意义时,应该使用它而不是创建一些变体笨拙的名称。


“大多数现代和灵活的语言都使用它”-需要引用。一些不提供方法重载的流行语言包括Python、Ruby、Rust、Haskell、OCaml等,这些语言通常提供其他解决方案,例如命名参数,可以解决不需要依赖静态分派的潜在问题。(快速思考:假设f是纯函数,在Java中如何使得x == yf(x) != f(y)解决方案 - wchargin
@wchargin 没错,我说了一些蠢话 :) 我更倾向于谈论我使用过的语言(C++、Java、C#)。 关于你的例子,这是一个预期的行为,因为纯函数概念不太友好于Java,因为Java并不依赖于函数式编程概念。 - davidxxx
@wchargin,我已经更正了。谢谢您的精准指导 :) - davidxxx

1

方法重载在以下场景中非常有用:

考虑您有一个跟踪名称列表的类。您采用上述代码样式,其中每个方法都有自己独特的操作方式来处理此名称列表。

突然,列表的内部表示发生改变(也许从 array 变为 ArrayList,这并不重要)。难道您想成为负责重构每个单独方法的人吗?

方法重载非常有用,因为您可以通过单个通用方法路由所有操作。这意味着每当内部表示更改时,您只需更改该通用方法,而所有其他专业方法仍然保持相同。


此外,请考虑提供的示例。如果您想更改程序打印消息的方式会发生什么?您将不得不修改两种方法以打印相同类型的消息,这是一场维护噩梦。当您的项目增长并且开始有更多方法依赖于这个(有效固定)消息格式时,请确保看到这一点。

所以在你的第一个方法中,你打印了一条消息,在你的第二个方法中也是如此。如果你想要标准化打印这些消息的方式,每次你改变方法1的打印方式,你都必须改变方法2的打印方式,除非你想保留方法2的旧方式。尝试维护许多方法会变得繁琐,这就是为什么在这种情况下重载很好的原因。 - Jeeter
2
但是我可以把我在第二个或第一个方法中编写的所有内容再创建一个方法,只需在方法名结尾加上"s",然后用那个名称调用它,什么都不会改变。 - Muntasir Alam

1

我是一个对象,我有一个能力,这个能力是固定的,但可以接受不同的参数。

如果这个能力可以接受1000种参数,你想费尽心思地想出1000个能力名称吗?

将其他人的帖子作为重载的良好实践,并将JNIEnv所做的作为不良实践,因为C不支持重载。

CallStaticObjectMethod,
CallStaticObjectMethodV,
CallStaticObjectMethodA,
CallStaticBooleanMethod,
CallStaticBooleanMethodV,
CallStaticBooleanMethodA,
CallStaticByteMethod,
CallStaticByteMethodV,
CallStaticByteMethodA,
CallStaticCharMethod,
CallStaticCharMethodV,
CallStaticCharMethodA,
CallStaticShortMethod,
CallStaticShortMethodV,
CallStaticShortMethodA,
CallStaticIntMethod,
CallStaticIntMethodV,
CallStaticIntMethodA,
CallStaticLongMethod,
CallStaticLongMethodV,
CallStaticLongMethodA,
CallStaticFloatMethod,
CallStaticFloatMethodV,
CallStaticFloatMethodA,
CallStaticDoubleMethod,
CallStaticDoubleMethodV,
CallStaticDoubleMethodA,
CallStaticVoidMethod,
CallStaticVoidMethodV,
CallStaticVoidMethodA,

请参考JNI以获取更详细的结构定义。


1
你的几个方法可能会做类似的事情,但其中一个需要4个参数,而另一个只需要3个参数 - 那又怎样呢?如果你调用add(5, 7, 56)add(5, 7, 56, 1),期望它们都会返回所有数字的总和。我想不出任何理由,需要一个add4things(int, int, int, int)add3things(int, int, int)方法。所有重载的方法应该做同样的事情,只是以稍微不同的方式实现。没有混淆。方法的签名是其合同的一部分,因此getAge(Tree tree)getAge(Frog frog)是明确无误的。 - VLAZ

1

过载的另一个原因是提供一个或多个默认参数。

考虑以下情况:

class Something {
    // Imagine that this is a real class, that does real work.

    public void doSomething(boolean b, char c, double d, int i) {
        // Imagine that this is one of the main components of class Something.
        System.out.println("Hi. You passed: " + b + ", " + c + ", " + d + ", and " + i + ".");
    }

    public void doSomething(boolean b, char c, double d) {
        doSomething(b, c, d, 42);
    }

    public void doSomething(boolean b, char c) {
        doSomething(b, c, 1.3579);
    }

    public void doSomething(boolean b) {
        doSomething(b, 'q');
    }

    public void doSomething() {
        doSomething(true);
    }
}

在这个例子中,每个重载函数都为其中一个参数提供了默认值,并将它们链接在一起,直到你得到一个完整的调用实际执行工作的 doSomething() 版本。
Something s = new Something();
Something t = new Something();
Something u = new Something();

// ...

s.doSomething(true, 'c', 2.9);
t.doSomething(false, 'z');
u.doSomething();

点击这里查看示例。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接