Java方法参数太多怎么办—Part4—重载

在Java编程中,如果一个方法带太多的参数被会给调用者带来很多困扰。调用者必须考虑是否按照正确的参数顺序给传入合适的值。在前面的文章中,先后探讨了通过自定义类型参数对象Builder模式来解决这个问题。还有一种方法,也是今天探讨的主题——通过方法重载来应对各种不同需求。与往常一样,在文章最后我会对重载的优缺点进行总结。

Java支持方法重载,可以通过方法签名来区分同名方法之间是否重载。请注意:方法返回值不能作为判断是否重载的依据(请大家注意“重写 Override”和“重载 Overload”之间的区别)。

实际编程中可能会有很多理由让你使用重载。其中一个是为不同类型实现同样的功能(特别是方法不适合通过泛型支持多种类型,或者编写代码的环境不支持泛型)。String.valueOf(boolean)String.valueOf(char)String.valueOf(double)String.valueOf(long)String.valueOf(java.lang.Object)以及其它类型String.valule()方法就是方法重载的一个简单示例。

选择重载的另一个原因是方便客户调用时“按需分配”。客户能根据需要的参数选择相应实现方法,这样可以避免传一个或多个null值或可选参数。例如java.util包中的Date类就使用重载方法,可供选择的构造函数多种多样,像是Date(int, int, int)Date(int, int, int, int, int)Date(int, int, int, int, int, int, int)等。

重载构造函数会产生多个不同的构造函数,每个构造函数接收的参数数量各不相同。这些构造函数又叫做重叠构造函数,一些人称之为“反模式”。事实上,在Effective Java第二版的第二条中,作者Josh Bloch提到:“之所以使用Builder模式,其中一个原因就是重叠构造器模式本身有缺陷。”顺便说一下,针对实现前面提到的目标,Date类也提供了一些重载的构造函数,支持从String类型构造Date

自己编写类时,可以通过重载构造函数和方法,只接收必需的参数或者尽可能减少必须的参数。下面的示例代码中,首先展示了一段问题代码——方法接受的参数过多;接下来展示了一些通过重载精简参数后的若干备选方案。为了便于讨论,我们假定重写过的方法签名中所有参数都是必须的,在调用时都必须填写。示例代码在注释中对方法重构过程中的一些假设进行了说明。

函数参数过多及重载改进示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/**
  * 生成Person实例。
  * 此方法要求初始化Person实例时,所有属性都必须有值。
  * 可选或不可用属性传null。
  *
  * @param lastName
  * @param firstName
  * @param middleName
  * @param salutation
  * @param suffix
  * @param streetAddress
  * @param city
  * @param state
  * @param isFemale
  * @param isEmployed
  * @param isHomeOwner
  * @return A Person object.
  */
public Person createPerson(
    final String lastName,
    final String firstName,
    final String middleName,
    final String salutation,
    final String suffix,
    final String streetAddress,
    final String city,
    final String state,
    final boolean isFemale,
    final boolean isEmployed,
    final boolean isHomeOwner)
{
    // implementation goes here...
}
 
/**
  * 生成Person实例。
  * Person初始化必须提供姓名和地址信息。
  * 对其它没有初始化的Person属性不赋初值。
  *
  * @param lastName
  * @param firstName
  * @param streetAddress
  * @param city
  * @param state
  * @return 返回的Person实例对中间名、性别、雇员状态或房屋所有权均不赋初值。
  */
public Person createPerson(
    final String lastName,
    final String firstName,
    final String streetAddress,
    final String city,
    final String state)
{
    // implementation goes here...
}
 
/**
  * 生成的Persn实例没有中间名但指定了房屋所有权状态。
  * 该方法返回的所有Person实例都假定为已雇佣女性,但不提供地址信息。
  *
  * @param lastName
  * @param firstName
  * @param homeOwnerStatus
  * @return Person实例,包含姓名和房屋所有权状态,并且假定为已雇佣女性、不包含地址信息。
  */
public Person createPerson(
    final String lastName,
    final String firstName,
    final boolean homeOwnerStatus)
{
    // implementation goes here...
}

