Вопрос №6061 от пользователя Andy в уроке «Модуль 1. Тест.», курс «Java: Структуры данных»
В офф. решении есть строчка
System.arraycopy(m, 0, a, 0, size);
Никакого приведения типов там не делается, тесты проходят только потому, что типы везде int.
System.arraycopy - это метод который на вход принимает два массива Object[] посему им можно пользоватья с любыми массивами.
Видать опять я чего-то недопонял. По простоте душевной думал, что при вызове 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<>();
Т.е. наш велосипедик тут не при чем. Где-то что-то не так делаю или рассуждаю.
По простоте душевной думал, что при вызове toArray(T1[] a) даже если в коллекции находится тип T, то вернется тип T1
так и будет, согласно сигнатуре метода вернется тот тип который переда во входном аргументен. То что я говорил про System.arraycopy, повторюсь:
System.arraycopy - это метод который на вход принимает два массива Object[] посему им можно пользоватья с любыми массивами.
Использовать не означает что он магическим образом Integer в String начится преобразовывать, это лиш означает что оно не провиряет совместимотьс типов и тупо на вход требует Object[], а за совместимость типов должна отвечать догика верхнего уровня. Соотвественно когда его просим "коня" превратить в "осьминога" он гераически умирает =)
Подобное поведение к слову указано в спеке: 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.
т.е. кидает исключение в рантайме, но компайлер это все скушает и приготовит байткод.
за совместимость типов должна отвечать догика верхнего уровня.
А где должна быть эта логика? Вне метода/класса? Зачем тогда декларируется:
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.
Можно показать юзкейс, как это на практике выглядит: что за внешний код, и как выглядит вызов с получением результата?
Можно показать юзкейс, как это на практике выглядит: что за внешний код, и как выглядит вызов с получением результата?
Не совсем понял вопрос, вот два варианта:
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);
Хотя перед копирование лучше проверок не ставить так как явно в еще более выскокуровневом коде ошибка и не пува сейчас ошибка может быть скрыта и потом долго и трудно будет ее отловить.
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.
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[].
На данный момент у меня сложилось впечатление, что "the runtime type of the returned array is that of the specified array" трактуется не как должен вернуть массив того же типа, а как должен попытаться, а если не получилось - значит не шмогла, разработчик сам виноват, подсовывает что попало. Просто потому, что нет единого механизма преобразования типов (ну, кроме как низложения до Object).
Ссылка "result" как раз и создается что бы не трогать ссылку "a".
Это я не понял. Ссылка она вроде и в Африке ссылка, просто указатель на место в памяти? Она же должна быть одна и та же и для "result" и для "a"?
Это я не понял. Ссылка она вроде и в Африке ссылка, просто указатель на место в памяти? Она же должна быть одна и та же и для "result" и для "a"?
Вы правы что она указывает на один и тот же объектв в оперативной памяти, но у каждоый ссылки свой тип и эти типы могут быть разные (хотя указывать на один и тот же объект в оперативной памяти). Простой пример:
String a = "ololo";
Object b = a;
Согданы две ссылки с разынм типом. Несмотря на то что ссылка b
узкаывает в оперативной памяти на String
ее нельзя использовтаь(без явного приведения обратно к String
) с методами и логикой которуая требует на вход тип String
.
Понятно, значит инфа о типе хранится в ссылке, а не в объекте.
Вы совершенно правы, объект это просто место в ОЗУ в битиками, а вот вся метаинформация хранится в таких вещах как ссылки.
Используйте Хекслет по максимуму!
- Задавайте вопросы по уроку
- Проверяйте знания в квизах
- Проходите практику прямо в браузере
- Отслеживайте свой прогресс
Зарегистрируйтесь или войдите в свой аккаунт
С нуля до разработчика. Возвращаем деньги, если не удалось найти работу.







