Страницы

Поиск по вопросам

Показаны сообщения с ярлыком java-8. Показать все сообщения
Показаны сообщения с ярлыком java-8. Показать все сообщения

среда, 4 марта 2020 г.

Как выполнить какое-то действие, если в коллекции найдется элемент с соответствующим полем?

#java #коллекции #java_stream #java_8


Как выполнить какое-то действие, если в коллекции найдется элемент с соответствующим
полем?

List items = new ArrayList<>();
...
add values
...
String customer;

for (item:items){
    if (Objects.equals(item.getId(), "123123123")){
        customer="admin";
        break;
    }
}


Как это будет выглядеть с использование java 8, stream api?
    


Ответы

Ответ 1



Воспользуйтесь методом Stream::anyMatch: boolean contains = items .stream() .anyMatch(item -> Objects.equals(item.getId(), "123123123")); String customer = contains ? "admin" : null; Ideone

Ответ 2



Вот еще вариант с Optional для разнообразия: String customer = items.parallelStream().map(Item::getId) .filter("123123123"::equals).map(s -> "admin") .findAny().orElse(null);

Ответ 3



Если принять во внимание то, что Id однозначно определяет соответствующий ему Item и, кроме того, в данном случае имплементит Comparable, то для оптимизации можно воспользоваться структурой данных TreeSet и хранить идентификаторы в ней. List items = new ArrayList<>(); TreeSet setOfIds = items.stream().map(item -> item.getId()). collect(Collectors.toCollection(TreeSet::new)); String customer = setOfIds.contains("123123123") ? "admin" : null; Такой подходит позволяет ускорить процесс поиска в коллекции до O(logN).

пятница, 28 февраля 2020 г.

Удаление дубликатов по полю объекта Stream API

#java #коллекции #lambda #java_stream #java_8


У нас есть список объектов доменной области (Person). У объекта Person есть 3 поля:
id, имя, фамилия. Задача: найти дубликаты и создать из них список (или множество, не
важно), остальные объекты отбросить. Дубликатами являются объекты, у которых совпадает
поле имя. Реализовать механизм необходимо через стримы. Решение: 

private static  Predicate distinctByKey(Function keyExtractor) {
            Set seen = new HashSet<>();
            return t -> seen.add(keyExtractor.apply(t));
}

persons.stream().filter(distinctByKey(Person::getName))


Реализация работает, но есть вопросы по работе такого подхода:


Судя по всему HashSet создается всего лишь единожды. Но почему? Изначально все же
ожидается будто множество будет создаваться при каждой итерации. Хотелось бы увидеть
развернутый ответ на этот счет.
Вызывает вопрос строка return. Откуда у нас берется реализация метода apply()? Явно
ведь я нигде не реализую Function и метод apply() соответственно.

    


Ответы

Ответ 1



Java для каждой лямбды и ссылки на метод в момент выполнения создаёт прокси-класс, реализующий функциональный интерфейс. Метод distinctByKey объявлен как принимающий функциональный интерфейс Function, поэтому при его вызове виртуальная машина создаст прокси-класс class Example$$Lambda$1 implements Function { public String apply(Person person) { return person.getName(); } } и передаст его вместо ссылки на метод Person::getName. Если убрать весь сахар, может стать понятнее, почему множество seen создаётся один раз. Ваш код в рантайме преобразовывается в приблизительно эквивалентный этому: class Example$$Lambda$2 implements Predicate { private final java.util.Set arg$1; public boolean test(String name) { return arg$1.add(name); } } private static Example$$Lambda$2 distinctByKey(Example$$Lambda$1 keyExtractor) { Set seen = new HashSet<>(); // Здесь seen "магическим" образом присваивается // полю arg$1 возвращаемого объекта return new Example$$Lambda$2(); } Example$$Lambda$1 keyExtractor = new Example$$Lambda$1(); Example$$Lambda$2 predicate = distinctByKey(keyExtractor); Iterator stream = persons.iterator(); List result = new ArrayList<>(); while (stream.hasNext()) { Person person = stream.next(); String key = keyExtractor.apply(person); boolean duplicate = predicate.test(key); if (!duplicate) { result.add(key); } } И кстати, операция filter оставляет в потоке элементы соответствующие предикату, а метод множества add возвращает true для тех элементов, которых в множестве не было. То есть вы наоборот убираете дубликаты из потока. Вам надо инвертировать предикат: persons.stream() .filter(distinctByKey(Person::getName).negate());

понедельник, 24 февраля 2020 г.

Стоит ли заменять анонимную реализацию с одним методом лямбдой?

#java #java_8


Intllij Idea предлагает заменить анонимную реализацию с одним методом лямбдой. Но
по смыслу это не функциональный интерфейс, а просто переопределение одного метода одного
класса. Стоит ли заменять?

return new NextImportIdParams() {
    @Override
    public String getCollectionName() {
            return new CollectionName();            
    }
};

    


Ответы

Ответ 1



Все зависит от того под какую версию java пишете + от принятого code style. Если вам никто не связывает руки, то определенно лямбы делают код более компактным и читабельным.

Ответ 2



В Java 8 можно любой анонимный класс, который реализует один абстрактный метод заменить на лямбду выражение, если анонимный класс реализует два абстрактных метода, то заменить на лямбду будет невозможно. Если вы проект перевели на Java 8, то такие измения безопасные. Idea дает возможность поменять все анонимные классы на лямбды.

пятница, 14 февраля 2020 г.

Как получить каждый n-ый элемент из списка с помощью Stream API?

#java #java_8 #java_stream #функциональное_программирование


Предположим, у меня есть такой список:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]


