本文由 ImportNew - 王村平 翻译自 dzone。如需转载本文,请先参见文章末尾处的转载要求。

本文是这个系列的第一篇文章,介绍了采用自定义类型处理参数过多的问题。如果你也希望参与类似的系列文章翻译,可以加入我们的Android开发 和 技术翻译 小组。

我认为构造函数和方法过长的传递参数列表是一种红色警告(”red
flag
“)。在开发过程中,从逻辑的和功能的角度来看并非错误,但是通常意味着现在或者将来犯错误的可能性更高。通过阅读一系列文章,我发现一些解决参数列表过长的办法,或者至少这些办法可以减少参数个数、增强代码的可读性并降低发生错误的概率。任何解决问题的办法都具有优点和缺点。本文旨在通过使用自定义类型改进长参数方法和构造函数代码的可读性和安全性。

方法和构造函数的参数列表过长会产生一系列的障碍。大量的参数不仅使得代码看起来冗余,而且使得调用起来会很困难。同时,它又容易导致因疏忽而产生的参数移位(参数类型没变,但是因为位置改变值却改变了)。这些错误在特定情况下难以发现。幸运地是大多时候我们不必处理另一个参数过长的缺点:Java虚拟机(JVM)通过编译时报告错误(compile-time
error
限制了方法的参数数量

使用自定义类型一方面可以减少构造函数和方法的传参个数,另一方面又可以增强参数列表的可读性并且降低参数位置放错的可能性。自定义类型的实现方式包括Data
Transfer Objects
JavaBeansValue
Objects
Reference
Objects
或者其他(在Java中经典的实现方式:枚举)自定义类型。

下面来看一个例子,该方法包含多个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
24
25
26
27
28
29
30
31
/**
   *
Instantiate a Person object.
   *
   *
@param lastName
   *
@param firstName
   *
@param middleName
   *
@param salutation
   *
@param suffix
   *
@param streetAddress
   *
@param city
   *
@param state
   *
@param isFemale
   *
@param isEmployed
   *
@param isHomeOwner
   *
@return
   */
  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
  }

可以发现很容易搞混参数的顺序,然后把它们放错位置。我通常更乐意通过改变参数类型来做一些提高,以期减少参数个数。下面这些代码展示了如何使用自定义类型。

三个名字可以改为自定义类型Name,而不是使用String。

Name.java

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
package

dustin.examples;
 
/**
 *
Name representation.
 *
 *
@author Dustin
 */
public

final

class

Name
{
   private

final

String name;
 
   public

Name(
final

String newName)
   {
      this.name
= newName;
   }
 
   public

String getName()
   {
      return

this
.name;
   }
 
   @Override
   public

String toString()
   {
      return

this
.name;
   }
}

称呼和后缀也可以替换为如下两段代码所示的自定义类型:

Salutation.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package

dustin.examples;
 
/**
 *
Salutations for individuals' names.
 *
 *
@author Dustin
 */
public

enum

Salutation
{
   DR,
   MADAM,
   MISS,
   MR,
   MRS,
   MS,
   SIR
}

Suffix.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package

dustin.examples;
 
/**
 *
Suffix representation.
 *
 *
@author Dustin
 */
public

enum

Suffix
{
   III,
   IV,
   JR,
   SR
}

其他的代码也可使用自定义类型。下面通过枚举的方式替换boolean型,以提高代码的可读性:

Gender.java

1
2
3
4
5
6
7
8
9
10
11
12
package

dustin.examples;
 
/**
 *
Gender representation.
 *
 *
@author Dustin
 */
public

enum

Gender
{
   FEMALE,
   MALE
}

EmploymentStatus.java

1
2
3
4
5
6
7
8
9
10
11
12
package

dustin.examples;
 
/**
 *
Representation of employment status.
 *
 *
@author Dustin
 */
public

enum

EmploymentStatus
{
   EMPLOYED,
   NOT_EMPLOYED
}

HomeOwnerStatus.java

1
2
3
4
5
6
7
8
9
10
11
12
package

dustin.examples;
 
/**
 *
Representation of homeowner status.
 *
 *
@author Dustin
 */
public

enum

HomeownerStatus
{
   HOME_OWNER,
   RENTER
}

此人的地址信息可以通过如下代码所示的自定义类型作为参数进行传递。

StreetAddress.java

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
package

dustin.examples;
 
/**
 *
Street Address representation.
 *
 *
@author Dustin
 */
public

final

class

StreetAddress
{
   private

final

String

address;
 
   public

StreetAddress(
final

String

newStreetAddress)
   {
      this.address
= newStreetAddress;
   }
 
   public

String

getAddress()
   {
      return

this
.address;
   }
 
   @Override
   public

String

toString()
   {
      return

this
.address;
   }
}

City.java

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
package

dustin.examples;
 
/**
 *
City representation.
 *
 *
@author Dustin
 */
public

final

class

City
{
   private

final

String cityName;
 
   public

City(
final

String newCityName)
   {
      this.cityName
= newCityName;
   }
 
   public

String getCityName()
   {
      return

this
.cityName;
   }
 
   @Override
   public

String toString()
   {
      return

this
.cityName;
   }
}

