【Java】变长参数的坑

VarArgs

VarArgs简述

只需要明确一点即可,java方法的变长参数只是语法糖,其本质上还是将变长的实际参数 varargs 包装为一个数组。

所以

Object[] objs
与
Object... objs

被看作是相同的签名,在源码级别是不能同时存在的,因此,无法编译通过

VarArgs包装

包装方式(注意,下面的代码不是实际的实现,而是一个比喻说明):

  1. 如果实参是唯一且匹配形参varargs要求的数组(就是实参独占形参varargs),那么可以认为你已经替编译器干了这个活,所以可以看作不包装,直接使用这个数组

    public void invoke(String...varargs){}
    
    invoke(new String[]{"a", "b", "c"});
    那么大致的调用过程如下:
    public void invoke(String...varargs){
        innerVarArgs = varargs;
    }
  2. 如果实参是多个单独的变量(就是多个实参分享varargs),那么会将这多个参数包装成一个相应的类型数组
    public void invoke(String...varargs){}
    
    invoke("a", "b", "c");
    
    那么大致的调用过程如下:
    public void invoke(String...varargs){
        innerVarArgs = new String[varargs.length];
        for(int i = 0, size = varargs; i < size; i++){
            innerVarArgs[i] = varargs[i];
        }
    }

那么问题来了

问题一:当变长参数是Object… objs的情况

要知道,数组也是一个对象,所以会产生下面的情况:

public static void test1(Object... varargs) {
        System.out.println(varargs.length);
    }

    [1] test1(new String[]{"a", "b", "c"});   //3
    [2] test1(1, new String[]{"a", "b", "c"});//2

    [3] test1(new Integer[]{2, 3, 4});        //3
    [4] test1(1, new Integer[]{2, 3, 4});     //2
    [5] test1(1, new Integer[]{2, 3, 4}, new Integer[]{2, 3, 4});//3

    [6] test1(2, 3, 4);              //3
    [7] test1(new int[]{2, 3, 4});   //1
    [8] test1(1, new int[]{2, 3, 4});//2

结果分析:

  1. [1]test1的实参是唯一的数组new String[]{“a”, “b”, “c”},因此直接使用
  2. [2]test1的实参有两项,而且这两项都符合varargs的要求,实参数组无法独占形参varargs,编译器看到的是test1(1, ArrayObject),
    因此包装侯结果为Object[]{1, ArrayObject}
    [注意,ArrayObject只是为了在本文中说明这个对像是一个数组,实际并不存在这种类型]
  3. 其他同理

问题二:int[] 与 Integer[]

注意问题一里面的[3]和[7],两者的结果是不一样的,原因:

int[]是没有办法直接转型成Object[]的,基本类型与Object也是无法匹配上的,因此,退而求其次,int[]被当作一个单纯的数组对象ArrayObject,包装结果为Object[]{ArrayObject},因此数组长度为1。

Integer[]本身就可以看作是Object[],因此参照包装方式一,可以直接使用这个数组,因此数组长度不变。

VarArgs在反射中的注意点

首先需要明确的一点是,反射是在运行时才能产生作用的,因此,下面两个方法的效果是一样的:

public void fn(String[] args) {}

    public void fn(String... args) {}

在运行时看来,此方法的签名就是:

