java 变长参数,泛型相关问题

java 变长参数,泛型相关问题

工作中遇到的问题,是使用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可变参数

以上都是个人观点,欢迎讨论

你可能感兴趣的:(编程)