J2SE 1.5Varargs机制(Object... params)

2023-12-04 02:08

本文主要是介绍J2SE 1.5Varargs机制(Object... params),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

J2SE 1.5提供了“Varargs”机制。借助这一机制,可以定义能和多个实参相匹配的形参。从而,可以用一种更简单的方式,来传递个数可变的实参。本文介绍这一机制的使用方法,以及这一机制与数组、泛型、重载之间的相互作用时的若干问题。
到J2SE 1.4为止,一直无法在Java程序里定义实参个数可变的方法——因为Java要求实参(Arguments)和形参(Parameters)的数量和类型都必须逐一匹配,而形参的数目是在定义方法时就已经固定下来了。尽管可以通过重载机制,为同一个方法提供带有不同数量的形参的版本,但是这仍然不能达到让实参数量任意变化的目的。

然而,有些方法的语义要求它们必须能接受个数可变的实参——例如著名的main方法,就需要能接受所有的命令行参数为实参,而命令行参数的数目,事先根本无法确定下来。

对于这个问题,传统上一般是采用“利用一个数组来包裹要传递的实参”的做法来应付。

1. 用数组包裹实参
“用数组包裹实参”的做法可以分成三步:首先,为这个方法定义一个数组型的参数;然后在调用时,生成一个包含了所有要传递的实参的数组;最后,把这个数组作为一个实参传递过去。

这种做法可以有效的达到“让方法可以接受个数可变的参数”的目的,只是调用时的形式不够简单。

J2SE 1.5中提供了Varargs机制,允许直接定义能和多个实参相匹配的形参。从而,可以用一种更简单的方式,来传递个数可变的实参。

Varargs的含义
大体说来,“Varargs”是“variable number of arguments”的意思。有时候也被简单的称为“variable arguments”,不过因为这一种叫法没有说明是什么东西可变,所以意义稍微有点模糊。

2. 定义实参个数可变的方法
只要在一个形参的“类型”与“参数名”之间加上三个连续的“.”(即“...”,英文里的句中省略号),就可以让它和不确定个实参相匹配。而一个带有这样的形参的方法,就是一个实参个数可变的方法。

清单1:一个实参个数可变的方法
private static int sumUp(int... values) {
}

注意,只有最后一个形参才能被定义成“能和不确定个实参相匹配”的。因此,一个方法里只能有一个这样的形参。另外,如果这个方法还有其它的形参,要把它们放到前面的位置上。

编译器会在背地里把这最后一个形参转化为一个数组形参,并在编译出的class文件里作上一个记号,表明这是个实参个数可变的方法。

清单2:实参个数可变的方法的秘密形态
private static int sumUp(int[] values) {
}

由于存在着这样的转化,所以不能再为这个类定义一个和转化后的方法签名一致的方法。

清单3:会导致编译错误的组合
private static int sumUp(int... values) {
}
private static int sumUp(int[] values) {
}

空白的存亡问题
根据J2SE 1.5的语法,在“...”前面的空白字符是可有可无的。这样就有在“...”前面添加空白字符(形如“Object ... args”)和在“...”前面不加空白字符(形如“Object... args”)的两种写法。因为目前和J2SE 1.5相配合的Java Code Conventions还没有正式发布,所以无法知道究竟哪一种写法比较正统。不过,考虑到数组参数也有“Object [] args”和“Object[] args”两种书写方式,而正统的写法是不在“[]”前添加空白字符,似乎采取不加空白的“Object... args”的写法在整体上更协调一些。

3. 调用实参个数可变的方法
只要把要传递的实参逐一写到相应的位置上,就可以调用一个实参个数可变的方法。不需要其它的步骤。

清单4:可以传递若干个实参
sumUp(1, 3, 5, 7);
在背地里,编译器会把这种调用过程转化为用“数组包裹实参”的形式:

清单5:偷偷出现的数组创建
sumUp(new int[]{1, 2, 3, 4});

另外,这里说的“不确定个”也包括零个,所以这样的调用也是合乎情理的:

清单6:也可以传递零个实参
sumUp();

这种调用方法被编译器秘密转化之后的效果,则等同于这样:

清单7:零实参对应空数组
sumUp(new int[]{});

注意这时传递过去的是一个空数组,而不是null。这样就可以采取统一的形式来处理,而不必检测到底属于哪种情况。

4. 处理个数可变的实参
处理个数可变的实参的办法,和处理数组实参的办法基本相同。所有的实参,都被保存到一个和形参同名的数组里。根据实际的需要,把这个数组里的元素读出之后,要蒸要煮,就可以随意了。

清单8:处理收到的实参们
private static int sumUp(int... values) {
    int sum = 0;
    for (int i = 0; i < values.length; i++) {
        sum += values[i];
    }
    return sum;
}

