Як повідомити, що порядок вставки має значення на карті?


24

Я виймаю набір кортежів із бази даних і вкладаю їх у карту. Запит до бази даних є дорогим.

Немає очевидного природного упорядкування елементів на карті, але порядок вставки все-таки має значення. Сортування карти було б важкою операцією, тому я хочу уникати цього, враховуючи, що результат запиту вже відсортований так, як мені хочеться. Тому я просто зберігаю результат запиту в a LinkedHashMapі повертаю карту методом DAO:

public LinkedHashMap<Key, Value> fetchData()

У мене є метод, processDataякий повинен виконувати деяку обробку на карті - змінювати деякі значення, додавати нові ключі / значення. Визначається як

public void processData(LinkedHashMap<Key, Value> data) {...}

Однак декілька ліній (Sonar тощо) скаржаться, що Тип 'даних' повинен бути інтерфейсом, таким як 'Map', а не реалізацією "LinkedHashMap" ( кальмар S1319 ).
Так в основному йдеться про те, що я мав би мати

public void processData(Map<Key, Value> data) {...}

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

Я не хочу використовувати SortedMap, тому що він (від javadoc ofjava.util.SortedMap ) "замовляється відповідно до природного впорядкування його ключів, або компаратором, який зазвичай надається під час створення сортованої карти".

Мої клавіші не мають природного впорядкування , і створення компаратора робити нічого не здається багатослівним.

І я все ще хотів би, щоб це була карта, щоб скористатися тим, putщоб уникнути повторюваних ключів і т. Д. Якщо ні, то dataміг би бути а List<Map.Entry<Key, Value>>.

Тож як я можу сказати, що мій метод хоче вже відсортовану карту ? На жаль, java.util.LinkedMapінтерфейсу немає , інакше я би цим скористався.

Відповіді:


56

Тож використовуйте LinkedHashMap.

Так , ви повинні використовувати Mapнад конкретної реалізації щоразу , коли це можливо, і так , це є найкращою практики.

Однак, це надзвичайно специфічна ситуація, коли реалізація Mapнасправді має значення. Це не буде правдою для 99,9% випадків у вашому коді, коли ви користуєтесь Map, і все ж ось ви знаходитесь у цій 0,1% ситуації. Sonar цього не може знати, тому Sonar просто каже вам уникати використання конкретної реалізації, оскільки це було б правильним у більшості випадків.

Я б заперечував, що якщо ви можете скласти справу про використання конкретної реалізації, не намагайтеся наносити помаду на свиню. Вам потрібно а LinkedHashMap, а не Map.

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


1
Прагматичний підхід, який мені подобається.
Відар С. Рамдал

20
Я майже повністю згоден з відповіддю. Я просто сказав би, що ти не проклятий Сонару. Ви завжди можете налаштувати його, щоб ігнорувати цю конкретну помилку / попередження. Див stackoverflow.com/questions/10971968 / ...
Володимир Стокич

11
if you are new to programming and stumble upon this answer, don't think this allows you to go against best practice because it doesn't.- Гарна порада, якби існувало таке поняття, як "найкраща практика". Краща порада: навчіться приймати правильні рішення. Дотримуйтесь практики, якщо це має сенс, але нехай інструменти та влада керують вашим процесом мислення, а не диктують його.
Роберт Харві

13
Примітка. Коли сонар вам повідомляє про щось, ви можете закрити це як "не буде вирішено" і залишити нотатку, чому ви цього не зробите. Як такий, не тільки сонар перестане вас турбувати, але і ви знайдете, чому ви це зробили.
Вальфрат

2
Я думаю, що аспектом, який робить це винятком із загального принципу, є те, що LinkedHashMap має контракт, специфічний для цієї реалізації та не виражений у будь-якому інтерфейсі. Це не звичайний випадок. Тож єдиний спосіб виразити залежність від цього контракту - це використовувати тип реалізації.
Дана

21

Ви ведете боротьбу з трьома речами:

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

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

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


2
"LinkedHashMap може бути неправильним інструментом для роботи". Так, можливо. Коли я кажу, що мені потрібно OrderedMap, я можу так само добре сказати UniqueList. Поки це якась колекція з певним порядком ітерації, вона перезаписує дублікати на вставці.
Відар С. Рамдал

