Чому java.util.Set не має get (int index)?


237

Я впевнений, що є вагомі причини, але чи могла б хто-небудь пояснити, чому java.util.Setне вистачає інтерфейсу get(int Index), чи якийсь подібний get()метод?

Здається, що набори чудово підходять для того, щоб укласти речі, але я не можу знайти елегантний спосіб вилучення з нього жодного предмета.

Якщо я знаю, що хочу перший елемент, я можу його використовувати set.iterator().next(), але в іншому випадку здається, що я повинен передати масив для отримання елемента з певним індексом?

Які належні способи отримання даних із набору? (крім використання ітератора)

Я впевнений, що той факт, що це виключено з API, означає, що є вагомі причини цього не робити - може хтось, будь ласка, просвітить мене?

EDIT: Деякі надзвичайно чудові відповіді тут, а декілька говорять про "більше контексту". Конкретним сценарієм був тест dbUnit, де я міг обґрунтовано стверджувати, що повернений набір із запиту містить лише 1 елемент, і я намагався отримати доступ до цього елемента.

Однак питання вірніше без сценарію, оскільки воно залишається більш сфокусованим:

Яка різниця між набором та списком .

Дякую всім за фантастичні відповіді нижче.


1
Чому ви отримаєте елемент із набору за індексом? Ви намагаєтесь використовувати набір як відсортований масив?
MSN

Конкретний екземпляр тут - тест dbUnit проти набору, повернутого із сплячого дзвінка. У моєму тесті доцільно припустити (тому що я стверджую), що повернутий об’єкт знаходиться в певному порядку, через мій IDataSet, який я використовував для його налаштування. Це нетиповий випадок, але це викликає мою цікавість щодо API.
Марті Пітт

1
Додавання речей у певному порядку не означає, що вони залишатимуться такими, якщо ви не скористаєтеся спеціальною реалізацією набору.
Майкл Майерс

1
"Якщо я знаю, що хочу перший елемент, я можу використовувати set.iterator (). Next ()" - Цей рядок насправді не має сенсу. Ви справді говорите: "Якщо я знаю, що хочу перший елемент, за визначенням першого пункту реалізації, тоді я можу ...". Набір не має порядку, тому індексований доступ не має сенсу. Тепер, якщо був ArrayListSet, це мало б більше сенсу (просто киньте в "Список" і будьте щасливі). Можливо, ви могли б дати більше контексту для питання?
осяяння

Набір не має порядку! Деякі його реалізації є, але деякі реалізації явно впорядковані певним чином.
reinierpost

Відповіді:


176

Тому що набори не мають впорядкування. Деякі реалізації (зокрема, що реалізують java.util.SortedSetінтерфейс), але це не є загальною властивістю наборів.

Якщо ви намагаєтесь використовувати набори таким чином, вам слід скористатися списком.


10
@matt b: Ні, я думаю, він повинен це врахувати. Мислення - це добре. ;)
Майкл Майерс

10
Поміркуйте, тоді зробіть це.
Джо Філліпс

21
"Поміркуй" - це правильне словосполучення. Можливі дві проблеми (а) Він використовує набір, коли йому слід користуватися чимось іншим, або (б) він намагається робити речі з наборами, які вони не підтримують, але щоб він міг зробити інший спосіб. Добре врахувати, який із них є таким.
kenj0418

6
Можливо, простішою відповіддю є використання відсортованого набору. (Я припускаю, що унікальність зіграла свою роль під час вибору набору). Але у мене виникає запитання, оскільки SortedSet замовлений, чому так, що в api немає методу get.
uncaught_exceptions

5
@HDave: Ні, той факт, що кілька реалізацій структури даних поділяють властивість, не робить його властивістю самої структури даних. Дві з трьох часто використовуваних реалізацій List (ArrayList і Vector) мають випадковий доступ, але це не робить випадковий доступ властивістю Lists.
Майкл Майерс

74

Насправді це питання, що повторюється, при написанні додатків JavaEE, які використовують Об'єктно-реляційне картографування (наприклад, зі сплячого режиму); а з усіх людей, які тут відповіли, Андреас Петерссон - єдиний, хто зрозумів справжнє питання і запропонував правильну відповідь на нього: у Java відсутній UniqueList! (або ви можете також назвати його OrdersSet або IndexedSet).