Можно ли использовать Java Stream API для переноса каждого второго элемента из этого
списка, чтобы получить такой список?

[1, 3, 5, 7, 9]


Или, может быть, каждый третий элемент?

[1, 4, 7, 10]


По сути, я ищу такую функцию, чтобы взять каждый n-ый элемент из потока:

List list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List list2 = list.stream().takenth(3).collect(Collectors.toList());
System.out.println(list2);
// => [1, 4, 7, 10]

    


Ответы

Ответ 1



Одним из главных мотивов для введения потоков Java было разрешение параллельных операций. Это привело к требованию, чтобы операции Java Streams, такие как map и filter, не зависели от положения элемента в потоке или элементов вокруг него. У этого есть преимущество, оно позволяет легко разделять потоки для параллельной обработки. Недостатком является сложность некоторых операций. Таким образом, нет простого способа сделать что-то такое, например, взять каждый n-ый предмет или сопоставить каждый предмет сумме всех предыдущих предметов. Самый простой способ выполнить ваше требование - использовать индекс списка: List list = ...; return IntStream.range(0, list.size()) .filter(n -> n % 3 == 0) //3 - каждый 3-ий элемент .mapToObj(list::get) .collect(Collectors.toList()); Примечание от Sergey Gornostaev: Решение рабочее, но должен заметить, подобное применение стримов - это антипаттерн. Не желательно работать с данными за пределами стрима. Как только появляется идея об обращении к переменной, стоит насторожиться и подумать "Я использую стримы неправильно или стримы тут вообще не подходят?" Используя Stream.iterate List list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); int skip = 3; int size = list.size(); // Limit to carefully avoid IndexOutOfBoundsException int limit = size / skip + Math.min(size % skip, 1); List result = Stream.iterate(0, i -> i + skip) .limit(limit) .map(list::get) .collect(Collectors.toList()); System.out.println(result); // [1, 4, 7, 10] Используя библиотеку Guava(GitHub): Streams .mapWithIndex(stream, SimpleImmutableEntry::new) .filter(entry -> entry.getValue() % 3 == 0) .map(Entry::getKey) .collect(Collectors.toList()); Используя библиотеку AbacusUtil: Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) .filter(MutableInt.of(0), (e, idx) -> idx.getAndDecrement() % 3 == 0) .println(); Используя библиотеку jOOλ и ее метод zipWithIndex(): System.out.println( Seq.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) .zipWithIndex() // This produces a Tuple2(yourvalue, index) .filter(t -> t.v2 % 3 == 0) // Filter by the index .map(t -> t.v1) // Remove the index again .toList() );

среда, 5 февраля 2020 г.

Сравнить две даты без учета времени

#java #timestamp #java_8


У меня есть дата в timestamp формате. Каким образом можно проверить равна ли она
текущей дате без учета времени? Например 1502010000 она равна  6.07.2017.

Я получил данную дату и текущую, но как сравнить не понятно:

Instant date= Instant.ofEpochSecond(1502010000);
Instant now = Instant.now();

    


Ответы

Ответ 1



Java 8 представила новый механизм для работы с датами реализованный API из пакета java.time.*. В данном случае для сравнения дат можно использовать следующий макет поведения: Timestamp timestamp = new Timestamp(1502010000); LocalDateTime before = timestamp.toLocalDateTime(); LocalDateTime now = LocalDateTime.now(); int compareREsult = now.compareTo(before); Если compareResult отрицательное число - если сравниваемая дата позже, положительное - если раньше и ноль если равны. UPD: Поскольку в вопросе есть неучтенное условие, что дата должна сравниваться без времени, то предлагается использоваться следующий вариант: Timestamp timestamp = new Timestamp(1502041448453l); // System.out.println(timestamp); выведет "2017-08-06 20:44:08.453" LocalDate localDateTime = timestamp.toLocalDateTime().toLocalDate(); LocalDate now = LocalDate.now(); // System.out.println(now); выведет "2017-08-06" // Выведет 0; System.out.println(now.compareTo(localDateTime));

Ответ 2



Я может что-то не понял, но: https://habrahabr.ru/post/61391/ TIMESTAMP Хранит 4-байтное целое число, равное количеству секунд, прошедших с полуночи 1 января 1970 года И данное вами число выдает 18 января 1970, а никак не 6 августа 2017 http://www.fileformat.info/tip/java/date2millis.htm Но если у вас все отлично показывает, то можете использовать Calendar и просто обнулить время private static Date dateRemoveTime(Date date){ Calendar calendar = new GregorianCalendar(); calendar.setTime(date); calendar.set(Calendar.HOUR, 0); calendar.set(Calendar.MINUTE, 0); calendar.set(Calendar.SECOND, 0); calendar.set(Calendar.MILLISECOND, 0); return calendar.getTime(); }

суббота, 11 января 2020 г.

Можно ли упростить цикл, используя java 9 или java 8?

#java #инспекция_кода #lambda #java_8


Можно ли как-то упростить этот цикл, используя java 9 или java 8?

for (String s : list) {
    if (s.indexOf("D") != -1) {
        numberList.addAll(getDoubleFromString(s));
    } else {
        if (s.indexOf(".") != -1) {
            numberList.add(new TypeSatellite(BigDecimal.valueOf(Double.valueOf(s))));
        } else {
            numberList.add(new TypeSatellite(Integer.valueOf(s)));
        }
    }
}

    