2
@ VidarS.Ramdal Запит на базу даних буде ідеальним місцем для видалення дублікатів. Якщо ваша база даних не може цього зробити, ви завжди можете зберігати тимчасові Setлише ключі під час створення списку a як спосіб їх помітити.
Blrfl

О, я бачу, я викликав плутанину. Так, результат запиту до бази даних не містить дублікатів. Але processDataзмінює карту, замінюючи деякі значення, вводячи якісь нові ключі / значення. Тож processDataможна було б ввести дублікати, якби він працював на чомусь іншому, ніж на a Map.
Відар С. Рамдал

7
@ VidarS.Ramdal: Здається, вам потрібно написати власний UniqueList(або OrderedUniqueList) і скористатися цим. Це досить просто і робить призначене використання зрозумілішим.
TMN

2
@TMN Так, я почав думати в цьому напрямку. Якщо ви хочете опублікувати свою пропозицію як відповідь, це, безумовно, отримає мою підтримку.
Відар С. Рамдал

15

Якщо все, що ви отримуєте, LinkedHashMap- це можливість перезаписувати дублікати, але ви дійсно використовуєте це як List, тоді я б запропонував, що краще зв’язати це використання із власною власною Listреалізацією. Ви можете базувати його на наявному класі колекцій Java і просто замінити будь-який addі removeметоди оновити ваш резервний магазин і відслідковувати ключ, щоб забезпечити унікальність. Надавши це своєрідне ім'я, ProcessingListце дасть зрозуміти, що аргументи, представлені вашому processDataметоду, потрібно обробляти певним чином.


5
Це все одно може бути хорошою ідеєю. Чорт забирає, ви навіть можете мати однорядний файл, який створюється ProcessingListяк псевдонім для LinkedHashMap- ви завжди можете вирішити замінити його чимось іншим пізніше, доки не збережете публічний інтерфейс неушкодженим.
CompuChip

11

Я чую, як ви говорите: "У мене є одна частина моєї системи, яка виробляє LinkedHashMap, і в іншій частині моєї системи мені потрібно прийняти лише об'єкти LinkedHashMap, які були створені першою частиною, оскільки ті, які були створені іншим процесом, виграли" t правильно працювати. "

Це змушує мене думати, що проблема тут насправді полягає в тому, що ви намагаєтеся використовувати LinkedHashMap, оскільки він здебільшого підходить до даних, які ви шукаєте, але насправді він не може бути замінений будь-яким іншим екземпляром, ніж створеним вами. Насправді ви хочете зробити, це створити власний інтерфейс / клас, який створює ваша перша частина, а друга ваша частина споживає. Він може обернути "справжній" LinkedHashMap і забезпечити отримання Map Map або реалізувати інтерфейс Map.

Це трохи відрізняється від відповіді CandiedOrange тим, що я рекомендував би інкапсулювати реальну карту (та делегувати дзвінки до неї за потребою), а не продовжувати її. Іноді це одна з тих святих війн стилю, але мені впевнено звучить, що це не «Карта з деякими додатковими матеріалами», це «Мій мішок корисної інформації про стан, який я можу внутрішньо представляти за допомогою Мапи».

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


Мені це подобається думати - я був там :) MyBagOfUsefulInformationбуде потрібен метод (або конструктор) для заповнення його: MyBagOfUsefulInformation.populate(SomeType data). Але dataповинен був бути відсортований результат запиту. То що було SomeTypeб, якби ні LinkedHashMap? Я не впевнений, що мені вдалося зламати цей
лов

Чому не може MyBagOfUsefulInformationбути створений DAO або що-небудь генерує дані у вашій системі? Чому взагалі потрібно виставляти основну карту на інший код, окрім виробника та споживача Сумки?

Залежно від вашої архітектури, ви можете використовувати приватний / захищений / лише конструктор пакетів, щоб переконатися, що об'єкт може бути створений лише тим виробником, якого ви хочете. Або, можливо, потрібно просто зробити це як умова, що його може створити лише правильна "фабрика".

Так, я зробив щось трохи схоже, перейшовши MyBagOfUsefulInformationяк параметр до методу DAO: softwareengineering.stackexchange.com/a/360079/52573
Vidar S. Ramdal