示例代码中的注释说明了各方法间的区别。第一个方法要求初始化Person实例时,所有属性都必须赋值。如果碰到有一些可选参数或参数不可用,参数传入null(比如这个人可能没有中间名middle name,或者某种情况下无需考虑中间名)。第二种方法重载不要求为所有属性赋值,返回的Person实例对没有设置的参数不进行赋值。

第三种方法重载的主要特点是一些属性不提供参数赋值。例如,方法假定实例化的Person都是女性且都是雇员。然而,这三种方法不能实例化一个男性或没有雇佣关系的Person。这从侧面说明了简单地使用方法重载来处理参数过多问题的一个短板(即只能通过参数个数和不同类型重载名称相同方法,这里请注意与重写Override区别)。

虽然示例代码列中没有提到构造函数,但其中的观点和方法对构造函数同样适用。同样地,构造函数重载和方法重载具有共同的优点与不足。

重载的好处和优点

在Java中进行方法重载看起来很好理解,在其它的一些编程语言比如C/C++C#中,方法重载也非常普遍。特别是遇到可选参数时,使用方法重载会十分有效。在上面的示例中,使用方法重载移除中间名参数要比假定为所有雇员都是女性更好。如果中间名、性别、雇佣情况确实可选,最好不要多此一举为它们设定特殊默认值。

重载的代价和缺点

适当的方法重载非常有用,但同时使用起来也要格外小心。 Learning the Java Language trail系列教程中对类和对象课程方法定义提到:要尽量少用方法重载,避免降低代码的可读性。

甚至在上面三个简单示例中也可以发现,方法重载会明显降低代码的可读性。在前文的示例中,开发者在阅读和使用代码时一方面要认真阅读并且准确理解注释,另一方面要深入到方法实现中才能发现各重载方法的区别。对编译器而言,如果存在多个版本的重载方法很难做到“具体问题具体分析”。

在示例代码中,方法注释必须清楚地解释每个重载方法进行的假设。正如刚才说的那样,如果代码的作者不屑于写好注释,那么这些重载的方法可能过时、不准确以致让别人不知所云。显然,为不同的方法取不同的名字效果会更好,至少方法名可以为理解方法的功能提供一些线索,而不仅仅只依靠注释。使用不同的方法名会在后面的文章中进行讨论。

代码示例中也体现了多个相同类型参数的重载方法的一个严重限制。第三个方法只提供了一个boolean参数,只有通过阅读注释和参数名才能知道这个方法只关心是否拥有房产,并不关心性别和雇佣状态。在只提供相同的方法名时,想要通过boolean参数代表Person的性别、雇佣情况等其他特征是不可能的。这种情况下,由于方法签名相同使用方法重载是行不通的。这再一次表明,必须采用不同的方法名来具体区分boolean参数要表达的隐含内容。

突破方法重载限制的另一个方法是单独或统一使用自定义类型和参数对象,提供各种版本的重载方法来接收不同的自定义类型组合。下面的示例代码展示了如何使用自定义类型进行方法重载。这些重载方法接收两个string参数和一个适用于三种情况的自定义类型参数,这样就不必使用相同的boolean参数了。

使用自定义类型改进方法、构造函数重载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public Person createPerson(
    final String lastName,
    final String firstName,
    final HomeownerStatus homeOwnership)
{
    // implementation goes here...
}
 
public Person createPerson(
    final String lastName,
    final String firstName,
    final Gender gender)
{
    // implementation goes here...
}
 
public Person createPerson(
    final String lastName,
    final String firstName,
    final EmploymentStatus employmentStatus)
{
    // implementation goes here...
}

关于方法重载的缺点,我想再补充一下。使用方法重载应对有构造函数或方法参数过多会带来大量的维护工作。在任何情况下,类(构造函数)属性或方法参数的增加、删除甚至是改变都会带来额外的审查或代码变更。

总结

方法重载确实有其适用的地方,并且可以提高方法和构造函数的可读性。但是在我看来,方法重载不及前面几篇中(自定义类型参数对象Builder模式)提到的方法,比起即将要介绍的一些方法(比如方法命名)也用得更少。通过结合其他方法可以改善方法重载的一些限制和不足,比如使用自定义类型和参数对象能够显著改善重载方法或构造函数对细粒度问题的处理能力。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值