Вопрос №6061 от пользователя Andy в уроке «Модуль 1. Тест.», курс «Java: Структуры данных»

Andy

В офф. решении есть строчка

System.arraycopy(m, 0, a, 0, size);

Никакого приведения типов там не делается, тесты проходят только потому, что типы везде int.

12 0

Вячеслав Ковалевский

System.arraycopy - это метод который на вход принимает два массива Object[] посему им можно пользоватья с любыми массивами.

0

Andy

Видать опять я чего-то недопонял. По простоте душевной думал, что при вызове toArray(T1[] a) даже если в коллекции находится тип T, то вернется тип T1. Т.е., если в тестах в testToArrayWhenInputArrayHaveCorrectSize() вместо

final Integer[] input = new Integer[3];

написать

final String[] input = new String[3];

то вернется массив из String. Первоначально, когда писал пост, у меня был error, но сейчас я понял, что просто не поменял тип на String в

final Integer[] result = testInstance.toArray(input);

После замены стало компилиться, но для этой строчки стало бросать exception в рантайме:

1) testToArrayWhenInputArrayHaveCorrectSize(ArrayCollectionTest)
java.lang.ArrayStoreException
        at java.lang.System.arraycopy(Native Method)

Тоже самое будет и для реального ArrayList, если делать testInstance через него:

final ArrayList<Integer> testInstance = new ArrayList<>();

Т.е. наш велосипедик тут не при чем. Где-то что-то не так делаю или рассуждаю.

0

Вячеслав Ковалевский

По простоте душевной думал, что при вызове toArray(T1[] a) даже если в коллекции находится тип T, то вернется тип T1

так и будет, согласно сигнатуре метода вернется тот тип который переда во входном аргументен. То что я говорил про System.arraycopy, повторюсь:

System.arraycopy - это метод который на вход принимает два массива Object[] посему им можно пользоватья с любыми массивами.

Использовать не означает что он магическим образом Integer в String начится преобразовывать, это лиш означает что оно не провиряет совместимотьс типов и тупо на вход требует Object[], а за совместимость типов должна отвечать догика верхнего уровня. Соотвественно когда его просим "коня" превратить в "осьминога" он гераически умирает =)

0

Вячеслав Ковалевский

Подобное поведение к слову указано в спеке: https://docs.oracle.com/javase/7/docs/api/java/lang/System.html#arraycopy(java.lang.Object,%20int,%20java.lang.Object,%20int,%20int)

ArrayStoreException - if an element in the src array could not be stored into the dest array because of a type mismatch.

т.е. кидает исключение в рантайме, но компайлер это все скушает и приготовит байткод.

0

Andy

за совместимость типов должна отвечать догика верхнего уровня.

А где должна быть эта логика? Вне метода/класса? Зачем тогда декларируется:

the runtime type of the returned array is that of the specified array. If the collection fits in the specified array, it is returned therein. Otherwise, a new array is allocated with the runtime type of the specified array and the size of this collection.

Parameters: a - the array into which the elements of this collection are to be stored, if it is big enough; otherwise, a new array of the same runtime type is allocated for this purpose.

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

0

Вячеслав Ковалевский

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

Не совсем понял вопрос, вот два варианта:

        final String[] left = {"1", "2", "3"};
        final Object[] right =  new Object[10];
        System.arraycopy(left, 0, right, 0, 3);

        final Integer[] right2 =  new Integer[10];
        System.arraycopy(right, 0, right2, 0, 3);

Так как оба варианта валидны согласно спеке а знаит скомпилируються то это ваша задача как разрабочтка не допустить у себя в коде воторого варианта. Можно дженериками гарантировать что вызов будет безопасным, можно всякие явные проверки делать типа:

        if (right[0] instanceof Integer)
            System.arraycopy(right, 0, right2, 0, 3);

Хотя перед копирование лучше проверок не ставить так как явно в еще более выскокуровневом коде ошибка и не пува сейчас ошибка может быть скрыта и потом долго и трудно будет ее отловить.

0

Andy

System.arraycopy(right, 0, right2, 0, 3);

Наверное, имелось в виду

System.arraycopy(left, 0, right2, 0, 3);

А то получается, что второй вариант такой же как первый, только String вместо Integer.

 

Но вообще меня не столько System.arraycopy интересовал, поскольку это внутренняя реализация, меня интересует, почему на подобном вызове

testInstance.add(1);
testInstance.add(2);
testInstance.add(3);
final String[] input = new String[3];
final String[] result = testInstance.toArray(input);

не происходит возвращение "new array of the same runtime type" (как указано в спецификации), а все валится. В моем представлении, метод toArray(input) должен преобразовывать тип внутреннего массива (в примере это int) к типу входящего input (в примере это String). Однако этого не происходит ни в нашей реализации, ни, что более странно, в офф. реализации.

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

public <T> T[] toArray(T[] a) {
    if (a.length < size)
        a = (T[])java.lang.reflect.Array.newInstance(
                            a.getClass().getComponentType(), size);
    int i = 0;
    Object[] result = a;
    for (Node<E> x = first; x != null; x = x.next)
        result[i++] = x.item;
    if (a.length > size)
        a[size] = null;
    return a;
}

Если разбирать по строчкам:

a = (T[])java.lang.reflect.Array.newInstance(
                            a.getClass().getComponentType(), size);

зачем дополнительно приводить тип с помощью (T[]), если он указан в качестве параметра newInstance? Подобный вопрос уже возникал у кого-то в каментах.

Это смешно (а может даже не смешно), но я не понимаю строчку Object[] result = a;.
Я бы сказал, что это копирование по ссылке с приведением a к типу Object. Т.е.

