From c5edbe94ddd4dae80de3ad930b612f3d8e86563e Mon Sep 17 00:00:00 2001 From: sjsdfg <736777445@qq.com> Date: Wed, 20 Mar 2019 14:06:58 +0800 Subject: [PATCH] =?UTF-8?q?53.=20=E6=98=8E=E6=99=BA=E5=AE=A1=E6=85=8E?= =?UTF-8?q?=E5=9C=B0=E4=BD=BF=E7=94=A8=E5=8F=AF=E5=8F=98=E5=8F=82=E6=95=B0?= =?UTF-8?q?.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/notes/53. 明智审慎地使用可变参数.md | 67 ++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 docs/notes/53. 明智审慎地使用可变参数.md diff --git a/docs/notes/53. 明智审慎地使用可变参数.md b/docs/notes/53. 明智审慎地使用可变参数.md new file mode 100644 index 0000000..ab7eb3e --- /dev/null +++ b/docs/notes/53. 明智审慎地使用可变参数.md @@ -0,0 +1,67 @@ +# 53. 明智审慎地使用可变参数 + +  可变参数方法正式名称称为可变的参数数量方法『variable arity methods』 [JLS, 8.4.1],接受零个或多个指定类型的参数。 可变参数机制首先创建一个数组,其大小是在调用位置传递的参数数量,然后将参数值放入数组中,最后将数组传递给方法。 + +  例如,这里有一个可变参数方法,它接受一系列 int 类型的参数并返回它们的总和。如你所料, `sum(1,2,3)` 的值为 6, `sum()` 的值为 0: + +```java +// Simple use of varargs +static int sum(int... args) { + int sum = 0; + for (int arg : args) + sum += arg; + return sum; +} +``` + +  有时,编写一个需要某种类型的一个或多个参数的方法是合适的,而不是零或更多。 例如,假设要编写一个计算其多个参数最小值的方法。 如果客户端不传递任何参数,则此方法定义不明确。 你可以在运行时检查数组长度: + +``` +// The WRONG way to use varargs to pass one or more arguments! +static int min(int... args) { + if (args.length == 0) + throw new IllegalArgumentException("Too few arguments"); + int min = args[0]; + for (int i = 1; i < args.length; i++) + if (args[i] < min) + min = args[i]; + return min; +} +``` + +  该解决方案存在几个问题。 最严重的是,如果客户端在没有参数的情况下调用此方法,则它在运行时而不是在编译时失败。 另一个问题是它很难看。 必须在 args 参数上包含显式有效性检查,除非将 `min` 初始化为 `Integer.MAX_VALUE`,否则不能使用 for-each 循环,这也很难看。 + +  幸运的是,有一种更好的方法可以达到预期的效果。 声明方法采用两个参数,一个指定类型的普通参数,另一个此类型的可变参数。 该解决方案纠正了前一个示例的所有缺陷: + +``` +// The right way to use varargs to pass one or more arguments +static int min(int firstArg, int... remainingArgs) { + int min = firstArg; + for (int arg : remainingArgs) + if (arg < min) + min = arg; + return min; +} +``` + +  从这个例子中可以看出,在需要参数数量可变的方法时,可变参数是有效的。可变参数是为 `printf` 方法而设计的,该方法与可变参数同时添加到 Java 平台中,以及包括经过改造的核心反射机制。`printf` 和反射机制都从可变参数中受益匪浅。 + +  在性能关键的情况下使用可变参数时要小心。每次调用可变参数方法都会导致数组分配和初始化。如果你从经验上确定负担不起这个成本,但是还需要可变参数的灵活性,那么有一种模式可以让你鱼与熊掌兼得。假设你已确定 95% 的调用是三个或更少的参数的方法,那么声明该方法的五个重载。每个重载方法包含 0 到 3 个普通参数,当参数数量超过 3 个时,使用一个可变参数方法: + +``` +public void foo() { } + +public void foo(int a1) { } + +public void foo(int a1, int a2) { } + +public void foo(int a1, int a2, int a3) { } + +public void foo(int a1, int a2, int a3, int... rest) { } +``` + +  现在你知道,在所有参数数量超过 3 个的方法调用中,只有 5%的调用需要支付创建数组的成本。与大多数性能优化一样,这种技术通常不太合适,但一旦真正需要的时候,它是一个救星。 + +  `EnumSet` 的静态工厂使用这种技术将创建枚举集合的成本降到最低。这是适当的,因为枚举集合为比特属性提供具有性能竞争力的替换(performance-competitive replacement for bit fields)是至关重要的 (条目 36)。 + +  总之,当需要使用可变数量的参数定义方法时,可变参数非常有用。 在使用可变参数前加上任何必需的参数,并注意使用可变参数的性能后果。 \ No newline at end of file