public void fn([java.lang.String args) // public void fn(String[] args)

注意,此时方法形参只有一个,就是那个 String[] args

一个示例:

public class Reflect {

    public static void main(String[] args) throws Exception {

        String[] mArgs = new String[]{"a", "b", "c"};

        Method mFn1 = Reflect.class.getMethod("fn1", mArgs.getClass());
        /*[调用1]*/mFn1.invoke(null, new Object[]{mArgs}); // 3
        /*[调用2]*/mFn1.invoke(null, (Object) mArgs);      // 3
        /*[调用3]*/mFn1.invoke(null, mArgs);               // error
    }

    public static void fn1(String... args) {
        System.out.println("fn1:" + args.length);
    }
}

现在看一下Method的invoke方法:

public Object invoke(Object obj, Object... args)

转化为对应的方法调用为:

fn1(args) //fn1签名 public void fn([java.lang.String args)
  1. [调用1]mFn1.invoke(null, new Object[]{mArgs});

    相当与调用fn1(mArgs),即fn1(new String[]{"a", "b", "c"})
  2. [调用2]mFn1.invoke(null, (Object) mArgs);
    mArgs被包装 -> mFn1.invoke(null, new Object[]{mArgs}) 转化为[调用1]
  3. [调用3]mFn1.invoke(null, mArgs);

    不包装,直接使用。
    即调用fn1(“a”, “b”, “c”),但是fn1只接受一个参数,即String[] args,所以会报"wrong number of arguments"错误,即参数数量不对

个人观点,欢迎指正

Android分享 Q群:315658668

时间: 2024-10-18 11:28:23

【Java】变长参数的坑的相关文章

java 变长参数使用原则

1.java变长参数用...表示,如Print(String... args){  ... }; 2.如果一个调用既匹配一个固定参数方法,又匹配一个变长参数方法,则优先匹配固定参数的方法 3.如果一个调用能匹配两个及以上的变长参数方法,则出现错误--这其实表示方法设计有问题,编译器会提示The method is ambiguous 4.方法只能有一个变长参数,且必须放在参数列表的最后一个java 变长参数使用原则,布布扣,bubuko.com

Java 变长参数Varargs

Varargs (variable arguments)可变长参数是Java 1.5引入的特性. 方法的形参如print(String ... s),实参为任意数目的值. public class VarargsDemo{ public static void print(String ... s){ for(String a: s) System.out.print(a); } public static void main(String[] args) { print("a", &

java变长参数

从java5开始提供了变长参数,可以把变长参数当作数据使用 可变长参数方法的定义 使用...表示可变长参数,例如 print(String... args){ ... } 在具有可变长参数的方法中可以把参数当成数组使用,例如可以循环输出所有的参数值. print(String... args){ for(String temp:args) System.out.println(temp); } 可变长参数的方法的调用 调用的时候可以给出任意多个参数,例如: print("hello")

Java语法糖初探(三)--变长参数

变长参数概念 在Java5 中提供了变长参数(varargs),也就是在方法定义中可以使用个数不确定的参数,对于同一方法可以使用不同个数的参数调用.形如 function(T -args).但是需要明确的一点是,java方法的变长参数只是语法糖,其本质上还是将变长的实际参数 varargs 包装为一个数组. 看下面的例子: 12345678910111213 public class VariVargs { public static void main(String []args) { tes

scala学习手记21 - 传递变长参数

在Java中是可以使用变长参数的,如下面的方法: public void check(String ... args){ for(String tmp : args){ System.out.println(tmp); } } 在scala中也可以使用变长参数.和java一样,也是只有最后一个参数可以接收可变长度的参数.使用方式是在参数类型后使用特殊符号"*",如下面的max()方法: def max(values: Int*) = values.foldLeft(values(0))

C++11 新特性之 变长参数模板

template <typename ... ARGS> void fun(ARGS ... args) 首先明确几个概念 1,模板参数包(template parameter pack):它指模板参数位置上的变长参数,例如上面例子中的ARGS 2,函数参数包(function parameter pack):它指函数参数位置上的变长参数,例如上面例子中的args 一般情况下 参数包必须在最后面,例如: template <typename T, typename ... Args>

java 变长參数使用原则

1.java变长參数用...表示,如Print(String... args){  ... }; 2.假设一个调用既匹配一个固定參数方法.又匹配一个变长參数方法,则优先匹配固定參数的方法 3.假设一个调用能匹配两个及以上的变长參数方法,则出现错误--这事实上表示方法设计有问题,编译器会提示The method is ambiguous 4.方法仅仅能有一个变长參数,且必须放在參数列表的最后一个

scala学习笔记-变长参数(5)

变长参数 在Scala中,有时我们需要将函数定义为参数个数可变的形式,则此时可以使用变长参数定义函数. 1 def sum(nums: Int*) = { 2 var res = 0 3 for (num <- nums) res += num 4 res 5 } 6 7 sum(1, 2, 3, 4, 5) 使用序列调用变长参数 在如果想要将一个已有的序列直接调用变长参数函数,是不对的.比如val s = sum(1 to 5).此时需要使用Scala特殊的语法将参数定义为序列,让Scala解

读书笔记:c语言标准库 - 变长参数

· 变长参数(stdarg.h) 变长参数是c语言的特殊参数形式,例如如下函数声明: int printf(const char * format,...); 如此的声明表明,printf函数除了第一个参数类型为const char*之外,其后可以追加任意数量.任意类型的参数. 在函数实现部分,可以使用stdarg.h里的多个宏来访问各个额外的参数:假设lastarg是变长参数函数的最后一个具名参数(printf里的format),那么在函数内容定义类型为va_list的变量: va_list