Лучший способ классифицировать список

У меня есть объект Order

public class Order{
  private Date orderDate;
  //other fields + getters & setters
}

И список заказов (Список<Заказы>). Теперь я хочу сгруппировать этот список заказов на основе аналогичной даты заказа (аналогичный здесь означает тот же год, месяц и день) и создать такую ​​карту:

Map<Date, List<Order>> orderMap = new HashMap<>();

Как это сделать в Java 8+?

Take note we might have similar date but different timestamp which means we want to based it on "year, month, day"

Дайте определение «похожему»

QBrute 25.05.2019 14:29

Даты @QBrute с одинаковым годом, месяцем, днем

Mehdi 25.05.2019 14:30

@michalk не работает, если у нас разные часы, минуты, секунды в течение дня

Mehdi 25.05.2019 14:53

Да, верно. Кстати, вы вынуждены использовать устаревшие классы для java.util? Существует пакет java.time, в котором есть такие классы, как LocalDate и LocalDateTime, которые следует использовать.

Michał Krzywański 25.05.2019 14:58

@michalk Пожалуйста, опубликуйте свое решение, каким бы оно ни было

Mehdi 25.05.2019 15:02

Я рекомендую вам не использовать Date. Этот класс плохо спроектирован и давно устарел. Вместо этого используйте LocalDate из java.time, современный API даты и времени Java.

Ole V.V. 25.05.2019 19:27
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
6
214
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

Вы можете создать метод, который возвращает «подобие» заказов, например:

public class Order{
  private Date orderDate;
  //other fields + getters & setters
  public String getStringDate(){
     SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd");
     return dateFormat.format(this.orderDate);
  }
}

а затем группировка по нему:

Map<String, List<Order>> ordersWithSimilarOrderDate = orders.stream().collect(Collectors.groupingBy(Order::getStringDate));

Эти ужасные классы даты и времени были вытеснены много лет назад современными классами Java.время, определенными в JSR 310. Предлагать их использовать в 2019 году — плохой совет.

Basil Bourque 25.05.2019 16:16

Поскольку класс Date устарел и содержит фактическую дату и время, вы можете использовать LocalDateTime в качестве своего поля в классе Order. Затем вы можете сгруппировать свои заказы по LocalDate, взятым из LocalDateTime:

List<Order> list = new LinkedList<>();

Map<LocalDate, List<Order>> collect = list.stream()
                .collect(Collectors.groupingBy(order -> order.getOrderDate().toLocalDate()));

Местная Дата содержит год, месяц и день без учета времени.

спасибо за ваш ответ, но у этого есть две проблемы: 1) я не хочу использовать LocalDate для своего ключа 2) toLocalDate для java.sql.Date не java.util.Date

Mehdi 25.05.2019 15:29
toLocalDate — это метод класса LocalDateTime здесь.
Michał Krzywański 25.05.2019 15:35

@Mehdi Если вам нужна более тонкая детализация, чем LocalDate для ваших ключей, рассмотрите Instant. В Java 8+ никогда не используйте Date.

Ole V.V. 26.05.2019 07:36

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

 List<Order> orders = ... your list ...
 Map<Date, List<Order>> orderMap = orders.stream().collect(Collectors.groupingBy(order->{
      //Converting from Date to Instant
      Instant dateInstant = order.getOrderDate().toInstant();
      //Converting Instant to LocalDateTime and setting it to the start of the day
      LocalDateTime dateTime = dateInstant.atZone(ZoneId.systemDefault()).toLocalDate().atStartOfDay();
      //Converting it back to an instant
      Instant startOfDayInstant = dateTime.atZone(ZoneId.systemDefault()).toInstant();
      //Returning the util date
      return Date.from(startOfDayInstant);
    }));

Это при условии, что вы используете дату нужно. В противном случае я поддерживаю ответ от michalk

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

Mehdi 25.05.2019 15:42

@Mehdi Вы можете переключить свое согласие на другой ответ.