State.java

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
package

dustin.examples;
 
/**
 *
Simple representation of a state in the United States.
 *
 *
@author Dustin
 */
public

enum

State
{
   AK,
   AL,
   AR,
   AZ,
   CA,
   CO,
   CT,
   DE,
   FL,
   GA,
   HI,
   IA,
   ID,
   IL,
   IN,
   KS,
   KY,
   LA,
   MA,
   MD,
   ME,
   MI,
   MN,
   MO,
   MS,
   MT,
   NC,
   ND,
   NE,
   NH,
   NJ,
   NM,
   NV,
   NY,
   OH,
   OK,
   OR,
   PA,
   RI,
   SC,
   SD,
   TN,
   TX,
   UT,
   VA,
   VT,
   WA,
   WI,
   WV,
   WY
}

通过使用自定义类型,开始展示的方法的代码变得更加易读易懂,同时更不容易出错了。下面是使用自定义类型改写后的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public

Person createPerson(
   final

Name lastName,
   final

Name firstName,
   final

Name middleName,
   final

Salutation salutation,
   final

Suffix suffix,
   final

StreetAddress address,
   final

City city,
   final

State state,
   final

Gender gender,
   final

EmploymentStatus employment,
   final

HomeownerStatus homeowner)
{
   //
implementation goes here
}

在上面这段代码中,编译器会不允许使用先前那种很多String、boolean参数的方式。这样来减少了放错参数顺序的可能性,从而帮助到开发者。但是三个名字仍然存在一个潜在的问题,因为代码的调用者依然容易搞乱它们的顺序。出于这种担心,需要为此专门定义FirstName、LastName、MiddleName 三种类型。但通常我喜欢使用一个自定义类型,里面放置上述三个名字作为新类的属性。当然那属于后来即将讲解的解决Java参数过长问题的文章的内容了。

使用自定义类型的好处和优点

提高了代码的可读性,为代码的维护者和API调用者提供了便利。提高了在编写代码时开发环境(IDE)参数匹配的能力,没有什么比使用自定义类型的编译环境检查更能帮助开发环境的了。通常来说,我更喜欢尽可能地把这些自动检查由运行环境移到编译环境,并且把他们定义为可直接调用的静态类型而非普通类型。

进一步说,自定义类型的存在也使我们将来为它添加更多细节变得更加容易。例如:将来我也许会为方法创建人添加一个全名或者把其他的状态放在枚举器当中,同时这样也不会改变自定义类型的原有面貌。这些都是使用String类型无法完成的。这样做的另一个潜在优点就是使用自定义类型拥有更大的灵活性,可以在不改变原有类型面貌的情况下改变实现方式。这些自定义类型(不包括枚举器)能够被扩展(String则不具备),并且可以在不改变它的类型的情况下灵活添加自定义细节。

自定义类型的代价和缺点

普遍存在缺点之一,就是开始需要额外的实例化和占用内存。例如:Name类需要实例化,而且封装了String。我认为之所以有人会持有这种观点,更多的是因为他从一种尚不够成熟的所谓的最优化角度,而非实用合理的角度来衡量性能问题。当然也有这种情况存在,即:额外实例化这些类型花费了太多的代价并且不能证明增强可读性和编译能力所带来的好处。然而大多时候这种额外的开销都是可以承受的,不会产生什么可见的坏影响。如果大多数时候使用自定义类型而不用String或者boolean会产生性能问题,这真的让人难以置信。

另一些人认为使用自定义类型而非内置类型需要付出额外的写和测试代码的努力。然而,正如我的这篇文章的代码所显示的那样,这些非常简单的类和枚举器写和测试起来并不难。使用一个优秀的开发环境和一门灵活的编程语言(如:Groovy),编写和测试会更加容易而且通常可以自动完成。

结论

我喜欢使用自定义类型来提高代码的可读性,将更多的编译检查负担转给编译器。我不喜欢这种传参方式的最大原因在于:这种方法本身只是提高了拥有过长参数列表的构造函数和方法的可读性却并没有减少实际需要传递的参数数量,代码的调用者依然需要写那些笨拙的客户端代码来调用构造函数和方法。因此,我通常使用其它技术而不是增加自定义类型来解决向方法传递参数过长的问题。这些技术将在接下来的文章里讲述。

原文链接: dzone 翻译: ImportNew.com王村平

译文链接: http://www.importnew.com/6518.html

[ 转载请保留原文出处、译者和译文链接。]