4

LinkedHashMap - єдина карта Java, яка має функцію порядку вставки, яку ви шукаєте. Тож відмова від принципу інверсії залежності залежає і може бути навіть практичною. Перш за все, подумайте, що знадобиться для цього. Ось що б просив вас зробити SOLID .

Примітка: замініть ім'я Ramdalописовим іменем, яке повідомляє, що споживач цього інтерфейсу є власником цього інтерфейсу. Що робить його органом, який вирішує, чи важливий порядок вставки. Якщо ви просто називаєте це, InsertionOrderMapви дійсно пропустили суть.

public interface Ramdal {
    //ISP asks for just the methods that processData() actually uses.
    ...
}

public class RamdalLinkedHashMap extends LinkedHashMap implements Ramdal{} 

Ramdal<Key, Value> ramdal = new RamdalLinkedHashMap<>();

ramdal.put(key1, value1);
ramdal.put(key2, value2);

processData(ramdal);

Це великий дизайн спереду? Можливо, залежить від того, наскільки ймовірно, ви думаєте, що вам також знадобиться впровадження LinkedHashMap. Але якщо ви не стежите за DIP тільки тому, що це буде величезним болем, я не думаю, що плита котла більш болюча, ніж ця. Це шаблон, який я використовую, коли хочу, щоб недоторканий код реалізував інтерфейс, який він не має. Найболючіша частина насправді - це думати про хороші імена.


2
Мені подобається називання!
Відар С. Рамдал

1

Дякую за багато хороших пропозицій та їжу для роздумів.

Я врешті-решт розширив створення нового класу карт, зробивши processDataметод екземпляра:

class DataMap extends LinkedHashMap<Key, Value> {

   processData();

}

Потім я відновив метод DAO, щоб він не повертав карту, а замість цього приймав targetкарту як параметр:

public void fetchData(Map<Key, Value> target) {
  ...
  // for each result row
  target.put(key, value);
}

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

public DataMap fetchDataMap() {
  var dataMap = new DataMap();
  dao.fetchData(dataMap);
  return dataMap;
}

Це дозволяє моїй програмі Map контролювати, як записи вставляються в неї, і приховує вимогу замовлення - тепер це детальна інформація про реалізацію DataMap.


0

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

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


0

Отже, дозвольте мені спробувати зрозуміти ваш контекст тут:

... Порядок вставки має значення ... Сортування карти було б важкою операцією ...

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

Тепер, що ви вже робите:

Я виймаю набір кортежів із бази даних і вкладаю їх у карту ...

А ось ваш поточний код:

public void processData(LinkedHashMap<Key, Value> data) {...}

Я пропоную зробити наступне:

  • Використовуйте ін'єкцію залежності та введіть деякий MyTupleRepository в метод обробки (MyTupleRepository - це інтерфейс, реалізований об'єктами, які отримують ваші кортежні об'єкти, як правило, з БД);
  • внутрішньо до способу обробки, помістіть дані із сховища (він же БД, який вже повертає впорядковані дані) у конкретну колекцію LinkedHashMap, оскільки це внутрішня деталізація алгоритму обробки (адже це залежить від того, як розташовані дані в структурі даних );
  • Зауважте, що це майже все, що ви вже робите, але в цьому випадку це буде зроблено в рамках способу обробки. Ваш сховище інстанціюється десь ще (у вас вже є клас, який повертає дані, це сховище в цьому прикладі)

Приклад коду

public interface MyTupleRepository {
    Collection<MyTuple> GetAll();
}

//Concrete implementation of data access object, that retrieves 
//your tuples from DB; this data is already ordered by the query
public class DbMyTupleRepository implements MyTupleRepository { }

//Injects some abstraction of repository into the processing method,
//but make it clear that some exception might be thrown if data is not
//arranged in some specific way you need
public void processData(MyTupleRepository tupleRepo) throws DataNotOrderedException {

    LinkedHashMap<Key, Value> data = new LinkedHashMap<Key, Value>();

    //Represents the query to DB, that already returns ordered data
    Collection<MyTuple> myTuples = tupleRepo.GetAll();

    //Optional: this would throw some exception if data is not ordered 
    Validate(myTuples);

    for (MyTupleData t : myTuples) {
        data.put(t.key, t.value);
    }

    //Perform the processing using LinkedHashMap...
    ...
}