Максвінг згадав про цей випадок використання (у якому вам потрібні замовлені ТА унікальні дані), і він запропонував SortedSet, але це не те, що дійсно потрібно Марті Пітту.

Цей "IndexedSet" НЕ збігається з SortedSet - в SortedSet елементи сортуються за допомогою порівняльника (або за допомогою їх "природного" впорядкування).

Але натомість він ближче до LinkedHashSet (який також запропонували інші), а ще більше - до (теж неіснуючого) "ArrayListSet", оскільки це гарантує повернення елементів у тому ж порядку, як вони були вставлені.

Але LinkedHashSet - це реалізація, а не інтерфейс! Необхідний інтерфейс IndexedSet (або ListSet, OrdersSet або UniqueList)! Це дозволить програмісту вказати, що йому потрібна колекція елементів, що мають певний порядок і без дублікатів, а потім інстанціювати його будь-якою реалізацією (наприклад, реалізацією, наданою Hibernate).

Оскільки JDK є відкритим кодом, можливо, цей інтерфейс буде нарешті включений у Java 7 ...


3
Чудова відповідь, наскільки це йдеться, але що ми робимо тим часом?
HDave

впевнений, що так. Я використав список як багатоматеріалів та onetomany ОРМ в сплячому режимі раніше. я зіткнувся з проблемою (або дефектом), коли запит на приєднання зліва, пов’язаний із більш ніж 3 суміжними особами, було викинуто виняток. шукайте тут докладнішу інформацію ( jroller.com/eyallupu/entry/… ). щоб обійти цю проблему, використовуючи встановлену як колекцію ORM картування. але якщо чесно сказати, набір не зручний для доступу до програмування, а також тоді, коли вам потрібна колекція замовлень. те, що нам насправді потрібно, - це «індексований набір», як те, що сказав Сорін Постельніку, «СОРТ і УНІКАЛЬНО»
Горацеман

2
Колекції Apache Commons мають ListOrderedSetте, що було потрібно ОП 7 років тому (і мені це було потрібно сьогодні).
Поль

@Paul: Це дійсно щось, що виглядає дуже добре. На жаль, він все ще має 3 недоліки: 1) Це клас, а не інтерфейс. 2) Це не в JDK. 3) Це не повертаються запити в сплячку.
Сорін Постельніку

Так, але крім цих 3 основних недоліків це ідеально! :) В ретроспективі я повинен був розмістити свій коментар до питання, а не вашу відповідь - я відключився What is needed is an IndexedSet (or ListSet, or OrderedSet, or UniqueList)...і проігнорував ...interface. Вибач за це!
Пол

29

Просто додайте один пункт, який не був згаданий у відповіді mmyers .

Якщо я знаю, що хочу перший елемент, я можу використовувати set.iterator (). Next (), але в іншому випадку здається, що я повинен передати в масив, щоб отримати елемент у певному індексі?

Які належні способи отримання даних із набору? (крім використання ітератора)

Ви також повинні ознайомитися з SortedSet інтерфейсом (найпоширеніша реалізація якого TreeSet).

SortedSet - це набір (тобто елементи унікальні), які впорядковуються впорядкованим шляхом природного упорядкування елементів або їх використанням Comparator. Ви можете легко отримати доступ до перших і останніх елементів за допомогою first()іlast() методами. АSortedSetПрігождается кожен раз в той час, коли вам потрібно , щоб зберегти свою колекцію як дублювати вільні і впорядковані певним чином.

Редагувати : Якщо вам потрібен набір, елементи якого зберігаються в порядку вставки (подібно до списку), подивіться LinkedHashSet.


Мені подобається LinkedHashSet сам. Але так, це добре згадати. +1
Майкл Майєрс

Дякую, я трохи переробив відповідь. (Здається, у мене деякі аспекти TreeSet переплутані з аспектами LinkedHashSet.)
Jonik,

25