Basil Bourque 25.05.2019 16:19

@BasilBourque Я знаю, но это неэтично. Всегда есть следующий раз, чувак

Mehdi 25.05.2019 16:21

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

Basil Bourque 25.05.2019 16:24

Если вам нужно использовать Date, вы все равно можете преобразовать его в LocalDate, предоставив информацию о часовом поясе.

Map<LocalDate, List<Order>> collect =
    list.stream().collect(Collectors.groupingBy(order -> 
        order.getOrderDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDate()));
Ответ принят как подходящий

тл;др

Как ключ к вашему Map< LocalDate , List< Order> >:

    order
    .getDate()                                  // Accessor method to retrieve your `java.util.Date` object.
    .toInstant()                                // Convert from terrible legacy class `Date` to modern `java.time.Instant` class. Both represent a moment in UTC.
    .atZone(                                    // Adjust from UTC to the time zone used by your business.
            ZoneId.of( "America/Los_Angeles" ) 
    )                                           // Remember: For any given moment, the date varies around globe by time zone, “tomorrow” in Tokyo while “yesterday” in Montréal. 
    .toLocalDate()                              // Extract the date-only portion, without time-of-day, without time zone, to use as the key in our map.

Держите своего члена Date

Если вы не можете изменить тип вашей orderDate переменной-члена из ужасного java.util.Date класса, тогда выполните бизнес-логику в Java.время. Преобразуйте в/из Date, используя новые методы, добавленные в старые классы.

Используйте нетLocalDateTime

Использование Java.время обсуждалось в других ответах, но в них используется неправильный класс: LocalDateTime. Это совершенно неправильный класс для использования, так как нет представляет момент. Он представляет моменты потенциал в диапазоне примерно 26-27 часов, диапазоне часовых поясов по всему миру. Класс содержит дату и время дня, но не имеет понятия часового пояса или смещения от UTC. Так что, если объект LocalDateTime держит 12 часов дня 23 января этого года, мы понятия не имеем, должен ли был быть полдень в Токио, полдень в Калькутте, полдень в Париже или полдень в Монреале — все очень разные моменты, несколько часов. отдельно.

Чтобы представить момент, используйте один из них:

  • Instant
    Момент в UTC
  • OffsetDateTime
    Момент со смещением от UTC, некоторое количество часов-минут-секунд
  • ZonedDateTime
    Момент, видимый через смещение, используемое людьми определенного региона, этот часовой пояс имеет название в формате Continent/Region

Table of date-time types in Java, both modern and legacy

DateInstant

Замена java.util.Date на java.time.Instant. Оба представляют момент в формате UTC, хотя Instant имеет более точное разрешение наносекунды по сравнению с миллисекундами.

Instant instant = orderDate.toInstant() ;

Часовой пояс

Настройте от UTC до желаемого часового пояса. Вы хотите, чтобы дата воспринималась так, как она видна в Барстоу, Калифорния? Используйте часовой пояс America/Los_Angeles. Примените ZoneId, чтобы получить ZonedDateTime. Этот ZonedDateTime представляет тот же момент, ту же точку на временной шкале, что и Instant, но использует другое время настенных часов.

Укажите правильное название часового пояса в формате Continent/Region, например America/Montreal, Africa/Casablanca или Pacific/Auckland. Никогда не используйте сокращения из 2-4 букв, такие как EST или IST, поскольку они представляют собой истинные часовые пояса нет, не стандартизированы и даже не уникальны (!).

Обратите внимание, что Java.время классифицирует потокобезопасный по дизайну, используя неизменяемые объекты. Таким образом, вы можете определить это ZoneId один раз и сохранить его для повторного использования, даже в разных потоках.