5. 转发个数可变的实参
有时候,在接受了一组个数可变的实参之后,还要把它们传递给另一个实参个数可变的方法。因为编码时无法知道接受来的这一组实参的数目,所以“把它们逐一写到该出现的位置上去”的做法并不可行。不过,这并不意味着这是个不可完成的任务,因为还有另外一种办法,可以用来调用实参个数可变的方法。

在J2SE 1.5的编译器的眼中,实参个数可变的方法是最后带了一个数组形参的方法的特例。因此,事先把整组要传递的实参放到一个数组里,然后把这个数组作为最后一个实参,传递给一个实参个数可变的方法,不会造成任何错误。借助这一特性,就可以顺利的完成转发了。

清单9:转发收到的实参们
public class PrintfSample {
    public static void main(String[] args) {
        //打印出“Pi:3.141593 E:2.718282”
        printOut("Pi:%f E:%f/n", Math.PI, Math.E);
    }
    private static void printOut(String format, Object... args) {
        //J2SE 1.5里PrintStream新增的printf(String format, Object... args)方法
        System.out.printf(format, args);
    }
}

Java里的“printf”和“sprintf”
C语言里的printf(按一定的格式输出字符串)和sprintf(按一定的格式组合字符串)是十分经典的使用Varargs机制的例子。在J2SE 1.5中,也分别在java.io.PrintStream类和java.lang.String类中提供了类似的功能。

按一定的格式输出字符串的功能,可以通过调用PrintStream对象的printf(String format, Object... args)方法来实现。

按一定的格式组合字符串的工作,则可以通过调用String类的String format(String format, Object... args)静态方法来进行。

6. 是数组?不是数组?
尽管在背地里,编译器会把能匹配不确定个实参的形参,转化为数组形参;而且也可以用数组包了实参,再传递给实参个数可变的方法;但是,这并不表示“能匹配不确定个实参的形参”和“数组形参”完全没有差异。

一个明显的差异是,如果按照调用实参个数可变的方法的形式,来调用一个最后一个形参是数组形参的方法,只会导致一个“cannot be applied to”的编译错误。

清单10:一个“cannot be applied to”的编译错误
private static void testOverloading(int[] i) {
    System.out.println("A");
}
public static void main(String[] args) {
    testOverloading(1, 2, 3);//编译出错
}

由于这一原因,不能在调用只支持用数组包裹实参的方法的时候(例如在不是专门为J2SE 1.5设计第三方类库中遗留的那些),直接采用这种简明的调用方式。

如果不能修改原来的类,为要调用的方法增加参数个数可变的版本,而又想采用这种简明的调用方式,那么可以借助“引入外加函数(Introduce Foreign Method)”和“引入本地扩展(Intoduce Local Extension)”的重构手法来近似的达到目的。

7. 当个数可变的实参遇到泛型
J2SE 1.5中新增了“泛型”的机制,可以在一定条件下把一个类型参数化。例如,可以在编写一个类的时候,把一个方法的形参的类型用一个标识符(如T)来代表,至于这个标识符到底表示什么类型,则在生成这个类的实例的时候再行指定。这一机制可以用来提供更充分的代码重用和更严格的编译时类型检查。

不过泛型机制却不能和个数可变的形参配合使用。如果把一个能和不确定个实参相匹配的形参的类型,用一个标识符来代表,那么编译器会给出一个“generic array creation”的错误。

清单11:当Varargs遇上泛型
private static <T> void testVarargs(T... args) {//编译出错
}

造成这个现象的原因在于J2SE 1.5中的泛型机制的一个内在约束——不能拿用标识符来代表的类型来创建这一类型的实例。在出现支持没有了这个约束的Java版本之前,对于这个问题,基本没有太好的解决办法。

不过,传统的“用数组包裹”的做法,并不受这个约束的限制。

清单12:可以编译的变通做法
private static <T> void testVarargs(T[] args) {
    for (int i = 0; i < args.length; i++) {
        System.out.println(args[i]);
    }
}

8. 重载中的选择问题
Java支持“重载”的机制,允许在同一个类拥有许多只有形参列表不同的方法。然后,由编译器根据调用时的实参来选择到底要执行哪一个方法。

传统上的选择,基本是依照“特殊者优先”的原则来进行。一个方法的特殊程度,取决于为了让它顺利运行而需要满足的条件的数目,需要条件越多的越特殊。

在引入Varargs机制之后,这一原则仍然适用,只是要考虑的问题丰富了一些——传统上,一个重载方法的各个版本之中,只有形参数量与实参数量正好一致的那些有被进一步考虑的资格。但是Varargs机制引入之后,完全可以出现两个版本都能匹配,在其它方面也别无二致,只是一个实参个数固定,而一个实参个数可变的情况。