Ответы

Ответ 1



Давай-те сделаем оптимизации по шагам (основная сложность непонятно что принимают TypeSatellite и какой тип у numberList, поэтому напишу для object'a): 1) Можно заменить s.indexOf(...) != -1 на s.contains(...), то есть записать что-то вроде list.forEach(s -> { if (s.contains("D")) { numberList.addAll(getDoubleFromString(s)); } else { if (s.contains(".")) { numberList.add(new TypeSatellite(BigDecimal.valueOf(Double.valueOf(s)))); } else { numberList.add(new TypeSatellite(Integer.valueOf(s))); } } }); 2) Можно вынести общий код list.forEach(s -> { if (s.contains("D")) { numberList.addAll(getDoubleFromString(s)); } else { Object number = s.contains(".") ? BigDecimal.valueOf(Double.valueOf(s)) : Integer.valueOf(s); numberList.add(new TypeSatellite(number)); } }); 3) Вынесем общий функцию: list.forEach(s -> { numberList.addAll(getListNumber(s)); }); } private static Collection getListNumber(String s) { if (s.contains("D")) { return getDoubleFromString(s)); } else { Object number = s.contains(".") ? BigDecimal.valueOf(Double.valueOf(s)) : Integer.valueOf(s); return Collections.singleton(new TypeSatellite(number)); } } 4) последний шаг используем стрим numberList = list.stream().flatMap(::getListNumber ).collect(Collectors.toList()); private static Stream getListNumber(String s) { if (s.contains("D")) { return getDoubleFromString(s).stream(); } else { Object number = s.contains(".") ? BigDecimal.valueOf(Double.valueOf(s)) : Integer.valueOf(s); return Stream.of(new TypeSatellite(number)); } }

Ответ 2



Данный код действительно можно отредактировать с использованием Java 8. Для начала будем руководствоваться тем, что list объявлен следующим образом: List list Воспользуемся функциональным интерфейсом из java.util.function.Predicate Predicate containsDouble = s->s.contains("D"); Predicate containsDecimal = s->s.contains("."); Predicate containsInteger = containsDouble.or(containsDecimal).negate(); Первый устанавливает, содержит ли строка "D", второй - точку, третий будет истинным, если первые два одновременно ложны. Далее мы можем заполнить numberList с, используя Stream API. list.stream().filter(containsDouble).forEach(s -> numberList.addAll(getDoubleFromString(s))); list.stream().filter(containsDecimal).forEach(s -> numberList.add(new TypeSatellite(BigDecimal.valueOf(Double.valueOf(s))))); list.stream().filter(containsInteger).forEach(s -> numberList.add(new TypeSatellite(Integer.valueOf(s)))); Замечу, что при этом объект numberList должен быть final или effectively-final. Трудоёмкость алгоритма при этом останется прежней O(N).

среда, 1 января 2020 г.

В книге Шилдта опечатка или нет?

#java #интерфейс #java_8 #лямбда_выражение


Читаю книгу Шилдта Java 8, главу "Ссылки на статические методы".

Вопрос про класс MyStringOps - видно, что объявлен класс, а не интерфейс, но в книге
почему-то написан интерфейс. Это опечатка? Или чего-то недопонимаю?

// Продемонстрировать ссылку на статический метод
// Функциональный интерфейс для операций с символьными строками

interface StringFunc {
  String func (String n);
}
// в этом интерфейсе определяется статический метод strReverse ()
class MyStringOps {
// Статический метод, изменяющий порядок
// следования символов в строке
  static String strReverse (String str) {
    String result = " " ;
    int i;
    for (i = str.length()- 1; i >= О; i--)
      result += str.charAt (i) ;
    return result;
   }
}
class MethodRefDemo {
// В этом методе функциональный интерфейс указывается в качестве
// типа первого его параметра . Следовательно, ему может быть передан
// любой экземпляр этого интерфейса , включая и ссылку на ме тод
  static StringstringOp (StringFunc sf, String s) { 
  return sf.func(s) ;
  public static void main (String args[])
  {
    String inS tr = "Лямбда- выражения повышают эффективность Java ";
    String out Str;
    // Здесь ссылка на метод strRaverse () передается методу stringOp()
    outStr = stringOp ( MyStringOps::strReverse, inStr);
    System.out.println (" Иcxoднaя строка : "+ inStr);
    System.out.println ("Oбpaщeннaя строка : "+ outStr); 
  }
}

    


Ответы

Ответ 1



Да, действительно, опечатка. В оригинале Java The Complete Reference (Ninth Edition):

LocalDate или Date

#java #дата #java_8


Сколько бы примеров и полей с датами на Java я не видел, всегда (или почти всегда)
используют класс Date. При этом, я думаю, всем известно, что класс Date сам по себе
плохой: он не потокобезопасен и представляет собой не то, что указано в его названии
(мы получаем дата-время вместо даты). В это же время классы LocalDate, LocalDateTime
и LocalTime полностью покрывают эти недостатки и вообще более удобны в использовании.
Возникает вопрос, почему тогда все еще люди (и не абы какие, а даже в некоторых оф.
документациях так) используют класс Date? 
    


Ответы

Ответ 1



Class Date ... Since: JDK1.0 vs Class LocalDate ... Since: 1.8 Ну это основная причина, т.е. куча кода написана используя java.util.Date и не будет никогда переписана. До появления в 8 версии java.time также во всю использовалась библиотека Joda-Time. Дело привычки и истории. На данный момент, естественно если есть выбор и нет необходимости поддерживать legacy-код, лучше использовать LocalDate, LocalTime и т.д.

вторник, 31 декабря 2019 г.

В чем разница между методами map и flatMap в Java 8?

#java #потоки_данных #java_8 #java_stream #java_faq


Чем отличаются методы Stream.map и Stream.flatMap друг от друга?
    


Ответы

Ответ 1



И map и flatMap могут быть применены к стриму Stream и оба возвращают стрим Stream. Разница заключается в том, что операция map создает одно выходное значение для каждого входного значения, тогда как операция flatMap создает произвольное число(ноль или больше) значений для каждого входного значения. Операция map(О работе map на русском) в качестве аргумента принимает Function(например, лямбду), которая вызывается для каждого значения входного стрима(который ), преобразует это значение в другое значение, и посылает получившееся значение в выходной стрим(который ). Т.е. map для каждого объекта в стриме возвращает по 1 объекту, потом преобразует все объекты в итоговый стрим. Операция flatMap(О работе flatMap на русском) принимает функцию (которая преобразует каждое значение входного стрима в стрим), применяет ее к каждому элементу, и на выходе возвращает стрим с одним, несколькими или ни c одним из элементов для каждого элемента входящего стрима. Т.е., flatMap возвращает по стриму для каждого объекта в первоначальном стриме, а затем результирующие потоки объединяются в исходный стрим. Пример для map: есть одномерный массив array с числами. Нужно получить массив из первоначального массива, в котором к каждому числу прибавлена 1. Решение: array = Arrays.stream(array) //преобразовываем массив в стрим .map(i -> i+1) //преобразовываем каждый элемент стрима .toArray(); //преобразовываем стрим в массив map к каждому значению стрима прибавляет 1, потом все новые значения преобразует в итоговый стрим. Пример для flatMap: есть двухмерный array с числами, надо получить одномерный массив с числами. Решение: secondArray = Arrays.stream(array) .flatMapToInt(i -> Arrays.stream(i)) //преобразовываем Stream в Stream .toArray(); // преобразовываем Stream в int[] В этом примере создается стрим состоящий из каждого элемента первоначального массива - т.е. стрим из массивов. Потом с помощью i -> Arrays.stream(i) преобразовываем каждый элемент(который является массивом) стрима в стрим с числами. После этого flatMap собирает все получившееся стримы в один итоговый стрим.

Лямбды в Java 8, что лучше использовать - Function.identity() или t->t?

#java #java_8 #lambda #java_faq


Вопрос по использованию Function.identity() метода. 

Допустим, есть следующий код:

Arrays.asList("a", "b", "c")
          .stream()
          .map(Function.identity()) // <- Этот кусок
          .map(str -> str)          // <- равен этому.
          .collect(Collectors.toMap(
                       Function.identity(), // <-- А этот
                       str -> str));        // <-- равен этому.


Должен ли я использовать Function.identity() вместо str->str(или наоборот)? Я думаю,
что второй вариант более читабелен и понимаем(допустим, если новый человек не знает
что делает identity). Но есть ли «реальная» причина, из-за которой следует отдать предпочтение
одному из способов?
    


Ответы

Ответ 1



Первоначально кажется, что у этих двух способов нет отличия, ведь реализация identity() такова: static Function identity() { return t -> t; } Однако Function.identity() всегда будет возвращать один и тот же экземпляр(обоснование этого решения), в то время как t -> t не только будет создавать новый экземпляр, но даже будет иметь отдельный класс реализации. Для более подробной информации смотрите здесь. Причина в том, что компилятор генерирует синтетический метод, содержащий тело лямбда выражений(случае t -> t это будет return t;) и указывает среде выполнения создать реализацию функционального интерфейса, вызывающего этот метод. Поэтому использование Function.identity() вместо t -> tможет сэкономить немного памяти, но совсем небольшое количество, поэтому это не должно влиять на ваше решение, если вы считаете, что t -> t более читабельно, чем Function.identity(). Также в некоторых методах нельзя вызвать Function.identity(). Допустим, есть такой список: List list ... ... Код ниже отлично скомпилируется: int[] arrayOK = list.stream().mapToInt(i -> i).toArray(); Но если попытаться скомпилировать следующий код, то будет ошибка компиляции: int[] arrayProblem = list.stream().mapToInt(Function.identity()).toArray(); Она произойдет из-за того что mapToInt ожидает ToIntFunction, которая не связана с Function. Также у ToIntFunction нет метода identity().

Разница между методом по умолчанию в интерфейсе и обычным методом в классе

#java #классы #методы #интерфейс #java_8


Чем отличаются между собой метод по умолчанию, объявленный в интерфейсе с модификатором
default и обычный метод, объявленный в обычном классе?

(update) Читаю


  Метод по умолчанию представляет собой метод, который объявлен в интерфейсе с модификатором
default; его тело всегда представлено блоком. Он предоставляет реализацию по умолчанию
для любого класса, который реализует интерфейс без перекрытия метода. Методы по умолчанию
отличны от конкретных методов, которые объявляются в классах.

    


Ответы

Ответ 1



default метод не может обращаться к состоянию объекта (полям объекта), так как никакого объекта нет, но может вызвать другие методы и обращаться к статическим данным (константам). default метод позволяет избежать необходимости изменить все классы, которые реализуют этот интерфейс. В классе, реализующим интерфейс с default методами, вы можете их переопределить. interface I1 { // это public static final int i = 0; // но в описании интерфейса public static final можно опустить int i = 0; default void m1() { System.out.println("I1 m1 i = " + i); m2(); } void m2(); } Без default этот класс бы не скомпилировался: public class C1 implements I1 { @Override public void m2() { System.out.println("C1 m2"); } public static void main(String[] args) { new C1().m1(); new C1().m2(); } } Вывод будет следующим: I1 m1 i = 0 C1 m2 C1 m2

Ответ 2



Одно из различий заключается в том, что все методы (в т.ч. и default) в интерфейсе неявно объявлены как public и поменять этот модификатор доступа не получится, а в обычном методе в классе Вы можете сами устанавливать различные модификаторы доступа. UPD: Метод по умолчанию представляет собой метод, который объявлен в интерфейсе с модификатором default; его тело всегда представлено блоком. Он предоставляет реализацию по умолчанию для любого класса, который реализует интерфейс без перекрытия метода. Методы по умолчанию отличны от конкретных методов, которые объявляются в классах. Вы несколько неправильно поняли это предложение. Здесь имелось ввиду то, что если класс реализует интерфейс, который содержит default-метод, то при переопределении в классе этого метода, метод из класса перекроет default-реализацию. Попробуйте сделать акцент на выделенном тексте, тогда поймете о чем я: Он предоставляет реализацию по умолчанию для любого класса, который реализует интерфейс без перекрытия метода. Методы по умолчанию отличны от конкретных методов, которые объявляются в классах. К тому же, если бы здесь имелись ввиду какие-то принципиальные различия, то они здесь же были бы указаны, так как в этом пункте из главы про интерфейсы рассказывается непосредственно про default-методы.

суббота, 28 декабря 2019 г.

Актуальность аплетов

#java #java_8 #applet


В книге Шилда "Java 8 для начинающих" пару раз разбирается создание аплетов разными
средствами. Разве сейчас, эти аплеты не вытеснены JavaScript'ом? Другими словами, есть
ли толк вникать в эту область, или же это стоит изучить для общего развития?
    


Ответы

Ответ 1



Нет. Насколько я знаю, современными браузерами апплеты не поддерживаются (без специального шаманства).

Ответ 2



С 2015-го года многие браузеры перестали поддерживать апплеты. С сентября 2017 Oracle объявили технологию устаревшей. Скорее всего, в 2019-м её окончательно уберут из Java.

воскресенье, 22 декабря 2019 г.

char[] в Character[]

#java #массивы #сортировка #char #java_8


Как перевести массив char[] к его оболочке Character[] для дальнейшей сортировки
в обратном порядке?
Делаю по аналогии как с int -> Integer:

int[] array = {...};
Integer[] newArray  = Arrays.stream(array).boxed().toArray(Integer[]::new);
Arrays.sort(newArray, Collections.reverseOrder());


Но с char так не работает:

Character[] newArray = Arrays.stream(array).boxed().toArray(Character[]::new);
Arrays.sort(newArray, Collections.reverseOrder());

    


Ответы

Ответ 1



Да, для примитивов char есть такая проблема. Можно например так её решить: char[] array = {'a','c','b'}; Character[] newArray = IntStream.range(0, array.length) .mapToObj(i -> array[i]) .toArray(Character[]::new); Arrays.sort(newArray, Collections.reverseOrder()); c, b, a

Ответ 2



Как вариант, можно использовать преобразование в строку и метод chars: char[] array = new char[] {'a', 'c', 'b'}; Character[] newArray = new String(array).chars() .mapToObj(i -> (char) i).toArray(Character[]::new);

Ответ 3



Используйте ArrayUtills Character[] charObjectArray = ArrayUtils.toObject(array); Arrays.sort(charObjectArray, Collections.reverseOrder());

суббота, 21 декабря 2019 г.

Подсчет количества вхождений букв в строку с помощью потока (Stream)

#java #java_stream #java_8


Есть определенная строка(например "Some example"). Нужно определить количество вхождений
каждой буквы в строке с помощью потока Stream и при этом без использования циклов,
if и прочего(лишь рекурсия, потоки Stream и его методы вроде map, reduce, filter). 

Я сделал из строки массив символов, который потом превратил в список c помощью рекурсивного
метода toCharList. 

String text = "Some example".toLowerCase.replaceAll("\\s", "")
List textInChar = toCharList(text.toCharArray(), 0);

static public List toCharList(char[] textChars, int i) {

    if(textChars.length == i) return new ArrayList<>();

    List ret =  new ArrayList<>();
    ret.add(textChars[i]);
    ret.addAll(toCharList(textChars, i + 1));


    return ret;
}


Затем из этого списка создал хэш-мапу c помощью перегруженного метода toCharMap.
Таким образом получил я список всех не повторяющихся букв, которые есть в строке.

Map  countChar = toCharMap(textInChar);

public static Map toCharMap(List l) {
    return toCharMap(l.iterator());
}


public static Map toCharMap(Iterator it) {
    if (!it.hasNext()) return new HashMap<>();

    Map ret = new HashMap<>();
    ret.put((Character)it.next(), 0);
    ret.putAll(toCharMap(it));

    return ret;
}


Но как теперь их посчитать в потоке, например с помощью filter и reduce?
    


Ответы

Ответ 1



1) Из строки массив символов лучше получать так: String text = ("Some example").toLowerCase().replaceAll("\\s", ""); List textInChar = Chars.asList(text.toCharArray()); или: textInChar = text.chars().mapToObj(e->(char)e).collect(Collectors.toList()); 2) Xэш-мапу не повторяющихся символов лучше получить тогда уж так: Map countChar = textInChar.stream().collect(HashMap::new, (m, c) -> { m.put(c, 1); m.put(c, 1); }, HashMap::putAll); А всю задачу я бы решил так: textInChar = text.chars().mapToObj(e->(char)e).collect(Collectors.toList()); Map countChar = textInChar.stream().collect(HashMap::new, (m, c) -> { if(m.containsKey(c)) m.put(c, m.get(c) + 1); else m.put(c, 1); }, HashMap::putAll); Проверим: countChar.forEach( (k, v) -> LOG.debug(k + " -> " + v)); Выведет: p -> 1 a -> 1 s -> 1 e -> 3 x -> 1 l -> 1 m -> 2 o -> 1 UPD: if(m.containsKey(c)) m.put(c, m.get(c) + 1); else m.put(c, 1); Можно сократить до: m.put(c, m.containsKey(c) ? (m.get(c) + 1) : 1);

Ответ 2



Для группировки элементов Stream можно воспользоваться коллектором Collectors.groupingBy. Кроме варианта с одним агрументом (функцией-классификатором) у него есть вариант, принимающий еще один коллектор, c помощью которого будет выполнена редукция групп к одному значению: // Character::valueOf принимает char, а поток у нас из int String output = "Some example".chars().mapToObj( ch -> new Character( (char)ch ) ) // сгруппировать по символам, внутри группы подсчитать количество .collect( Collectors.groupingBy( Function.identity(), Collectors.counting() ) ) // группировка возвращает Map с количеством разных символов .entrySet().stream() // если надо выбрать только встречающиеся 1 раз //.filter( entry -> entry.getValue() == 1 ) // раз forEach плохо :) //.forEach( entry -> System.out.printf( "'%s' -> %d%n", entry.getKey(), entry.getValue() ) ); .map( entry -> String.format( "'%s' -> %d%n", entry.getKey(), entry.getValue() ) ) .collect( Collectors.joining() ); System.out.println( output );

Ответ 3



String string = "Some example"; Map result = IntStream .range(0, string.length()) // .parallel() // can be parallel for very big strings .mapToObj(string::charAt) .collect( toMap( Function.identity(), c -> new AtomicInteger(1), (count1, count2) -> { count1.addAndGet(count2.getAndSet(0)); return count1; }) ); для сравнения императивная реализация: String string = "Some example"; Map map = new HashMap<>(); for (int i = 0; i < string.length(); i++) { map .computeIfAbsent(string.charAt(i), character -> new AtomicInteger()) .incrementAndGet(); } которая гораздо производительнее при любых раскладах

пятница, 20 декабря 2019 г.

Вопрос про stream api java 8

#java #lambda #java_8 #java_stream


Здравствуйте, имеется 2 стринговых листа List fileList и List arr,
требуется вывести сколько слов в fileList содержатся в arr, сделал так:

fileList
        .stream()
        .filter(s -> arr
                        .stream()
                        .anyMatch(s::equals)
        )
        .count()


Правильный ли такой подход? Или есть какой-то более лучший способ?
    


Ответы

Ответ 1



Быстрее и проще будет воспользоваться пересечением множеств: Set set = new HashSet<>(fileList); set.retainAll(new HashSet(arr)); int count = set.size(); А если уж использовать стримы, то не плодить их для каждого элемента. Эффективнее будет так: long count = fileList.stream() .filter(arr::contains) .count();

четверг, 19 декабря 2019 г.

Помогите перевести мою задачу на стримы JAVA8 JAVA9

#java #java_8 #java_stream


Учу сейчас стримы из Java 8 и JAVA 9. Проблема такая, как вначале пропустить строки
файла после позиции END OF HEADER(предпоследняя строка файла), а затем считать каждое
значение(оставшиеся цифры) и возможно ли мою задачу сделать на стримах?.

2.10           N: GPS NAV DATA                         RINEX VERSION / TYPE
teqc  2017Feb10     UNAVCO Archive Ops  20170418 19:33:16UTCPGM / RUN BY / DATE
    9.2160D+04 -1.1469D+05 -1.3107D+05  7.2090D+05          ION BETA
    9.313225746155D-10 3.552713678801D-15   233472     1930 DELTA-UTC: A0,A1,T,W
                                                            END OF HEADER
29 17  1  1  2  0  0.0 6.356909871101D-04-2.046363078989D-12 0.000000000000D+00


Начал делать так:

try {
       Files.lines(Paths.get("src/main/java/ab010010.17n"), StandardCharsets.UTF_8).//
дальше что применять и множества методов?;
    } catch (IOException e) {
       e.printStackTrace();
    }   

    


Ответы

Ответ 1



В Java9 появился метод потока dropWhile, серьёзно облегчающий вашу задачу: Files.lines(Paths.get("src/main/java/ab010010.17n"), StandardCharsets.UTF_8) .dropWhile(s -> !s.contains("END OF HEADER")) .skip(1) .map(s -> s.split(" ")) .flatMap(Arrays::stream) .filter(i -> !i.isEmpty()) .forEach(System.out::println);

Ответ 2



Как уже было сказано, можно решить задачу с использованием dropWhile из Java 9, но можно и написать собственный метод в Java 8, который выполнит аналогичные действия, например, следующим образом: Stream dropWhile(Stream source, Predicate predicate) { Spliterator sourceSpliterator = source.spliterator(); return StreamSupport.stream(new Spliterators.AbstractSpliterator( sourceSpliterator.estimateSize(), sourceSpliterator.characteristics() ) { boolean dropped; public boolean tryAdvance(Consumer action) { if (dropped) { return sourceSpliterator.tryAdvance(action); } while (!dropped) { if (!sourceSpliterator.tryAdvance(t -> dropped = predicate.test(t))) { return false; } } return true; } }, source.isParallel()); } И затем использовать данный метод в своём коде: Stream input = ... dropWhile(input, s -> s.contains("END OF HEADER")) .map(s -> s.split("\\s+")) .flatMap(Arrays::stream) .forEach(System.out::println);

среда, 18 декабря 2019 г.

Java 8 interface и abstract class

#java #интерфейс #java_8


В Java 8 появилась возможность добавлять неабстрактные реализации методов в интерфейс,
используя ключевое слово default, пример:

interface Formula {    
    default double sqrt(int a) {
        return Math.sqrt(a);
    }
}


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

Зачем такое сделали? Для чего? Каково применение такого подхода? В чем профит? Ведь
специально интерфейсы и были сделаны для того, чтобы реализацию оставить на программиста,
а там всего лишь сделать черновик, набросок. Объясните пожалуйста.
    


Ответы

Ответ 1



Чтобы, например, можно было не париться с вещами такого рода, переопределяя их только там, где это даст выигрыш: void add(X element); default void addAll(Collection elements) { elements.forEach(this::add); } кроме того, абстрактный класс сужает возможности наследника, а интерфейс позволяет их расширить: отнаследовавшись от абстрактного класса, нельзя не унести с ним всю его реализацию и нельзя взять еще какую-то. В принципе, описанное в последнем абзаце - шаг в сторону множественного наследования, которое во многих случаях будет указывать на корявую архитектуру, но, тем не менее, добавление такого функционала не вводит новые ограничения, а только расширяет возможности для применения.

Ответ 2



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

Ответ 3



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

Ответ 4



На мой взгляд, основная причина, почему добавили default методы в интерфейс - возможность расширения интерфейса без необходимости переписывания классов, реализующих интерфейс. Например, используя JDK 1.6 ты написал свой класс имплементирующий интерфейс Collection, потом решил обновить джаву до 1.8. Если бы не было default методов в интерфейсах, у тебя бы проект не собрался и тебе пришлось бы реализовывать removeIf, spliterator, stream и т.д. Т.е. изначально default методы нужны были самим разработчикам 8-ой джавы. А мы теперь можем использовать для того же. Плюс в отличии от абстрактных классов, возможности default методов в интерфейсах ограничены, т.к. они не могут оперировать с полями, разве что читать статичные.

Ответ 5



Основная цель создания методов по умолчанию(default) сохранение обратной совместимости при модификации интерфейсов. В частности это было необходимо для внесения новых методов в интерфейсы самой платформы Java.

понедельник, 16 декабря 2019 г.

Где имеет смысл использовать Optional?

#java_8 #optional


В следующей статье про Optional такое резюме: 


  не загрязняйте свой код использованием везде Optional, используйте
  их осторожно, где это имеет смысл (don't pollute your code with
  Optional everywhere, use them carefully where it makes sense).


А где имеет смысл использовать Optional? Есть ли чёткое разделение (или хотя бы какие-нибудь
рекомендации), когда лучше использовать if-else, а когда Optional?
    


Ответы

Ответ 1



Прежде всего, все разговоры об Optional - это холивар. Однако есть ряд следствий: Правильное использование Optional упрощает контракт в приложении, когда разработчик библиотеки явно показывает, какие поля могут быть null. А разработчик конечного приложения явно видит, где необходимо сделать проверки, а где можно обойтись без них. Тут важны ключевые слова правильное использование и явно видит. В реальности сама переменная типа Optional<*> может быть null'ом. Работа с Optional ударяет по производительности, так как может потребовать выделение в куче дополнительных объектов - самих классов Optional, лябд. Заодно увеличивается вероятность боксинга примитивных значений. При работе с Optional'ами увеличивается объем кода. Из всего этого есть важные следствия: Вопросы об Optional ближе к вопросам о Code Review, о Style Guide. При правильном подходе код будет более чистый, при неправильном - всё станет только хуже. Для JVM класс Optional не оказывает положительного эффекта, однако может оказать отрицательный. Если вам надежность важнее скорости (например, нагрузка не такая большая, а данные серьезные), то Optional может помочь сделать код лучше. И еще раз - это холивар, четкого и однозначного ответа нет.

воскресенье, 15 декабря 2019 г.

Java 8 stream map to new Objects

#java #map #java_8 #java_stream


Как можно замапить стрим в новые объекты? Или, эти примеры что не равнозначны?

list.stream().map(new SomeObject()::convertToSomeObject)

list.stream().map(item -> new SomeObject().convertToSomeObject(item))

public SomeObject convertToSomeObject(Item item) {
    this.name = item.getName;
    this.value = item.getValue;
    return this;
}


в первом случае у меня все замапилось одним объектом 
    


Ответы

Ответ 1



Почему не работает первый вариант При выполнении строчки list.stream().map(new SomeObject()::convertToSomeObject) происходит следующее: до вызова метода map создаётся новый объект SomeObject ссылка на метод этого объекта передаётся в качестве параметра-лямбды в метод map берутся по очереди все элементы старого потока, к ним применяется переданный метод из полученных в результате применения метода новых объектов создаётся новый поток Здесь объект SomeObject создаётся ровно один раз, и так как его метод convertToSomeObject всегда возвращает единственный созданный экземпляр, то и получается, что новый поток состоит из ссылок на один и тот же объект. При этом метод convertToSomeObject вызывался для каждого элемента потока и каждый раз значения полей объекта перезаписывались Почему работает второй варинат При выполнении строчки list.stream().map(item -> new SomeObject().convertToSomeObject(item)) происходит следующее: никаких объектов SomeObject до вызова map не создаётся в метод map передаётся лямбда-функция, которая работает как обычная функция: принимает объект item создаёт новый экземпляр класса SomeObject вызывает метод convertToSomeObject у созданного экземпляра возвращает результат вызова метода convertToSomeObject, то есть только что созданный экземпляр класса SomeObject заметим, что лямбда-функция создаёт новый объект при каждом вызове Как исправить первый вариант написать конструктор SomeObject, принимающий Item в качестве параметра: public SomeObject(Item item) { this.name = item.getName(); } list.stream().map(SomeObject::new) добавить в класс SomeObject статический метод, принимающий Item и возвращающий SomeObject: public static SomeObject convertToSomeObject(Item item) { SomeObject object = new SomeObject(); object.name = item.getName(); return object; } list.stream().map(SomeObject::convertToSomeObject) Перенести метод convertToSomeObject в класс Item: public SomeObject convertToSomeObject() { SomeObject object = new SomeObject(); object.name = this.getName(); return object; } list.stream().map(Item::convertToSomeObject)

суббота, 7 декабря 2019 г.

Анти-паттерн сессия-на-операцию

#java #hibernate #java_ee #шаблоны_проектирования #java_8


В одной статье по Hibernate прочитал, что если вы хотите использовать многопоточность,
то создавайте новую сессию для каждой CRUD-операции. То есть вот как выглядит, например,
операция сохранения в моем DAO-классе:

public void save(User user) {
    try (Session session = HibernateSessionFactoryUtil.getSessionFactory().openSession()) {
        Transaction tx = session.beginTransaction();
        session.save(user);
        tx.commit();
    }
}


Но вот незадача. Сегодня наткнулся на еще одну статью, в которой говорят, что описанное
выше действие является анти-паттерном. Дословно: 


  Это анти-паттерн про открытие и закрытие объекта Session на каждую операцию к БД
в одном потоке. Это также анти-паттерн в терминах транзакций БД. Группируйте ваши вызовы
в одну запланированную последовательность. Также, не делайте авто-коммит транзакции
на каждое SQL-выражение. Hibernate выключает, или ожидает, что сервер приложений немедленно
выключит режим авто-коммита. 


Заместо этого в этой статье предложен Паттерн сессия-на-запрос, где его описывают как:


  Наиболее распространенный паттерн транзакций. Термин “запрос” здесь следует понимать
в контексте системы, реагирующей на серии запросов от пользователя/клиента. Веб-приложения
является основным примером таких систем, но, конечно, не только они одни. На этапе
начала обработки запроса, приложение открывает объект Session, инициирует транзакцию,
проводит всю сопутствующую работу с данными, завершает транзакцию и закрывает Session.
Суть паттерна – это отношение один-к-одному между транзакцией и сессией. 


После этого у меня возник когнитивный диссонанс. Во-первых потому, что оказывается
согласно этой статье, раньше я делал не так, создавая сессию для каждой операции, а
во-вторых потому, что я не понял, что предполагает второй паттерн. Объясните, что из
этого правда, а что ложь? Что использовать, а про что забыть?
    


Ответы

Ответ 1



Если обратится к документации Hibernate - то по ней сессия-на-операцию официально обьявлена антипаттерном. Что касается сессии, то сам ее смысл предполагает что вы можете и по возможности должны объединять связанные по смыслу операции в рамках одной или нескольких транзакций в одну сессию. В тоже время ничего плохого нет в том что ваша сессия может содержать лишь одну операцию, если кроме этой операции ничего делать не надо. Чтобы понять разницу между первым и вторым приведу пример: представьте что существует еще один класс связанный с User, например UserHistory (пусть он будет хранить историю изменения имени пользователя). Мы предполагаем по смыслу что изменяя name в User должен обязательно обновляться и класс UserHistory. Теперь представим что мы сделали все через operation-per-session. В этом случае у нас будет две разные сессии и две транзакции которые обновят User и UserHistory независимо друг от друга. Проблема возникнет тогда когда одна из транзакций не дойдет (например из за внезапных потерь на сети) что неизбежно приведет к нарушению целостности данных в БД. И да, при работе с БД не через пулы - крайне ресурсоемко создавать и закрывать соединения с БД при каждом действии.

Ответ 2



Действительно как пишется в документации к Hibernate: The scope of a Hibernate org.hibernate.Session is flexible but you should never design your application to use a new Hibernate org.hibernate.Session for every database operation. Even though it is used in the following examples, consider session-per-operation an anti-pattern Обратите внимание, что говорится о новой сессии, по сути предлагается использовать текущую сессию, а именно: Session session = HibernateUtil.getSessionFactory().getCurrentSession(); session.beginTransaction(); //blah-blah session.getTransaction().commit(); То есть если нет текущей сессии, то создаем, если есть берем текущую. Создание сессии при каждой операции является антипаттерном, но не потому, что типа в другой сессии будет произведено обновление или там не дойдет до конца транзакция (менеджер транзакций СУБД вполне умеет с такими вещами работать), а из-за стоимости создания сессии - по сути это новый коннект к БД - крайне дорогая и ресурсоемкая операция.