ZoneId z = ZoneId.of( "America/Los_Angeles" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;  // Same moment, different wall-clock-time.

LocalDate

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

LocalDate ld = zdt.toLocalDate() ;  // The date as seen for this moment in Barstow, California.

Используйте LocalDate как ключ к вашему Map< LocalDate , List< Order> >.

Измените своего участника Date на Instant

How to do this in Java 8+ ?

Лучше всего перенести свой код с ужасных устаревших классов даты и времени на современные классы Java.время. Sun, Oracle и сообщество JCP отказались от старых классов, когда они приняли JSR 310. Так и вы — они действительно настолько плохи.

Как правило, на бэкэнде лучше всего работать, думать, хранить и обмениваться значениями даты и времени в формате UTC. Применяйте часовой пояс только для (а) представления пользователю, (б) если этого требует бизнес-логика.

public class Order{
  private Instant whenOrdered ;
  //other fields + getters & setters
}

Примените ту же логику настройки часового пояса, что и выше, чтобы получить LocalDate в качестве ключа к вашему Map< LocalDate , List< Order > >.

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

Locale locale = new Locale( "fr" , "TN" ) ;      // French in Tunisia.
ZoneId userZone = ZoneId.of( "Africa/Tunis" ) ;  // Or ZoneId.systemDefault() to get the JVM’s current default time zone (appropriate to a client machine, but not a server machine). 
String outputForDisplay = 
    order
    .whenOrdered
    .atZone( userZone )                          // Adjust from UTC to time zone, going from `Instant` to `ZonedDateTime`. 
    .format(                                     // Generate text representing the value inside our `ZonedDateTime` object.
        DateTimeFormatter.ofLocalizedDateTime( FormatStyle.LONG )  // Let java.time automatically localize, rather than hard-code a formatting pattern.
    ) 
;

Если вас интересует только дата, а не время суток, и вас не волнует нечеткость определения даты в разных часовых поясах (у вас небольшой местный бизнес), используйте LocalDate.

public class Order{
  private LocalDate dateOrdered ;
  //other fields + getters & setters
}

О Java.время

Платформа Java.время встроена в Java 8 и более поздние версии. Эти классы заменяют проблемные старые классы даты и времени наследие, такие как java.util.Date, Calendar и SimpleDateFormat.

Чтобы узнать больше, см. Учебник по оракулу. И поищите множество примеров и пояснений в Stack Overflow. Спецификация JSR 310.

Проект Джода-Время, теперь именуемый Режим технического обслуживания, рекомендует перейти на классы Java.время.

Вы можете обмениваться объектами Java.время напрямую с вашей базой данных. Используйте JDBC-драйвер, совместимый с JDBC 4.2 или более поздней версии. Нет необходимости в строках, нет необходимости в классах java.sql.*.

Где получить классы java.time?

  • Ява SE 8, Ява SE 9, Ява SE 10, Ява SE 11 и более поздние версии — часть стандартного Java API со встроенной реализацией.
    • Java 9 добавляет некоторые незначительные функции и исправления.
  • Ява SE 6 и Ява SE 7
    • Большая часть функциональности Java.время обратно перенесена на Java 6 и 7 в ThreeTen-Backport.
  • Андроид
    • Более поздние версии реализации пакетов Android классов Java.время.
    • Для более ранних версий Android (<26) проект тридесятьABP адаптирует ThreeTen-Backport (упомянутый выше). См. Как использовать ThreeTenABP….

Проект ThreeTen-Extra расширяет java.time дополнительными классами. Этот проект является испытательным полигоном для возможных дополнений к java.time в будущем. Здесь вы можете найти несколько полезных классов, таких как Interval, YearWeek, YearQuarter и более.

правильный и правильный ответ с невероятным профессионализмом. Я кое-что узнал в этом ответе. Но иногда быстрое и простое исправление «достаточно хорошо», поскольку этот код можно использовать в большом программном обеспечении, которое в конечном итоге основано на util.date, и изменение всего потребует слишком много времени и денег. Если у кого-то есть такая же проблема в небольшом проекте, идите по этому пути, а не простому и быстрому решению.

Scorix 25.05.2019 22:13

Другие вопросы по теме