Java方法参数太多怎么办—Part 1—自定义类型的更多相关文章

  1. java方法参数传递方式只有----值传递!

    在通常的说法中,方法参数的传递分为两种,值传递和引用传递,值传递是指将实际参数复制一份传递到方法中, 在方法中的改动将不会影响到实际参数本身,而引用传递则是指传递的是实际参数本身,在方法中的改动将会影 ...

  2. java方法参数(超详细)

    前言 在上一篇文章中,壹哥给大家讲解了方法的定义.调用和返回值,但方法的内容还有很多,比如方法的参数是怎么回事?接下来壹哥会在这篇文章中,继续给大家讲解方法参数相关的知识,这就是我们今天要学习的内容. ...

  3. java方法参数

    Java程序设计语言总是采用值调用.也就是说,方法得到的是所有参数的一个拷贝,特别是方法不能修改传递给它的任何参数变量的内容. 基本类型参数 1)X被初始化为percent值的一个拷贝: 2)X被乘以 ...

  4. java 方法参数-值调用,引用调用问题

    (博客内容来自于core java卷一) 1. xx调用:程序设计语言中方法参数的传递方式: 引用调用(call by reference):表示方法接收的是调用者提供的变量地址. 值调用(call ...

  5. Java方法参数的传递方式

    程序设计语言中,将参数传递给方法(或函数)有两种方法.按值传递(call by value)表示方法接受的是调用者提供的值:按引用调用(call by reference)表示方法接受的是调用者提供的 ...

  6. 【转】java方法参数传递方式--按值传递、引用传递

    java的方法参数传递方式有两种,按值传递和引用传递 1.按值传递 参数类型是int,long等基本数据类型(八大基本数据类型),参数传递的过程采用值拷贝的方式 代码片段1: public class ...

  7. 辨析Java方法参数中的值传递和引用传递

    小方法大门道 小瓜瓜作为一个Java初学者,今天跟我说她想通过一个Java方法,将外部变量通过参数传递到方法中去,进行逻辑处理,方法执行完毕之后,再对修改过的变量进行判断处理,代码如下所示. publ ...

  8. java 方法参数的执行顺序

    java方法的参数的执行顺序是从左到右还是从右到左呢? 写出一下测试程序: 1 import java.util.*; 2 import java.io.*; 3 public class Test ...

  9. Java方法参数:

    一个方法不能修改一个基本数据类型的参数 一个方法可以改变一个对象参数的状态 一个方法不能实现让对象参数引用一个新的对象 案例1: 一个方法不能修改一个基本数据类型的参数 String a = &quo ...

  10. Java方法调用机制

    最近在编程时,修改方法传入对象的对象引用,并没有将修改反映到调用方法中.奇怪为什么结果没有变化,原因是遗忘了Java对象引用和内存分配机制.本文介绍3个点: ① 该问题举例说明 ② 简要阐述Java内 ...

随机推荐

  1. Falcon Mamba: 首个高效的无注意力机制 7B 模型

    Falcon Mamba 是由阿布扎比的 Technology Innovation Institute (TII) 开发并基于 TII Falcon Mamba 7B License 1.0 的开放 ...

  2. vue 消息订阅与发布

    vue 消息订阅与发布 一.场景 vue中非父子组件之间通信时,使用vuex有时间会很麻烦,这时候可以通过bus总线来实现 消息的订阅与发布 二.实现方法 1.main.js //main.js Vu ...

  3. Navicat Premium 12的下载和安装

    首先下载navicate程序以及破解文件,这里一并存到了百度网盘直接下载即可 链接:https://pan.baidu.com/s/1a2QKHHUZ0ej1D78CjQ1ldA提取码:klus 解压 ...

  4. skywalking/8.5部署

    制作镜像 OAP镜像 # cat oap/Dockerfile FROM apache/skywalking-oap-server:8.5.0-es7 # 时区修改为东八区 RUN apk add - ...

  5. NIO实现聊天室之:一切都要从网络编程的基础开始聊起!

    一.写在开头 大家好,Build哥回来啦!停更了大概2个月之久,之前有段时间去写小说去了,后来又因为公司活太多,牛马干的太投入,就拉下了博客的更新,国庆节期间,难得的闲下来,准备回归老本行啦. 大致的 ...

  6. eUSB是什么/可以干什么?

    eUSB总结 1.什么是eUSB 1.1 eUSB概念 eUSB是原USB物理层上的补充,为了解决USB_phy低压需求的问题而出现的,eUSB可以将信号电平降至1.2V甚至更低,与此同时可以优化电源 ...

  7. 技术分享 | 徐轶韬:从MySQL5.7升级到MySQL 8.0

    在6月20日举办的[墨天轮数据库沙龙-MySQL 5.7 停服影响与应对方案]中,甲骨文MySQL解决方案首席工程师徐轶韬分享了<从MySQL5.7升级到MySQL 8.0>主题演讲,本文 ...

  8. kotlin函数和Lambda表达式——>函数

    函数: 1.函数声明 kotlin中的函数使用fun关键字声明: fun double(x: Int): Int { return 2 * x } 2.函数用法 调用函数使用传统的方法: val re ...

  9. C# .netcore NPOI库 实现报表的列自适应删减

    实际需求:业务上的一个需求,数据库表A中的B字段存放的是该条数据的一些标签,标签存在两级[即一级标签和二级标签], 现在要是实现将这些标签统计到报表中,一级标签作为表头,二级标签作为填充值. 由于之前 ...

  10. 云原生周刊:Kubernetes v1.29 新特性一览 | 2023.12.11

    开源项目推荐 kubedog Kubedog 是一个用于在 CI/CD 部署管道中监视和跟踪 Kubernetes 资源的库. 这个库被用于 werf CI/CD 工具中,在部署过程中跟踪资源. Ru ...