Почему тип аргумента var-arg "более приближенный"? - PullRequest
9 голосов
/ 30 июня 2011

Если я правильно понимаю, Integer[] является подтипом Object[]. Вы можете, например, сделать

Object[] objs = new Integer[] { 1, 2, 3 };

Играя с var-args, я понял, что компилятор «переосмысливает» тип массива без видимой причины.

Программа ниже, например, печатает 123 123. Разве это не имело бы смысла / было бы более точным, если бы оно напечатало 123 6?

class Test {

    public static Object combine(Object... objs) {

        if (objs instanceof Integer[]) {

            int sum = 0;
            for (Integer i : (Integer[]) objs)
                sum += i;
            return sum;

        } else {

            String concat = "";
            for (Object o : objs)
                concat += o;
            return concat;

        }
    }

    public static void main(String[] args) {
        System.out.println(combine("1", "2", "3"));  // prints 123
        System.out.println(combine(1, 2, 3));        // prints 123
    }
}

Полагаю, мой вопрос можно сформулировать так: возникнет ли какое-либо противоречие / проблема, если JLS будет определен для передачи T[] в качестве аргумента, где T - наименьшая верхняя граница типов всех приведенных аргументов?


Редактировать: Я понимаю, что в данном конкретном случае я мог бы перегрузить метод combine, чтобы он также взял Integer[] ( ideone demo ). Тем не менее, остается вопрос, почему этот дизайн был выбран.

Ответы [ 6 ]

6 голосов
/ 30 июня 2011

Что касается этого конкретного вопроса:

Возникло бы какое-либо противоречие / проблема, если бы JLS был определен для передачи T [] в качестве аргумента, где T была наименьшей верхней границей типов всех приведенных аргументов?

Да, потому что массив не только для чтения; это доступно для записи:

package com.example.test;

import java.util.Arrays;

public class Varargs3 {
    public static Object[] changeLastArgument(Object... objs) {
        if (objs.length > 0)
            objs[objs.length-1] = "Pow!";
        return objs;
    }

    public static void main(String[] args) {
        System.out.println(
            Arrays.toString(changeLastArgument(1,2,3))
        );
    }
}

который печатает

[1, 2, Pow!]

Если JLS был определен так, как вы просите (например, для foo(T... args), если вы вызываете foo(a,b,c), то компилятор создает массив с наименьшей верхней границей типов a, b, c), тогда этот случай допускает ошибку времени выполнения: при вызове changeLastArgument(1,2,3) будет создан массив типа Integer[], но метод changeLastArgument() попытается присвоить "Pow!" последнему элементу, и вы получите ошибку времени выполнения.

Объявление changeLastArgument() определяет его типы ввода, и поэтому он должен иметь возможность предположить, что его входной аргумент действительно является Object[], а не подтипом Object[], так что он может безопасно изменять входные аргументы , (Это аналогично принципу PECS - для того, чтобы List<T> был безопасным для чтения и записи, вы не можете использовать какие-либо подстановочные знаки, такие как List<? extends T> - которые можно безопасно читать, но не безопасно записываемый - или List<? super T> - который безопасно записывается, но не читается безопасно.)

3 голосов
/ 30 июня 2011

Чтобы напечатать 6, в результате компилятор должен быть достаточно умен, чтобы понять, что все аргументы могут быть упакованы в аналогичный класс-оболочку.

Полагаю, это слишком много усилий или слишком сложно правильно указать для некоторых очень редких случаев.


Кроме вопроса, похоже, простое правило таково:: массив всегда типа Object[] (если тип varargs Object), вот несколько демонстрационных кодов:

public static void main (String[] args) {
    temp("1", "2", "3");
    temp(1,2,3);
    temp(String.class, Integer.class);
}

public static void temp(Object... objs) {
    System.out.println(objs.getClass());
}

Вывод:

class [Ljava.lang.Object;
class [Ljava.lang.Object;
class [Ljava.lang.Object;
1 голос
/ 30 июня 2011

JLS определяет это поведение (создание массива элементов типа, который является типом параметра переменной arity, т.е. если метод vararg равен foo(Bar bar, Baz baz, T...), тогда массив, созданный при вызове метода, имеет тип T[]), если вы найдете правильное место:

С JLS 8.4.1 (в данный момент у сайта Oracle возникли проблемы, мне пришлось использовать Интернет-архив):

Если последний формальный параметр переменный параметр арности типа T, это считается определить формальное параметр типа T []. Метод затем метод переменной арности. В противном случае это метод фиксированной арности. Вызовы метода переменной арности может содержать более актуальный аргумент выражения, чем формальные параметры. Все фактические выражения аргумента которые не соответствуют формальным параметры, предшествующие переменной Параметр арности будет оценен и результаты сохраняются в массиве будет передан в метод вызов (§15.12.4.2).

Из JLS 15.12.4.2:

15.12.4.2 Оценка аргументов Процесс оценки аргумента список отличается в зависимости от того, вызываемый метод является фиксированной арностью метод или метод переменной арности (§8.4.1).

Если вызываемый метод является метод переменной арности (§8.4.1) m, это обязательно имеет n> 0 формальных параметров. Конечный формальный параметр м обязательно имеет тип T [] для некоторого T, и м обязательно вызывается с k> = 0 фактическими выражениями аргументов.

Если m вызывается с k! = N фактическим выражения аргумента, или, если m вызывается с фактическим аргументом k = n выражения и тип kth Выражение аргумента не является присваиванием совместим с T [], то аргумент список (e1, ..., en-1, en, ... ek) является оценивается как если бы оно было написано как (e1, ..., en-1, new T [] {en, ..., ek}).

Выражения аргумента (возможно переписан как описано выше) сейчас оценивается для получения значений аргументов. Каждое значение аргумента соответствует точно один из п формального метода параметры.

Выражения аргумента, если таковые имеются, оценивается по порядку, слева направо право. Если оценка какого-либо выражение аргумента завершено внезапно, то никакая часть какого-либо аргумента Выражение справа от были оценены, и метод вызов завершается внезапно для Причина та же. Результат оценки J-е выражение аргумента является J-й значение аргумента для 1 <= j <= n. оценка затем продолжает, используя аргумент значения, как описано ниже. </p>

Поэтому я сохраняю свой первоначальный ответ (см. Ниже).


Я считаю, что ответ в декларации:

public static Object combine(Object... objs)

Компилятор соответствует этому методу, и поэтому для varargs он выделяет Object[]. Для этого нет причин выделять Integer[].


пробный тест:

package com.example.test;
public class Varargs1 {
    public static void varargs(Object... objs) {
        System.out.println(objs.getClass());
    }

    public static void main(String[] args) {
        varargs("1", "2", "3");
        varargs(1, 2, 3);

        Integer[] ints = {1,2,3};
        varargs(ints); // Eclipse yields the following warning:
        /* 
         * The argument of type Integer[] should explicitly be 
         * cast to Object[] for the invocation of the varargs 
         * method varargs(Object...) from type Varargs1. 
         * It could alternatively be cast to Object for a 
         * varargs invocation
         */
    }
}

который печатает:

class [Ljava.lang.Object;
class [Ljava.lang.Object;
class [Ljava.lang.Integer;

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

package com.example.test;

public class Varargs2 {
    public static <T> void varargs(T... objs) {
        System.out.println(objs.getClass());
    }

    public static void main(String[] args) {
        varargs("1", "2", "3");
        varargs(1, 2, 3);
        varargs(1, "2", 3); // warning from Eclipse:
        /*
         * Type safety : A generic array of 
         * Object&Comparable<?>&Serializable
         * is created for a varargs parameter
         */
    }
}

который печатает:

class [Ljava.lang.String;
class [Ljava.lang.Integer;
class [Ljava.lang.Comparable;
1 голос
/ 30 июня 2011

Мне кажется, что combine(1, 2, 3) даст int[], а не Integer[].Поскольку массив int[] не является экземпляром массива Integer[], первая проверка завершается неудачно, и вы возвращаетесь к блоку concat.

0 голосов
/ 30 июня 2011

По-моему, вы не указали тип, который varargs должен принимать.

Ниже показано, что я имею в виду:

/**
 * @author The Elite Gentleman.
 *
 */
public class Test {

    public static Object combine(Object... objs) {
        System.out.println("combine()");
        System.out.println(objs.getClass().getName());

        if (objs instanceof Integer[]) {

            int sum = 0;
            for (Integer i : (Integer[]) objs)
            sum += i;
            return sum;

        } else {

            String concat = "";
            for (Object o : objs) {
            System.out.println(o.getClass().getName());
            concat += o;
            }
            return concat;

        }
    }


    public static void main(String[] args) {
        System.out.println("1");
        System.out.println(combine(new String[] {"1", "2", "3"}));
        System.out.println(combine(new Integer[] {1, 2, 3}));

        System.out.println("2");
        System.out.println(combine("1", "2", "3"));
        System.out.println(combine(1, 2, 3));
    }
}

Вывод:

1
combine()
[Ljava.lang.String;
java.lang.String
java.lang.String
java.lang.String
123
combine()
[Ljava.lang.Integer;
6
2
combine()
[Ljava.lang.Object;
java.lang.String
java.lang.String
java.lang.String
123
combine()
[Ljava.lang.Object;
java.lang.Integer
java.lang.Integer
java.lang.Integer
123

Понятно, что, не передавая «нетипизированный» массив, JVM преобразует его в Object[], который передается методу combine().

PS, я не смогнайти JLS, поскольку сервер Oracle не работает.

0 голосов
/ 30 июня 2011

Ну, вы не отправляете Integer [] в функцию объединения.Вот почему он работает не так, как вы ожидаете.

Используйте

System.out.println(combine(new Integer[] {1, 2, 3}));

, чтобы заставить его работать.

...