遇到这种情况时,所用的判定规则是“实参个数固定的版本优先于实参个数可变的版本”。

清单13:实参个数固定的版本优先
public class OverloadingSampleA {
    public static void main(String[] args) {
        testOverloading(1);//打印出A
        testOverloading(1, 2);//打印出B
        testOverloading(1, 2, 3);//打印出C
    }
    private static void testOverloading(int i) {
        System.out.println("A");
    }
    private static void testOverloading(int i, int j) {
        System.out.println("B");
    }
    private static void testOverloading(int i, int... more) {
        System.out.println("C");
    }
}

如果在编译器看来,同时有多个方法具有相同的优先权,它就会陷入无法就到底调用哪个方法作出一个选择的状态。在这样的时候,它就会产生一个“reference to 被调用的方法名 is ambiguous”的编译错误,并耐心的等候作了一些修改,足以免除它的迷惑的新源代码的到来。

在引入了Varargs机制之后,这种可能导致迷惑的情况,又增加了一些。例如现在可能会有两个版本都能匹配,在其它方面也如出一辙,而且都是实参个数可变的冲突发生。

清单14:左右都不是,为难了编译器
public class OverloadingSampleB {
    public static void main(String[] args) {
        testOverloading(1, 2, 3);//编译出错
    }
    private static void testOverloading(Object... args) {
    }
    private static void testOverloading(Object o, Object... args) {
    }
}

另外,因为J2SE 1.5中有“Autoboxing/Auto-Unboxing”机制的存在,所以还可能发生两个版本都能匹配,而且都是实参个数可变,其它方面也一模一样,只是一个能接受的实参是基本类型,而另一个能接受的实参是包裹类的冲突发生。

清单15:Autoboxing/Auto-Unboxing带来的新问题
public class OverloadingSampleC {
    public static void main(String[] args) {
        /* 编译出错 */
        testOverloading(1, 2);
        /* 还是编译出错 */
        testOverloading(new Integer(1), new Integer(2));
    }
    private static void testOverloading(int... args) {
    }
    private static void testOverloading(Integer... args) {
    }
}

9. 归纳总结
和“用数组包裹”的做法相比,真正的实参个数可变的方法,在调用时传递参数的操作更为简单,含义也更为清楚。不过,这一机制也有它自身的局限,并不是一个完美无缺的解决方案。

参考资源
•可以通过Sun的Java Technology页面找到下载J2SE 1.5的SDK及其文档的链接,目前的最新版本是J2SDK 5.0 Beta 2。
•《JSR 201: Extending the Java Programming Language with Enumerations, Autoboxing, Enhanced for loops and Static Import》中定义了很多在J2SE 1.5中出现了的新语言特性。虽然在标题里并没有明确提到,但是Varargs机制也在其中。
•Calvin Austin在《J2SE 1.5 in a Nutshell》一文中,比较全面的对J2SE 1.5的新特性进行了简单的介绍。
•John Zukowski在《驯服 Tiger:Tiger 预览版现已推出》一文中,介绍了如何开始使用J2SDK 1.5的基础知识。不过因为这篇文章是依照J2SDK 1.5 Alpha版的状况所写,所以里面提到的一些细节(如下载地址和默认安装路径)已经发生了变化。
•John Zukowski在《驯服 Tiger:格式化输出》一文中,介绍了利用J2SE 1.5中新增的功能进行字符串格式化的办法。
•Gilad Bracha在《Generics in the Java Programming Language》一文中,细致的介绍了J2SE 1.5中的泛型机制的使用方法和各种限制。
•Martin Fowler在《Refactoring -- Improving the Design of Existing Code》(中译本名为《重构——改善既有代码的设计》,由侯捷、熊节合译)一书的第七章《在对象之间移动特性(Moving Features Between Objects)》中,介绍了被称作“引入外加函数(Introduce Foreign Method)”和“引入本地扩展(Intoduce Local Extension)”的重构手法。
•Gilad Bracha、James Gosling、Bill Joy和Guy Steele在《The Java Language Specification, 2nd Edition》一书里的《Classes》一章中的《Overloading》一节和《Expression》一章中的《Method Invocation Expressions》一节里深入的讨论了重载方面的问题。不过因为这本书是依照J2SDK 1.2版的状况所写,所以并没有完整地涵盖J2SE 1.5中的实际情况。

 

 

http://blog.csdn.net/avius/archive/2004/08/05/65970.aspx      (CSDN上的原创)

这篇关于J2SE 1.5Varargs机制(Object... params)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/451712

相关文章

