2017-02-05 14 views
3

Вчера я столкнулся с проблемой, когда писал свою домашнюю работу. Я закончил домашнюю работу, но я до сих пор не понимаю, почему работает мой код. Мне пришлось написать функцию сортировки, которая принимает varargs любого сопоставимого общего объекта в качестве аргумента и возвращает аргумент. Проблема заключалась в том, что мне пришлось возвращать массив отсортированных объектов. Поэтому мне пришлось больше узнать о списках и массивах varargs.Преобразование общего списка в массив. Зачем мне нужен клон?

Функция была определена следующим образом.

public <T extends Comparable<T>> T[] stableSort(T ... items) 

и внутри функции я составил список, который я бы сортировал и выполнял всю работу.

List<T> list = new ArrayList<T>(Arrays.asList(items)); 

и в конце функции я возвращал список toArray, чтобы он соответствовал типу вывода T [].

list.toArray(items.clone()); 

Мой вопрос, так как я уже сделал список из списков параметров, почему я должен делать items.clone() внутри функции ToArray. Мне казалось, что это делает для меня две вещи. Я думал, что arrays.asList() будет клонировать значения массива для списка, и я не понимаю, почему я делаю это снова в конце кода в toArray(). Я знаю, что это был правильный способ написать это, потому что вчера я закончил домашнюю работу и выяснил это на форумах этого класса, но я до сих пор не понимаю, почему.

EDIT

Задача требуется мне, чтобы создать новый массив с отсортированными файлами и вернуть его вместо этого. Из-за Type Erasure невозможно создать экземпляр массива общего типа без ссылки на класс, который соответствует родовому. Однако массив varargs имеет тип T, поэтому я должен был клонировать массив типа, который соответствует общим ограничениям. Который я не знал, как сделать вовремя. Поэтому я решил использовать список, чтобы облегчить мне время до крайнего срока.

+2

Разница заключается в том, что 'asList' возвращает' List', а 'toArray' возвращает массив. – Andremoniy

+1

@Andremoniy Я думаю, вопрос в том, почему клон? * – CKing

+0

Это кажется поразительно бессмысленным. Массив обернут как список, который не нужен, но для того, чтобы убедиться, что он завернут в другой список, также не нужен, тогда клонируется массив, который создает массив со всеми элементами, но копирует в него все элементы из список, только что созданный, т.е. те же элементы. –

ответ

3

Мой вопрос, так как я уже сделал список из списков параметров, почему я должен делать items.clone()

Вы правы. К сожалению, компилятор не сможет определить тип массива, если вы просто используете метод toArray(). Вы должны получить ошибку компиляции: Невозможно преобразовать из Object [] в T []. Вызов item.clone() необходим, чтобы помочь компилятору в выводе типа. Альтернативный подход состоял бы в том, чтобы сказать return (T[])list.toArray

При этом я бы не рекомендовал ни один из подходов. На самом деле не имеет смысла преобразовывать массив в список и преобразовывать его обратно в массив. Я не вижу каких-либо значительных приемов, которые вы бы даже поняли из этого кода.

+0

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

+0

Может ли downvoter быть немного более гражданским и оставить комментарий, чтобы объяснить, что не так с моим ответом? Это кажется немного неэтичным ИМО. – CKing

+0

«Альтернативный подход состоял бы в том, чтобы сказать« return (T []) list.toArray' »Если вы имеете в виду« return (T []) list.toArray(); ', это ужасная идея, так как это загрязнение кучи и приведет к исключению класса, когда оно возвращается в контекст, который ожидает конкретного типа для 'T'. – newacct

0

Вы должны использовать это:

List<T> list = new ArrayList<T>(Arrays.asList(items)); 

, когда вы хотите сделать инлайн декларацию.

Например:

List<String> list = new ArrayList<String>(Arrays.asList("aaa", "bbb", "ccc")); 
2

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

Я думал, что arrays.asList() будет клонировать значения массива в список, и я не понимаю, почему я делаю это снова в конце кода в toArray().

Это, вероятно, именно так, как он набран, но это должно быть ясно, что вы не клонировать Объектами в массиве, но только сделать новый список с ссылками на объекты в массив. Сами объекты будут такими же в массиве, что и в Списке. Я считаю, что это, вероятно, то, что вы имели в виду, но терминология здесь может быть сложной.

Я думал Arrays.asList() будет клонировать значения массива в список ...

не очень. Использование Arrays.asList(T[] items) предоставит вид на массив items, который реализует интерфейс java.util.List. Это список фиксированного размера. Вы не можете добавить к нему. Изменения в нем, такие как замена элемента или сортировка на месте, будут переданы в базовый массив. Так что, если вы сделаете это

List<T> l = Arrays.asList(T[] items); 
l.set(0, null); 