Я думаю, це дозволить позбутися попередження Sonar, а також вказати у підписі конкретний макет даних, необхідний методом обробки.


Хм, але як би сховище було б створене? Хіба це не просто перенесе проблему кудись інше (куди MyTupleRepositoryстворено?)
Vidar S. Ramdal

Я думаю, я зіткнуся з тією ж проблемою, що і з відповіддю Пітера Купера .
Відар С. Рамдал

Моя пропозиція передбачає застосування принципу введення залежностей; у цьому прикладі; MyTupleRepository - це інтерфейс, який визначає можливість отримання згаданих вами кортежів (який запитує БД). Тут ви вводите цей об’єкт у метод обробки. У вас вже є клас, який повертає дані; це лише абстрагує його в інтерфейсі, і ви вводите об'єкт у метод 'processData', який внутрішньо використовує LinkedHashMap, оскільки це є суттєвою частиною обробки.
Емерсон Кардосо

Я відредагував свою відповідь, намагаючись бути більш зрозумілим щодо того, що я пропоную.
Емерсон Кардосо

-1

Це питання - це чимало проблем із вашою моделлю даних, зведеною в одну. Починати розкручувати їх потрібно по черзі. Більш природні, інтуїтивні рішення випадуть, коли ви намагатиметеся спростити кожен фрагмент головоломки.

Проблема 1: Ви не можете залежати від замовлення БД

Ваші описи сортування даних не зрозумілі.

  • Найбільша потенційна проблема полягає в тому, що ви не вказуєте явного сортування у своїй базі даних за допомогою ORDER BYпункту. Якщо ви цього не зробите, це здається занадто дорогим, у вашій програмі є помилка . Базам даних дозволяється повертати результати в будь-якому порядку, якщо ви не вказали їх; Ви не можете залежати від того, щоб випадково повертати дані в порядку лише тому, що Ви кілька разів виконували запит, і це виглядає саме так. Порядок може змінитися, оскільки рядки переставляються на диску, або деякі видаляються, а нові замінюються, або додається індекс. Ви повинні вказати якесь ORDER BYзастереження. Швидкість бездоганна без коректності.
  • Також незрозуміло, що ви маєте на увазі під значенням порядку вставки. Якщо ви говорите про саму базу даних, у вас повинен бути стовпець, який насправді відстежує це, і він повинен бути включений у ваш ORDER BYпункт. Інакше у вас є помилки. Якщо такий стовпець ще не існує, його потрібно додати. Типовими параметрами для таких стовпців буде стовпець із позначкою часу вставки або ключ з автоматичним збільшенням. Ключ з автоматичним збільшенням надійніше.

Проблема 2: Ефективність сортування пам’яті

Після того, як ви переконаєтесь, що гарантовано повертати дані в порядку, який ви очікуєте, ви можете використовувати цей факт, щоб зробити види пам’яті набагато ефективнішими. Просто додайте до набору результатів запиту row_number()абоdense_rank() стовпчик (або еквівалент вашої бази даних). Тепер кожен рядок має індекс, який дасть вам пряму вказівку про те, яким має бути замовлення, і ви можете сортувати це за допомогою пам'яті тривіально. Просто переконайтеся, що ви дали індексу значущу назву (як sortedBySomethingIndex).

Віола. Тепер вам більше не доведеться залежати від порядку встановлення результатів бази даних.

Проблема 3: Вам навіть потрібно робити цю обробку в коді?

SQL насправді дуже потужний. Це дивовижна декларативна мова, яка дозволяє робити багато перетворень та агрегацій на ваших даних. Більшість БД навіть зараз підтримують міжрядкові операції. Їх називають віконними або аналітичними функціями:

Вам навіть потрібно втягувати свої дані в пам’ять так? Або ви могли виконати всю роботу над запитом SQL, використовуючи функції вікна? Якщо ви можете виконати всю (а може навіть навіть значну частину) роботи в БД, фантастично! Ваша проблема з кодом відходить (або стає набагато простіше)!