Linux系统稳定性的奥秘:探究其背后的机制与哲学

在计算机操作系统的世界里,Linux以其卓越的稳定性和可靠性著称,成为服务器、嵌入式系统乃至个人电脑用户的首选。那么,是什么造就了Linux如此之高的稳定性呢?本文将深入解析Linux系统稳定性的几个关键因素,揭示其背后的技术哲学与实践。 1. 开源协作的力量Linux是一个开源项目,意味着任何人都可以查看、修改和贡献其源代码。这种开放性吸引了全球成千上万的开发者参与到内核的维护与优化中,形成了

Spring中事务的传播机制

一、前言 首先事务传播机制解决了什么问题 Spring 事务传播机制是包含多个事务的方法在相互调用时,事务是如何在这些方法间传播的。 事务的传播级别有 7 个,支持当前事务的:REQUIRED、SUPPORTS、MANDATORY; 不支持当前事务的:REQUIRES_NEW、NOT_SUPPORTED、NEVER,以及嵌套事务 NESTED,其中 REQUIRED 是默认的事务传播级别。

多头注意力机制(Multi-Head Attention)

文章目录 多头注意力机制的作用多头注意力机制的工作原理为什么使用多头注意力机制?代码示例 多头注意力机制(Multi-Head Attention)是Transformer架构中的一个核心组件。它在机器翻译、自然语言处理(NLP)等领域取得了显著的成功。多头注意力机制的引入是为了增强模型的能力,使其能够从不同的角度关注输入序列的不同部分,从而捕捉更多层次的信息。 多头注意力机

Linux-笔记 线程同步机制

目录 前言 实现 信号量(Semaphore) 计数型信号量 二值信号量  信号量的原语操作 无名信号量的操作函数 例子 互斥锁(mutex) 互斥锁的操作函数 例子 自旋锁 (Spinlock) 自旋锁与互斥锁的区别 自旋锁的操作函数 例子 前言         线程同步是为了对共享资源的访问进行保护,确保数据的一致性,由于进程中会有多个线程的存在,

Spring 集成 RabbitMQ 与其概念,消息持久化,ACK机制

目录 RabbitMQ 概念exchange交换机机制 什么是交换机binding?Direct Exchange交换机Topic Exchange交换机Fanout Exchange交换机Header Exchange交换机RabbitMQ 的 Hello - Demo(springboot实现)RabbitMQ 的 Hello Demo(spring xml实现)RabbitMQ 在生产环境

Rust:Future、async 异步代码机制示例与分析

0. 异步、并发、并行、进程、协程概念梳理 Rust 的异步机制不是多线程或多进程,而是基于协程(或称为轻量级线程、微线程)的模型,这些协程可以在单个线程内并发执行。这种模型允许在单个线程中通过非阻塞的方式处理多个任务,从而实现高效的并发。 关于“并发”和“并行”的区别,这是两个经常被提及但含义不同的概念: 并发(Concurrency):指的是同时处理多个任务的能力,这些任务可能在同一时

ROS话题通信机制实操C++

ROS话题通信机制实操C++ 创建ROS工程发布方(二狗子)订阅方(翠花)编辑配置文件编译并执行注意订阅的第一条数据丢失 ROS话题通信的理论查阅ROS话题通信流程理论 在ROS话题通信机制实现中,ROS master 不需要实现,且连接的建立也已经被封装了,需要关注的关键点有三个: 发布方(二狗子)订阅方(翠花)数据(此处为普通文本) 创建ROS工程 创建一个ROS工程

Python: create object

# encoding: utf-8# 版权所有 2024 涂聚文有限公司# 许可信息查看:# 描述:# Author : geovindu,Geovin Du 涂聚文.# IDE : PyCharm 2023.1 python 3.11# Datetime : 2024/6/15 18:59# User : geovindu# Product :

Java面试题:内存管理、类加载机制、对象生命周期及性能优化

1. 说一下 JVM 的主要组成部分及其作用? JVM包含两个子系统和两个组件:Class loader(类装载)、Execution engine(执行引擎)、Runtime data area(运行时数据区)、Native Interface(本地接口)。 Class loader(类装载):根据给定的全限定名类名(如:java.lang.Object)装载class文件到Runtim

【进阶篇-Day5:JAVA常用API的使用(Math、BigDecimal、Object、包装类等)】

目录 1、API的概念2、Object类2.1 Object类的介绍2.2 Object的toString()方法2.3 Object的equals()方法2.4 Objects概述 3、Math类4、System类5、BigDecimal类6、包装类6.1 包装类的概念6.2 几种包装类(1)手动转换包装类:(2)自动转换包装类:(3)Integet常用方法:(4)练习: 1