Java-可变参数的使用与优化
可变参数是在jdk1.5中被加入的,之前并没有这个机制。
我是在一次无意中查看某个jar包(好像是mybatis)的源码时发现的可变参数,对【String …】这种形式很好奇,然后就感觉发现了新大陆,在之后的工作中把我所有需要用到多个相同类型参数的地方都换成了可变参数,这样我相当于写了0-n个参数的方法重载,我一直以为这是提升开发效率的绝佳手段,直到昨天。
昨天在学习Guava,在使用ImmutableList.of方法时,发现其同一个of方法,重载了12次。不同之处在于其参数由1个E类型变成了11+n个E类型。
public static <E> ImmutableList<E> of() {
return RegularImmutableList.EMPTY;
}
public static <E> ImmutableList<E> of(E element) {
return new SingletonImmutableList(element);
}
public static <E> ImmutableList<E> of(E e1, E e2) {
return construct(e1, e2);
}
public static <E> ImmutableList<E> of(E e1, E e2, E e3) {
return construct(e1, e2, e3);
}
public static <E> ImmutableList<E> of(E e1, E e2, E e3, E e4) {
return construct(e1, e2, e3, e4);
}
public static <E> ImmutableList<E> of(E e1, E e2, E e3, E e4, E e5) {
return construct(e1, e2, e3, e4, e5);
}
public static <E> ImmutableList<E> of(E e1, E e2, E e3, E e4, E e5, E e6) {
return construct(e1, e2, e3, e4, e5, e6);
}
public static <E> ImmutableList<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7) {
return construct(e1, e2, e3, e4, e5, e6, e7);
}
public static <E> ImmutableList<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8) {
return construct(e1, e2, e3, e4, e5, e6, e7, e8);
}
public static <E> ImmutableList<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9) {
return construct(e1, e2, e3, e4, e5, e6, e7, e8, e9);
}
public static <E> ImmutableList<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9, E e10) {
return construct(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10);
}
public static <E> ImmutableList<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9, E e10, E e11) {
return construct(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11);
}
在看到前11个方法之时,我很不解,jdk1.5之后不是有可变参数吗?为啥开发者要这要麻烦的重写这么多遍?而且也不能支持所有的参数啊?
直到第12个方法,12个E 参数 加一个 E … 可变参数。
@SafeVarargs
public static <E> ImmutableList<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9, E e10, E e11, E e12, E... others) {
Preconditions.checkArgument(others.length <= 2147483635, "the total number of elements must fit in an int");
Object[] array = new Object[12 + others.length];
array[0] = e1;
array[1] = e2;
array[2] = e3;
array[3] = e4;
array[4] = e5;
array[5] = e6;
array[6] = e7;
array[7] = e8;
array[8] = e9;
array[9] = e10;
array[10] = e11;
array[11] = e12;
System.arraycopy(others, 0, array, 12, others.length);
return construct(array);
}
看到这里我恍然大悟却又不知所云,恍然大悟是因为最后一个E …解决了我对这个方法的效果的疑虑不知所云的是为啥要这样写?明明一个可变参数就解决了,为啥要多写11个重载方法?考虑到此包作者水平肯定是顶尖级别的,那么这样写肯定有其优势,我感觉有必要弄懂这样写的原理。
在查阅的多篇资料中,都提到可变参数可能会影响性能,其底层原理是数组,表面上看我们少写了很多代码,实际上底层还是帮我们做了数组的循环处理,效率上不会更快,有时候会更慢。如果使用重载可以兼顾性能和效率。
另外我注意到,在最后一个方法中,有使用@SafeVarargs 注解,该注解是在jdk1.7中加入的。
在这之前,可变长度的参数与泛型一起使用会遇到一个麻烦,就是编译器产生的警告过多
//使用泛型的变长参数方法产生编译器警告的示例
public static <T> T useVarargs(T... args) {
return args.length > 0 ? args[0] : null;
}
如果参数传递的是不可具体化(non-reifiable)的类型,如List这样的泛型类型,会产生警告信息。每一次调用该方法,都会产生警告信息。
这其中的原因是可变长度的方法参数的实际值是通过数组来传递的,而数组中存储的是不可具体化的泛型类对象,自身存在类型安全问题。因此编译器会给出相应的警告信息。(在这段资料中,给出的警告信息反向验证了,可变参数其底层确实就是数组)
在使用@SafeVarargs 注解之后,编译器遇到类似的情况,就不会再给出相关的警告信息。
但是该注解只能用在参数长度可变的方法或构造方法上,且方法必须声明为static或final,否则会出现编译错误。一个方法使用@SafeVarargs注解的前提是,开发人员必须确保这个方法的实现中对泛型类型参数的处理不会引发类型安全问题。
参考资料如下:
1.高效Java第四十二条慎用可变参数 https://baijiahao.baidu.com/s?id=1572743119816956&wfr=spider&for=pc
2.《深入理解Java 7:核心技术与最佳实践》 http://book.51cto.com/art/201205/339154.htm