Цей вид призводить до питання, коли ви повинні використовувати набір і коли ви повинні використовувати список. Зазвичай порада стосується:

  1. Якщо вам потрібні впорядковані дані, використовуйте Список
  2. Якщо вам потрібні унікальні дані, використовуйте набір
  3. Якщо вам потрібно обоє, використовуйте або: SortedSet (для даних, упорядкованих компаратором), або OrdersSet / UniqueList (для даних, упорядкованих вставкою). На жаль, API Java ще не має OrdersSet / UniqueList.

Четвертий випадок, який часто виникає, - це те, що вам це не потрібно. У цьому випадку ви бачите, що деякі програмісти йдуть зі списками, а деякі із наборами. Особисто мені здається дуже шкідливим бачити набір як список, не замовляючи його, - бо це справді інший звір. Якщо вам не потрібні такі речі, як встановити унікальність або встановити рівність, завжди надайте переваги спискам.


2
якщо ви не визначені, прийміть колекцію <T> або навіть відключену <T> та ініціалізуйте як список.
Андреас Петерсон

Це була б сумка або мультисети. Але Java не підтримує їх; вони кажуть, що ви повинні просто використовувати Collection <T> безпосередньо.
Механічний равлик

4. вам потрібні унікальні дані та не піклується про замовлення. НЕ МОЖЕТЕ використовувати набір. Список, сумка або мультисети працюватимуть.
Ендрю Галлаш

17

Я не впевнений, чи хтось це написав саме так, але вам потрібно зрозуміти наступне:

У наборі немає "першого" елемента.

Тому що, як говорили інші, набори не мають впорядкування. Набір - це математичне поняття, яке конкретно не включає впорядкування.

Звичайно, ваш комп'ютер не може зберегти список речей, які не впорядковані в пам'яті. Це повинно мати впорядкування. Всередині це масив чи пов'язаний список чи щось таке. Але ви насправді не знаєте, що це таке, і він насправді не має першого елемента; Елемент, який виходить «першим», виходить таким чином випадково, і може бути не перший наступний раз. Навіть якщо ви вжили заходів, щоб «гарантувати» певний перший елемент, він все одно виходить випадково, тому що ви просто трапилися правильно для однієї конкретної реалізації набору; інша реалізація може не працювати таким чином з тим, що ви зробили. Насправді ви, можливо, не знаєте того, що використовуєте, як добре, як вважаєте.

Люди натрапляють на ВСЕ. THE. ЧАС. із системами RDBMS і не розумію. Запит RDBMS повертає набір записів. Це однотипний набір з математики: невпорядкована колекція предметів, лише в цьому випадку предмети є записами. Результат запиту RDBMS взагалі не має гарантованого порядку, якщо ви не використовуєте пункт ORDER BY, але весь час люди припускають, що він це робить, а потім відключають себе в якийсь день, коли форма їх даних або коду трохи змінюється і запускає оптимізатор запитів для роботи по-іншому і раптом результати не виходять у тому порядку, який вони очікують. Зазвичай це люди, які не звернули уваги в класі баз даних (або під час читання документації чи навчальних посібників), коли їм було пояснено наперед, що результати запитів не мають гарантованого замовлення.


Хе-х, і звичайно замовлення зазвичай змінюється відразу після того, як код увійде у виробництво, коли це занадто повільно, тому вони додають індекс, щоб пришвидшити запит. Тепер код працює швидко, але дає неправильні відповіді. І ніхто не помічає три-чотири дні ... якщо пощастить. Якщо вам не пощастило, ніхто не помічає протягом місяця ...
TMN

Я не думаю, що він цього не пропустив (можливо, він неохайний з нотацією). Він не хоче першого елемента з множини, він хоче довільний елемент з набору. Ви можете надати йому довільний елемент, оскільки Setє Iterable.
Елазар Лейбович

