Как определяется тип компонента для массива varargs?

Когда вы используете varargs, как определяется тип компонента результирующего массива?

Например, гарантируется ли эта программа печатью true или ее технически неопределенное поведение?

public static void main(String[] args) {
    foo("", 0);
}

static <T> void foo(T... arr) {
    System.out.println(arr.getClass() == Serializable[].class);
} 

Ответы

Ответ 1

Я запустил этот код, и вывод говорит, что у вас нет гарантий (по крайней мере, если классы имеют более одного общего предка в разных ветвях иерархии)

Честно говоря, я не знаю причин этой магии, но я просто не мог опубликовать ее в качестве комментария

import java.util.*;
import java.lang.*;
import java.io.*;

class Ideone
{

  interface A{}
  interface B{}
  class AB implements A, B {}
  class BA implements A, B {}

    public static void main (String[] args) throws java.lang.Exception
    {
        foo(new AB(), new BA());
        foo2(new AB(), new BA());
    }

    static <T> void foo(T... arr) {
        System.out.println(arr.getClass() == A[].class);
    }

    static <T> void foo2(T... arr) {
        System.out.println(arr.getClass() == B[].class);
    } 
}

Выход

true
false

Более странные вещи:

Если interface B объявлено до interface A, результат будет обратным:

false
true

Изменение порядка аргументов в вызове метода, порядок объявления порядка и порядок интерфейсов в блоке implements не влияют на меня (1.8.0_51).

Ответ 2

Этот ответ может быть не на 100% ответом, который вы ищете, но, возможно, это помогает. В общем случае arr.getClass() вернет некоторый класс массива (наиболее общий Object[].class, но также может быть Integer[].class, Number[].class или даже Serializable[].class), как правило, наиболее конкретный тип всех элементов, но я бы 't рассчитывать на это см. ответ m-szalik). Если вы хотите, чтобы все классы, содержащиеся в массиве, являлись экземпляром Serializable, вам нужно было бы проверить каждый элемент (кстати, представленная реализация не поддерживает значения null):

static <T> void foo(T... arr) {
    System.out.println(Stream.of(arr)
            .filter(e -> !Serializable.class.isInstance(e.getClass()))
            .findFirst()
            .orElse(null) == null);
}

Вы можете посмотреть:

Btw. Я согласен с мнением капитана Фогетти:

Ну, я не уверен в этом на 100%, но я думаю, что это не имеет ничего общего с varargs, но скорее похоже на динамическое связывание, generics и стирание типов.

Примечание

Несколько примеров реализации foo:

  • foo(1, 2) будет Integer[].class
  • foo(1, 2.0) будет Number[].class
  • foo ("", 1) будет Serializable[].class
  • foo(null, 2) будет Integer[].class
  • foo("", new Object()) будет Object[].class

Ответ 3

Ну, я не уверен в этом на 100%, но я думаю, что это не имеет ничего общего с varargs, но скорее похоже на динамическое связывание, generics и стирание типов.

"Массивы на Java являются ковариантными, но generics - нет. Другими словами, String [] является подтипом Object [], но Stack не является подтипом Stack."

Источник: http://algs4.cs.princeton.edu/13stacks/

Итак, чтобы ответить на ваш вопрос, это поведение указано документированным и ожидаемым.

В книге: "Эффективная Java" Джошуа Блох объясняет это следующим образом:

Массивы отличаются от общих типов двумя важными способами. Во-первых, массивы ковариантны. Это страшное звучание означает просто, что если Sub является подтипом Super, тогда тип массива Sub [] является подтипом Super []. Напротив, генераторы являются инвариантными: для любых двух разных типов Type1 и Type2, List <Type1 > не является ни подтипом, ни супертипом List <Type2 > [JLS, 4,10; Naftalin07, 2.5]. Вы можете подумать, что это означает, что дженерики недостаточны, но, возможно, это недостаточные массивы.

И почему я говорю о массивах, которые вы могли бы спросить? Ну, потому что varargs в конечном итоге станут массивами.

"В предыдущих выпусках метод, который принял произвольное количество значений, потребовал, чтобы вы создали массив и поместили значения в массив до вызова метода."

"По-прежнему верно, что в массиве должны быть переданы несколько аргументов, но функция varargs автоматизирует и скрывает процесс.

Источник: https://docs.oracle.com/javase/8/docs/technotes/guides/language/varargs.html

Ответ 4

Речь идет не о varargs, а о дженериках. Во время компиляции теряется общая информация. Ваш параметр varargs преобразуется компилятором в массив. Поэтому JVM не принимает решения о типе, но компилятор делает.

Ваш код скомпилирован в байт-код, как показано ниже:

public static void main(String[] args) {
    foo(new Serializable[]{"", Integer.valueOf(0)});
}

Алгоритм решения довольно длинный, но вы можете прочитать об этом здесь. https://docs.oracle.com/javase/tutorial/java/generics/genTypeInference.html