Проблема 4: Ти що робиш до цього data?

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

Вибач, але що за чорт?

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

Ось, можливо, краща ідея:

  • Є ваша функція приймає List.
  • Є кілька способів вирішити проблему замовлення.
    1. Застосувати Fail Fast. Введіть помилку, якщо список не в тому порядку, якого вимагає функція. (Примітка. Ви можете скористатися індексом сортування з проблеми 2, щоб визначити, чи є він.)
    2. Створіть відсортовану копію самостійно (знову використовуючи індекс із проблеми 2).
    3. Придумайте спосіб побудови самої карти в порядку.
  • Побудуйте потрібну вам карту внутрішньо для функції, тому абонентові не потрібно про це піклуватися.
  • Тепер перегляньте все, що ви маєте для представлення порядку, і робіть те, що вам потрібно.
  • Поверніть карту або перетворіть її у відповідне значення повернення

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

А може, це має більше сенсу: позбудьтесь dataпараметра і processDataфактично отримайте власні дані. Потім ви можете задокументувати, що ви це робите, оскільки він має дуже конкретні вимоги щодо способу отримання даних. Іншими словами, зробіть функцію власником всього процесу, а не лише одного його фрагмента; взаємозалежності занадто сильні, щоб розділити логіку на менші шматки. (Змініть назву функції в процесі.)

Можливо, це не допоможе вашій ситуації. Я не знаю без повних деталей проблеми. Але я знаю тендітний і заплутаний дизайн, коли чую його.

Підсумок

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

Знайдіть когось, хто до цього звернеться: ваше завдання - звести свою проблему до набору простих, простих. Тоді ви можете створити надійний, інтуїтивно зрозумілий код. Поговоріть з ними. Хороший код та гарний дизайн змушують вас думати, що будь-який ідіот міг їх придумати, адже вони прості та прості. Можливо, є старший розробник, який має такий розум, з яким можна поговорити.


"Що ви маєте на увазі, що немає природного порядку, але має значення порядок вставки? Ви хочете сказати, що має значення, для якого порядку були введені дані в таблицю БД, але у вас немає стовпця, який би міг сказати вам, які замовлення були вставлені?" - у запитанні зазначено так: "Сортування карти було б важкою операцією, тому я хочу уникати цього, враховуючи, що результат запиту вже відсортований". Це ясно вказує на те , що є що розраховується певний порядок в даних, так як в противному випадку його сортування було б неможливо , а не важким, але певний порядок відрізняється від природного порядку ключів.
Жуль

2
Іншими словами, ОП працює над результатами запиту як select key, value from table where ... order by othercolumnі потребує підтримання порядку в їх обробці. Порядок вставки, на який вони посилаються, - це порядок вставки в їхню карту , визначений порядком, який використовується в їх запиті, а не порядком вставки в базу даних . Це стає зрозумілим завдяки їх використанню LinkedHashMap, що є структурою даних, яка має характеристики як пар, так Mapі Listключових значень.
Жуль

@Jules Я трохи приберу цей розділ, дякую. (Я насправді пам’ятав, що читав це, але коли я перевіряв речі, коли писав питання, я не міг його знайти. Отож, потрапив також у бур’яни.) Але питання не зрозуміло, що вони роблять із БД запит і чи є вони явного сортування чи ні. Вони також кажуть, що "порядок вставки має значення". Справа в тому, що навіть якщо сортування важке, ви не можете покластися на БД, щоб просто магічно впорядкувати речі правильно, якщо ви не скажете це чітко. І якщо ви це робите в БД, то ви можете використовувати "індекс", щоб зробити його ефективним у коді.
jpmc26

* написання відповіді (Метінкс, я повинен незабаром лягати спати.)
jpmc26

Так, @Jules має рацію. Там єorder by пункт в запиті, але це не є тривіальним ( НЕ тільки order by column), тому я хочу , щоб уникнути повторної реалізації сортування в Java. Хоча SQL є потужним (і ми тут говоримо про базу даних Oracle 11g), характер processDataалгоритму значно полегшує вираження в Java. І так, "порядок вставки" означає " порядок вставки карти ", тобто порядок результатів запиту.
Відар С. Рамдал
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.