... вы просто установите элемент с индексом 0 фактического массива items обнулить.

Часть кода, где вы это делаете

List<T> list = new ArrayList<T>(Arrays.asList(items)); 

может быть написано как это:

List<T> temp = Arrays.asList(items); 
List<T> list = new ArrayList<T>(temp); 

Первая строка «просмотр», то вторая линия будет эффективно создавать новый java.util.ArrayList и заполнить его значениями вида в том порядке, в котором они возвращаются своим итератором (который является всего лишь порядком в массиве). Поэтому любые изменения в list, которые вы сейчас делаете, не меняют массив items, но имейте в виду, что это все еще только список ссылок. items и list ссылаются на те же объекты, только со своим заказом.

Мой вопрос в том, что я уже сделал список из varargs, почему мне нужно делать items.clone() внутри функции toArray.

Здесь может быть две причины. Первое, как сказал CKing в его/ее ответе. Из-за стирания типа и способа реализации массивов в Java (существуют отдельные типы массивов в зависимости от того, является ли это массивом примитивов или ссылок), JVM не будет знать, какой тип массива будет создан, если вы только что назвали toArray() в списке, который поэтому этот метод имеет тип возврата Object[]. Поэтому, чтобы получить массив определенного типа, вы должны предоставить массив методу, который можно использовать во время выполнения, чтобы определить тип. Это часть Java API, в которой тот факт, что дженерики работают с помощью стирания типов, не сохраняются во время выполнения, а конкретный способ работы массивов - все это вместе, чтобы удивить разработчика. A bit of abstraction is leaking there.

Но может быть и вторая причина.Если вы поедете, проверьте toArray(T[] a) method in the Java API, вы увидите эту часть:

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

Пусть некоторый код на другой разработчика использует свой метод stableSort так:

T[] items; 
// items is created and filled... 
T[] sortedItems = stableSort(items); 

Если вы не сделали клон, что произошло бы в ваш код будет таким:

List<T> list = new ArrayList<T>(Arrays.asList(items)); 
// List is now a new ArrayList with the same elements as items 
// Do some things with list, such as sorting 
T[] result = list.toArray(items); 
// Seeing how the list would fit in items, since it has the same number of elements, 
// result IS in fact items 

Итак, теперь вызывающий код получает sortedItems обратно, но этот массив является тот же самый массив, в котором он прошел, а именно items. Видишь ли, varargs - не что иное, как синтаксический сахар для метода с аргументом массива и реализуются как таковые. Возможно, вызывающий не ожидал, что массив, который он передал в качестве аргумента, который должен быть изменен, и может все еще нуждаться в массиве с исходным порядком. Выполнение клона сначала позволит избежать этого и сделать эффект метода менее неожиданным. Хорошая документация по вашим методам имеет решающее значение в таких ситуациях.

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

EDIT:

На самом деле, ваш код может быть гораздо проще. Вы добьетесь же с:

T[] copy = items.clone(); 
Arrays.sort(copy); 
return copy; 

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

+0

Да, мое назначение состояло в том, чтобы реализовать алгоритм сортировки. Которая сама по себе была очень простой, но общие типы и varargs, с которыми я не сталкивался, меня так сильно смутили. – Vato

0

Кстати, вы не должны использовать return list.toArray(items.clone()); Вы могли бы использовать, например, return list.toArray(Arrays.copyOf(items, 0));, где вы передаете в list.toArray() пустой массив, который не содержит ни один из аргументов с items.

Весь смысл передачи аргумента версии list.toArray(), которая принимает аргумент, заключается в предоставлении объекта массива, фактический класс выполнения которого является фактическим классом среды выполнения объекта массива, который он хочет вернуть. Это могло быть достигнуто с помощью items.clone() или с самим items (хотя это могло бы вызвать list.toArray() для записи результирующих элементов в исходную матрицу, на которую указывает items, что вы, возможно, не захотите) или с, как я показал выше, пустым массив, который имеет тот же класс времени выполнения.

Кстати, необходимость передать аргумент list.toArray() не является проблемой типа generics. Даже если бы вы написали это с помощью pre-generics Java, вам пришлось бы сделать то же самое. Это связано с тем, что версия List::toArray(), которая не принимала аргументов, всегда возвращает объект массива, фактический класс выполнения которого равен Object[], так как List во время выполнения не знает, каков его тип компонента. Чтобы он возвращал объект массива, реальный класс выполнения которого был чем-то другим, вам нужно было дать ему пример массива подходящего класса времени выполнения.Вот почему pre-generics Java также имела версию List::toArray(), которая приняла один аргумент; хотя в предварительных дженериках оба метода были , объявленные, чтобы вернуть Object[], они различаются, так как фактический класс времени выполнения отличается.