Ви говорите про get (index) за індексом. Що щодо отримання (Об'єкта) рівності?
Кумар Маніш

10

деякі структури даних відсутні у стандартних колекціях Java.

Сумка (як набір, але може містити елементи кілька разів)

UniqueList (упорядкований список, може містити кожен елемент лише один раз)

здається, вам знадобиться унікальний список у цьому випадку

якщо вам потрібні гнучкі структури даних, вас можуть зацікавити колекції Google


1
Чи надає Гува "UniqueList"?
Майк Риландер

ні, але ви можете мати java.util.LinkedHashSet, який має подібні властивості.
Andreas Petersson

7

Це правда, елемент у Set не впорядкований за визначенням колекції Set. Таким чином, вони не можуть отримати доступ через індекс.

Але чому ми не маємо метод get (object), не надаючи індекс як параметр, а об'єкт, рівний тому, якого ми шукаємо? Таким чином, ми можемо отримати доступ до даних елемента всередині набору, просто знаючи його атрибути, використовувані рівним методом.


7

Якщо ви збираєтеся робити безліч випадкових доступів за індексом у наборі, ви можете отримати представлення масиву його елементів:

Object[] arrayView = mySet.toArray();
//do whatever you need with arrayView[i]

Однак є два основні недоліки:

  1. Це не є ефективною пам'яттю, оскільки для цього потрібно створити масив для всього набору.
  2. Якщо набір буде змінено, подання застаріло.

5

Це тому, що Set гарантує лише унікальність, але нічого не говорить про оптимальні схеми доступу та використання. Тобто, набір може бути списком або картою, кожна з яких має дуже різні характеристики пошуку.


5

Єдиною причиною, про яку я можу подумати для використання числового індексу в наборі, буде ітерація. Для цього використовуйте

for(A a : set) { 
   visit(a); 
}

Неправда, як щодо доступу до випадкового елемента?
Джеремі Салвен

Ха, га. хороший пункт :) але це було б дуже схильне до неправильного використання, я впевнений.
Гюго

3

Я зіткнувся з ситуаціями, коли насправді хотів відсортований набір з доступом через індекс (я погоджуюся з іншими плакатами, що звертатись до несортованого набору з індексом немає сенсу). Прикладом може бути дерево, де я хотів, щоб діти були відсортовані, а дітей-дублікатів не дозволяли.

Мені потрібен був доступ через індекс, щоб відобразити їх, і наборі атрибутів були корисні для ефективного усунення дублікатів.

Не знайшовши підходящої колекції в колекціях java.util або google, я зрозумів, що це легко реалізувати самостійно. Основна ідея - обернути SortedSet і створити список, коли потрібен доступ через індекс (і забути список, коли сортується Setet). Це, звичайно, працює ефективно лише тоді, коли зміна загорнутого SortedSet та доступ до списку відокремлюються протягом життя колекції. В іншому випадку він веде себе як список, який сортується часто, тобто занадто повільно.

Що стосується великої кількості дітей, це значно покращило продуктивність у порівнянні зі списком, який я постійно сортував за допомогою Collections.sort.


2

Зауважте, що лише через 2 основні структури даних можна отримати доступ через індекс.

  • До структури даних масиву можна отримати доступ через індекс із O(1)часовою складністю для досягнення get(int index)операції.
  • До структури даних LinkedList також можна отримати доступ через індекс, але з O(n)тимчасовою складністю для досягнення get(int index)операції.

У Java ArrayListреалізується за допомогою структури даних Array .

Хоча структура даних Set зазвичай може бути реалізована за допомогою структури даних HashTable / HashMap або BalancedTree , для швидкого виявлення наявності елемента та додавання неіснуючого елемента, як правило, добре реалізований набір може досягти O(1)складності в часі contains. У Java HashSet- це найпоширеніша реалізація набору , вона реалізована за допомогою виклику HashMapAPI і HashMapреалізована за допомогою окремого ланцюжка із пов'язаними списками (комбінація Array та LinkedList ).

Оскільки набір може бути реалізований за допомогою різної структури даних, для нього не існує get(int index)методу.


Пальчикові дерева (див. Data.Sequence.lookupФункцію Haskell ) також дозволяють отримувати доступ через індекс ( точніше O(1)біля кінців O(log n), точніше O(min(log(k), log(n-k)))), також двійкові дерева (див. Data.Set.lookupIndexФункцію Haskell ). Отже, ваше початкове твердження про те, що "Будь ласка, зверніть увагу, що лише через 2 основні структури даних можна отримати доступ через індекс" не є правильним.
крапка з комою

1