for (Node<E> x = first; x != null; x = x.next)
  result[i++] = x.item;

это добавление к result (он же теперь и а) значения ноды с приведением от исходного типа E к Object. Т.е. в итоге toArray (T[] a) вернет Object[] a ? И где тогда обещание вернуть тот же тип, что и на входе? Формально, конечно, все есть Object, но я ожидал на выходе именно T, а не тень былого могущества с усечение методов до Object. Хотя все эти рассуждения ничего не стоят, т.к. на практике все равно ArrayStoreException.

0

Вячеслав Ковалевский

System.arraycopy(right, 0, right2, 0, 3); Наверное, имелось в виду System.arraycopy(left, 0, right2, 0, 3); А то получается, что второй вариант такой же как первый, только String вместо Integer.

Нет имелось в виду именно System.arraycopy(right, 0, right2, 0, 3); так как там Object в качестве типа первого массива вместо String.

не происходит возвращение "new array of the same runtime type" (как указано в спецификации), а все валится.

Все происходит согласно спеке, Java пытается начать делать кастинг, однако как Вы себе представляете кастинг, ну скажем типа "Dog" к типу "Bridge", одно мост второе собака, каким магическим образом Java из одного сделает второе что бы вернуть массив мостов передалв в них массив собак. Исходя из того что у Object есть массив toString еще можно было бы предположить что если справа массив String то можно в стринги переводить через toString, но это было бы очень странное исключение для одного единственного типа и его не сделали (так как в спеке нету). Как говорилось ранее это отвественно более высокоуровневого кода что бы массив оба типа были "совместимы", и если оне не совместимы то согласно спеке:

ArrayStoreException - if an element in the src array could not be stored into the dest array because of a type mismatch.

Все работает согласно спеке. По поводу:

java.lang.reflect.Array.newInstance

Глядим в спеку: https://docs.oracle.com/javase/7/docs/api/java/lang/reflect/Array.html#newInstance(java.lang.Class,%20int...)

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

По поводу строки:

Object[] result = a;

У нас в лисет все типы: E, массив у нас иммет тип T, соотвсетвенно нам нужно как то преобразовать E в T для того что бы записать все эдементы из листа в массив. Что бы не мучаться с этим массив типа T (с именем "a") приводят к типу Object, и теперь в этот массив result можно запихивать все что нужно. А так как result это просто ссылка на массив "a" то в конце можно вернуть тот самый массив "a" так как его ячейки уже изменены.

Т.е. в итоге toArray (T[] a) вернет Object[] a

Ссылка "result" как раз и создается что бы не трогать ссылку "a". Но в любом случае в Java нельзя динамически поменять тип ссылки, ссылка "a" как имела тип T[], так и будет его иметь все время, что и похволяет нам вернуть T[].

0

Andy

На данный момент у меня сложилось впечатление, что "the runtime type of the returned array is that of the specified array" трактуется не как должен вернуть массив того же типа, а как должен попытаться, а если не получилось - значит не шмогла, разработчик сам виноват, подсовывает что попало. Просто потому, что нет единого механизма преобразования типов (ну, кроме как низложения до Object).

Ссылка "result" как раз и создается что бы не трогать ссылку "a".

Это я не понял. Ссылка она вроде и в Африке ссылка, просто указатель на место в памяти? Она же должна быть одна и та же и для "result" и для "a"?

0

Вячеслав Ковалевский

Это я не понял. Ссылка она вроде и в Африке ссылка, просто указатель на место в памяти? Она же должна быть одна и та же и для "result" и для "a"?

Вы правы что она указывает на один и тот же объектв в оперативной памяти, но у каждоый ссылки свой тип и эти типы могут быть разные (хотя указывать на один и тот же объект в оперативной памяти). Простой пример:

String a = "ololo";
Object b = a;

Согданы две ссылки с разынм типом. Несмотря на то что ссылка b узкаывает в оперативной памяти на String ее нельзя использовтаь(без явного приведения обратно к String) с методами и логикой которуая требует на вход тип String.

0

Andy

Понятно, значит инфа о типе хранится в ссылке, а не в объекте.

0

Вячеслав Ковалевский

Вы совершенно правы, объект это просто место в ОЗУ в битиками, а вот вся метаинформация хранится в таких вещах как ссылки.

0

Используйте Хекслет по максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Даю согласие на обработку персональных данных, соглашаюсь с «Политикой конфиденциальности» и «Условиями оказания услуг»

Рекомендуемые программы

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

Иконка программы Фронтенд-разработчик
Профессия
Разработка фронтенд-компонентов веб-приложений
30 июня 10 месяцев
Иконка программы Python-разработчик
Профессия
Разработка веб-приложений на Django
30 июня 10 месяцев
Иконка программы PHP-разработчик
Профессия
Разработка веб-приложений на Laravel
30 июня 10 месяцев
Иконка программы Node.js-разработчик
Профессия
Разработка бэкенд-компонентов веб-приложений
30 июня 10 месяцев
Иконка программы Fullstack-разработчик
Профессия
Новый
Разработка фронтенд и бэкенд компонентов веб-приложений
30 июня 16 месяцев
Иконка программы Верстальщик
Профессия
Вёрстка с использованием последних стандартов CSS
в любое время 5 месяцев
Иконка программы Java-разработчик
Профессия
Разработка приложений на языке Java
30 июня 10 месяцев
Иконка программы Разработчик на Ruby on Rails
Профессия
Создает веб-приложения со скоростью света
30 июня 5 месяцев