工作中遇到的问题,是使用scala的array,使用java的Arrays.asList方法转换时,得到了意想不到的结果,因此查阅了很多资料对这个问题做以下总结,涉及到java中的泛型,可变参数,数组等。
说到变长参数,必须要提到数组,数组到底是什么呢?是对象么?
数组是对象,但是这个数组对象并不是从某个类实例化来的,而是由JVM直接创建的,因此查看类名的时候会发现是很奇怪的类似于”[I”这样的样子,这个直接创建的对象的父类就是Object,所以可以调用Object中的所有方法,更多可以参考文献1.
例如:
public static void main(String[] args) {
String[] a={"a","b"};
int[] b={1,2,3};
Integer[] c={1,2,3};
System.out.println(a.getClass().getName());
System.out.println(b.getClass().getName());
System.out.println(c.getClass().getName());
System.out.println(a instanceof Object);
System.out.println(a.getClass().getSuperclass().getName());
}
#output
[Ljava.lang.String;
[I
[Ljava.lang.Integer;
true
java.lang.Object
从上边可以看出数组的class name都是[命名的,并且要注意基本数据类型与java类的区别。再看一下代码
public class Test {
public static void main(String[] args) {
String[] a={"a","b","c"};
int[] b={1,2,3};
Integer[] c={1,2,3};
Test t=new Test();
t.test1(a);
t.test1(b);
t.test1(c);
}
public void test1(T... strings){
System.out.println(strings.length);
System.out.println(strings.getClass().getName()
);
}
}
#output
3
[Ljava.lang.String;
1
[Ljava.lang.Object;
3
[Ljava.lang.Integer;
发现了什么?int类型数据b,通过可变参数传进去,这个参数的类是Object,长度为1,这是为什么呢?猜测到与java的泛型与可变参数的实现有关系,当我们传入的是Integer[] 数组时,可变参数T… strings,java直接将c的引用赋值给strings,因此classname和size符合,为什么说是引用呢,因为若是在test函数中改变strings中的元素,原数组也会改变。但是若是传入的是int[] 数组的时候,泛型T不支持基本数据类型,因此无法将b的引用直接付给strings,上边说过数组也是对象,因此数组支持泛型,因此数组b传入到Strings的时候,嵌套了一层数组,strings={b},也就是说strings[0]才等于数组b。实际上可变参数的实现应该是数组,也就是int[] 无法转型为T[], 因而被当作一个单纯的数组对象 ; Integer[] 可以转型为 T[], 可以作为一个对象数组。如下
public class Test {
public static void main(String[] args) {
String[] a={"a","b","c"};
int[] b={1,2,3};
Integer[] c={1,2,3};
Test t=new Test();
System.out.println(t.test1(a).equals(a));
System.out.println(t.test1(b).equals(b));
System.out.println(t.test1(b)[0].equals(b));
}
public T[] test1(T... strings){
System.out.println(strings.length);
System.out.println(strings.getClass().getName()
);
return strings;
}
}
#output
3
[Ljava.lang.String;
true
1
[Ljava.lang.Object;
false
1
[Ljava.lang.Object;
true
从上边的结果可以看出,对于基本数据类型和对象,在泛型可变参数下是完全不同的,个人觉得主要跟泛型的实现和可变参数的实现有关系。并且丢失了原数组的类型,直接是object,需要强制类型转换。通过上边的分析也就解释了我们平时在使用Arrays.asList方法是遇到的坑,在传入基本数据类型数组时并不能得到我们想要的结果。因为asList涉及到的源代码就有泛型变长数组。如下部分关键Arrays源代码。
@SafeVarargs
public static List asList(T... a) {
return new ArrayList<>(a);
}
/**
* @serial include
*/
private static class ArrayList extends AbstractList
implements RandomAccess, java.io.Serializable
{
private static final long serialVersionUID = -2764017481108945198L;
private final E[] a;
ArrayList(E[] array) {
if (array==null)
throw new NullPointerException();
a = array;
}
并且在scala中使用java.util.Arrays.asList 将scala中的数组Array转成List的时候也会存在问题,不管Array的元素类型是基本数据类型还是包装类或者实现类。个人理解是java的泛型原因,java的泛型不支持scala中的类,因此在使用asList时,scala中的数组在传入到asList中时,用了一个Object数组,数组中的元素是scala数组,也就是发生了上边介绍的使用int[] 类型同样的问题。
Test2.java
public class Test2 {
public static T[] test1(T... strings){
System.out.println(strings.length);
System.out.println(strings.getClass().getName()
);
return strings;
}
}
tests.scala
object tests {
def main(args: Array[String]): Unit = {
val a=Array("1","2","3")
println(a.getClass().getName())
println(Test2.test1(a)(0).equals(a))
val b=Array(1,2,3)
println(b.getClass().getName())
println(Test2.test1(b)(0).equals(b))
}
}
#output
[Ljava.lang.String;
1
[[Ljava.lang.String;
true
[I
1
[[I
true
从中可以看出scala和java的string数组 className相同,但是在传入可变参数时,java的string数组能够直接转型成可变参数数组。而scala中的数组则无法转型,都是作为一个数组元素又嵌套了一层数组。也就是数组类型的数组,而运行java语言是则这直接是object数组,而其中的元素也就是object equels数组。上边提到java在传入基本数据类型数组时会丢失类型,最后需要强制类型转换,而这里却没有丢失类型。
java同样不会丢失类型,上边是代码有误。
public class Test {
public static void main(String[] args) {
String[] a={"a","b","c"};
int[] b={1,2,3};
Integer[] c={1,2,3};
// Test t=new Test();
// t.test1(a);
// t.test1(b);
// t.test1(c);
Test2.test1(a);
Test2.test1(b);
Test2.test1(c);
}
public void test1(T... strings){
System.out.println(strings.length);
System.out.println(strings.getClass().getName()
);
}
}
Test2.java
public class Test2 {
public static T[] test1(T... strings){
System.out.println(strings.length);
System.out.println(strings.getClass().getName()
);
return strings;
}
}
#output
3
[Ljava.lang.String;
1
[[I
3
[Ljava.lang.Integer;
最上边的代码虽说是泛型T,但是在new Test()时,没有指定默认是Object,也就相当于以下代码,因此上边的部分理解有误。是个人的思考过程,懒的修改了
public class Test {
public static void main(String[] args) {
String[] a={"a","b","c"};
int[] b={1,2,3};
Integer[] c={1,2,3};
Test t=new Test();
t.test1(a);
t.test1(b);
t.test1(c);
// Test2.test1(a);
// Test2.test1(b);
// Test2.test1(c);
}
public Object[] test1(Object... strings){
System.out.println(strings.length);
System.out.println(strings.getClass().getName()
);
return strings;
}
}
# outpue
3
[Ljava.lang.String;
1
[Ljava.lang.Object;
3
[Ljava.lang.Integer;
1.知乎-JAVA中的数组是对象吗?
2.Arrays.asList
3.java可变参数
以上都是个人观点,欢迎讨论