Причина встановлення інтерфейсу не має дзвінка типу індексу або навіть ще більш базового, наприклад першого () або останнього (), полягає в тому, що це неоднозначна операція, а отже, потенційно небезпечна операція. Якщо метод повертає Set, а ви викликаєте, скажіть, метод first () на ньому, який очікуваний результат, враховуючи, що загальний набір не дає гарантій на замовлення? Об'єкт, що отримує результат, може дуже різнитися між кожним викликом методу, інакше він не може і впаде в помилкове почуття безпеки, поки бібліотека, яку ви використовуєте, не змінить реалізацію внизу, і тепер ви виявите, що весь код порушений для немає конкретної причини.

Пропоновані тут пропозиції щодо обхідних шляхів хороші. Якщо вам потрібен індексований доступ, скористайтеся списком. Будьте обережні з використанням ітераторів чи toArray із загальним набором, оскільки а) немає гарантії на замовлення; б) немає гарантії, що замовлення не зміниться з наступними викликами або з різними базовими реалізаціями. Якщо вам потрібно щось середнє, SortedSet або LinkedHashSet - це те, що ви хочете.

// Я б хотів, щоб інтерфейс Set мав елемент get-random-element.


1

java.util.Set- це колекція не упорядкованих предметів. Не має сенсу, якщо у Set є get (int index), тому що Set не має індексу, а також ви можете лише здогадуватися про значення.

Якщо ви дійсно хочете цього, введіть метод, щоб отримати випадковий елемент із Set.


0

Ви можете зробити new ArrayList<T>(set).get(index)


Це повертає список наборів, а get (index) повертає набір. Швидше, я використовував: new ArrayList<T>(t).get(0) я думаю, що існує справедлива опозиція до ідеї отримання певного елемента з набору за допомогою індексу. Але було б добре, якби Set мав єдину () функцію-члена, яка для наборів розміру 1 забезпечувала простий доступ до єдиного елемента в наборі. Це врятувало б вищезгадане new ArrayListабоfor (Foo foo : foos) { return foo; }
Doug Moscrop

0

Якщо ви не заперечуєте проти набору для сортування, то, можливо, вам буде цікаво поглянути на проект індексованого дерева-карти .

Розширений TreeSet / TreeMap забезпечує доступ до елементів за індексом або отримання індексу елемента. А реалізація заснована на оновленні ваг вузлів у дереві RB. Тож ніякої ітерації чи резервного копіювання за списком тут.


0

Set - це інтерфейс, і деякі його класи реалізації - HashSet, TreeSet і LinkedHashSet. Він використовує HashMap під кришкою для зберігання значень. Оскільки HashMap не зберігає порядок, неможливо отримати значення за індексом.

Тепер ви повинні думати, як Set використовує HashMap, оскільки HashMap зберігає ключ, пару значень, але набір цього не робить. дійсне питання. коли ви додаєте елемент у Set, внутрішньо, він підтримує HashMap, де ключовим є елемент, який ви хочете ввести у Set, а значення - константа манекена. Нижче представлена ​​внутрішня реалізація функції додавання. Отже, всі ключі в HashMap матимуть однакове постійне значення.

// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();

public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

Усі Setреалізації s використовуються HashMapпід кришкою для зберігання значень. Чи можете ви обґрунтувати цю претензію TreeSet?
сіра борода

1
the keys in the HashMap will have the same constant value ключі у HashMapзаповіті відображатимуть одне і те ж незміннеObject
сіруватий


-3

Щоб отримати елемент у наборі, я використовую наступний:

public T getElement(Set<T> set, T element) {
T result = null;
if (set instanceof TreeSet<?>) {
    T floor = ((TreeSet<T>) set).floor(element);
    if (floor != null && floor.equals(element))
    result = floor;
} else {
    boolean found = false;
    for (Iterator<T> it = set.iterator(); !found && it.hasNext();) {
    if (true) {
        T current = it.next();
        if (current.equals(element)) {
        result = current;
        found = true;
        }
    }
    }
}
return result;
}

функція не те, про що задавали питання. нам потрібен індекс, а не значення. яка ваша функція все-таки виконується? схоже, що він просто повертає елемент, якщо він був рівний елементу всередині. що це робить, що містить () не?
Янус Трольсен

Де Tвизначено? Чому if (true)?
квантовий
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.