API, ради которых наконец-то стоит обновиться с Java 8. Часть 2

    Продолжаем рассказ про API, которые появились в новых версиях Java.



    1. Files.mismatch()


    Появился в: Java 12


    На практике довольно часто возникает необходимость проверить, являются ли два файла в точности одинаковыми или нет. С помощью метода Files.mismatch(), появившегося в Java 12, это наконец-то можно сделать. Этот метод возвращает позицию первого несовпадающего байта в двух файлах или -1, если файлы идентичны.


    Это может быть полезно, например, когда синхронизируешь содержимое двух директорий. Чтобы не перезаписывать файл при копировании тем же самым содержимым и лишний раз не нагружать диск, можно сначала проверить, идентичны файлы или нет:


    public static void syncDirs(Path srcDir, Path dstDir)
            throws IOException {
        // Для простоты демонстрации считаем, что поддиректорий нет
        try (Stream<Path> stream = Files.list(srcDir)) {
            for (Path src : stream.collect(toList())) {
                Path dst = dstDir.resolve(src.getFileName());
                if (!Files.exists(dst)) {
                    System.out.println("Copying file " + dst);
                    Files.copy(src, dst);
                } else if (Files.mismatch(src, dst) >= 0) {
                    System.out.println("Overwriting file " + dst);
                    Files.copy(src, dst, StandardCopyOption.REPLACE_EXISTING);
                }
            }
        }
    }

    (Кстати, когда уже наконец Stream отнаследуют от Iterable? Хочется просто писать for (Path file : stream), а не возиться с промежуточными списками.)


    2. Новые методы в java.time


    Появились в: Java 9


    В Java почти 20 лет не было нормального API для работы с датами и временем. Эту проблему решили лишь в Java 8, когда ввели новый пакет java.time под руководством небезызвестного Стивена Колборна, создателя библиотеки Joda Time. А в девятой версии java.time добавили множество интересных методов.


    В Java 8 Duration нельзя просто разбить на составляющие (например, прошло 2 дня, 7 часов, 15 минут, 12 секунд). В Java 9 для этого появились методы toDaysPart(), toHoursPart(), toMinutesPart(), toSecondsPart() и т.д. Пример:


    public static String modifiedAgo(Path path) throws IOException {
        FileTime time = Files.getLastModifiedTime(path);
        Instant to = Instant.now();
        Instant from = time.toInstant();
        Duration d = Duration.between(from, to);
        return String.format(
            "Файл был изменён %d дней, %d часов, %d минут, %d секунд назад",
            d.toDaysPart(), d.toHoursPart(),
            d.toMinutesPart(), d.toSecondsPart());
    }

    А что если нам надо узнать, сколько месяцев назад был изменён файл? Элегантного способа на Java 8, насколько мне известно, нет. А в Java 9 для этого можно использовать новый метод Duration.dividedBy():


    public static long modifiedAgo(Path path, ChronoUnit unit)
            throws IOException {
        FileTime time = Files.getLastModifiedTime(path);
        Instant to = Instant.now();
        Instant from = time.toInstant();
        Duration d = Duration.between(from, to);
        return d.dividedBy(unit.getDuration());
    }
    
    public static void main(String[] args) throws Exception {
        Path path = ...
        System.out.printf("Файл был изменён %d месяцев назад%n",
            modifiedAgo(path, ChronoUnit.MONTHS));
    }

    Нововведения коснулись также класса LocalDate. С помощью метода LocalDate.ofInstant() можно сконвертировать Instant в LocalDate:


    LocalDate date = LocalDate.ofInstant(
        Instant.now(), ZoneId.systemDefault());
    System.out.println(date);

    А используя новый метод LocalDate.datesUntil(), наконец-то можно легко получить Stream всех дат в интервале между двумя датами:


    LocalDate from = LocalDate.of(2020, 1, 1);
    LocalDate to = LocalDate.of(2020, 1, 9);
    from.datesUntil(to)
        .forEach(System.out::println);

    Вывод:


    2020-01-01
    2020-01-02
    2020-01-03
    2020-01-04
    2020-01-05
    2020-01-06
    2020-01-07
    2020-01-08

    Также есть перегрузка, где можно указать период:


    LocalDate from = LocalDate.of(2020, 1, 1);
    LocalDate to = LocalDate.of(2020, 1, 31);
    from.datesUntil(to, Period.ofWeeks(1))
        .forEach(System.out::println);

    Вывод:


    2020-01-01
    2020-01-08
    2020-01-15
    2020-01-22
    2020-01-29

    Остальные методы:



    3. Collection.toArray() с функцией-генератором


    Появился в: Java 11


    С конвертацией коллекций в массивы у Java была непростая история. С момента появления Collection в Java 1.2 было два способа создания массива на основе коллекции:


    • Использовать метод Collection.toArray(), который возвращает Object[].
    • Использовать метод Collection.toArray(Object[]), который принимает уже созданный массив и заполняет его. Если переданный массив недостаточной длины, то создаётся новый массив нужной длины того же типа и возвращается. С появлением дженериков в Java 1.5 метод логичным образом поменял свою сигнатуру на Collection.toArray(T[]).

    Загвоздка в том, что если нужен массив конкретного типа (допустим String[]), второй метод можно использовать двумя способами:


    • Использовать конструкцию collection.toArray(new String[0]). Тем самым, мы сознательно почти всегда отбрасываем массив, а передаём его туда, чтобы метод узнал тип массива.
    • Использовать конструкцию collection.toArray(new String[collection.size()]). В этом случае массив передаётся нужной длины, а значит ничего зря не отбрасывается, и код по идее работает быстрее. К тому же здесь не нужен рефлективный вызов.

    Таким образом, второй вариант долгое время считался основным, и в IntelliJ IDEA даже была инспекция, которая подсвечивала первый вариант и предлагала конвертировать его во второй, более эффективный.


    Однако в 2016 году вышла статья Алексея Шипилёва, где он решил досконально разобраться в этом вопросе и пришёл к выводу, что не-а: первый вариант всё-таки быстрее (по крайней мере в версиях JDK 6+). Эта статья получила большой резонанс, и в IDEA решили изменить инспекцию, сделав у неё три опции: предпочитать пустой массив (default), предпочитать преаллоцированный массив или предпочитать то или иное в зависимости от версии Java.


    Но история на этом не закончилась, потому что некоторые программисты принципиально не желали использовать эти хаки с пустыми массивами и хотели писать код «элегантно». Поэтому они вспомнили про Stream.toArray(IntFunction[]) и стали писать collection.stream().toArray(String[]::new). Медленно? Ну и что, зато красиво.


    Программисты из Oracle посмотрели на всё это безобразие и подумали: а давайте уже сделаем один нормальный способ, который и будет рекомендованным? И в Java 11 добавили долгожданный метод Collection.toArray(IntFunction[]), тем самым запутав людей ещё сильнее.


    Но на самом деле никакой путаницы нет. Да, теперь есть 4 варианта, но если вы не выжимаете такты из своего процессора, то вам следует просто использовать новый метод:


    List<Integer> list = ...;
    Integer[] array = list.toArray(Integer[]::new);

    4. Методы InputStream: readNBytes(), readAllBytes(), transferTo()


    Появились в: Java 9 / Java 11


    Ещё одно неудобство, которое существовало в Java долгие годы – отсутствие стандартного короткого способа считать все данные из InputStream. Если не прибегать к библиотекам, то в Java 8 решить такую задачу довольно нетривиально: нужно завести список буферов, заполнять их, пока данные не кончатся, потом слить в один большой массив, учесть, что последний буфер заполнен лишь частично и т.д. Короче, нюансов хватает.


    В Java 9 добавили метод InputStream.readAllBytes(), который берёт всю эту работу на себя и возвращает заполненный массив байтов точной длины. Например, прочитать stdout/stderr процесса теперь очень легко:


    Process proc = Runtime.getRuntime().exec("java -version");
    try (InputStream inputStream = proc.getErrorStream()) {
        byte[] bytes = inputStream.readAllBytes();
        System.out.print(new String(bytes));
    }

    Вывод:


    openjdk version "14-ea" 2020-03-17
    OpenJDK Runtime Environment (build 14-ea+33-1439)
    OpenJDK 64-Bit Server VM (build 14-ea+33-1439, mixed mode, sharing)

    Также если надо прочитать только N байтов, то можно использовать метод из Java 11 InputStream.readNBytes().


    Если же надо легко и эффективно (без промежуточного массива) перенаправить InputStream в OutputStream, то можно использовать InputStream.transferTo(). Например, для вывода версии Java в файл код будет выглядеть примерно так:


    Process proc = Runtime.getRuntime().exec("java -version");
    Path path = Path.of("out.txt");
    try (InputStream inputStream = proc.getErrorStream();
         OutputStream outputStream = Files.newOutputStream(path)) {
        inputStream.transferTo(outputStream);
    }

    Кстати, перенаправить Reader во Writer теперь тоже можно: с помощью метода Reader.transferTo(), появившегося в Java 10.


    5. Collectors.teeing()


    Появился в: Java 12


    При использовании Stream часто возникает необходимость собрать элементы в два коллектора. Допустим, у нас есть Stream из Employee, и нужно узнать:


    • Сколько всего сотрудников в Stream.
    • Сколько сотрудников, у которых есть телефонный номер.

    Как это сделать в Java 8? Первое, что приходит в голову: сначала позвать Stream.count(), а потом Stream.filter() и Stream.count(). Однако это не сработает, потому что Stream является одноразовым и второй вызов выбросит исключение.


    Второй вариант – завести два счётчика и увеличивать их внутри Stream.forEach():


    Stream<Employee> employees = ...
    int[] countWithPhoneAndTotal = {0, 0};
    employees
        .forEach(emp -> {
            if (emp.getPhoneNumber() != null) {
                countWithPhoneAndTotal[0]++;
            }
            countWithPhoneAndTotal[1]++;
        });
    System.out.println("Employees with phone number: "
        + countWithPhoneAndTotal[0]);
    System.out.println("Total employees: "
        + countWithPhoneAndTotal[1]);

    В принципе, это работает, но это императивный подход, который плохо переносится на другие виды коллекторов. Stream.peek() плох по той же причине.


    Ещё есть идея использовать Stream.reduce():


    class CountWithPhoneAndTotal {
        final int withPhone;
        final int total;
    
        CountWithPhoneAndTotal(int withPhone, int total) {
            this.withPhone = withPhone;
            this.total = total;
        }
    }
    
    CountWithPhoneAndTotal countWithPhoneAndTotal = employees
        .reduce(
            new CountWithPhoneAndTotal(0, 0),
            (count, employee) -> new CountWithPhoneAndTotal(
                employee.getPhoneNumber() != null
                    ? count.withPhone + 1
                    : count.withPhone,
                count.total + 1),
            (count1, count2) -> new CountWithPhoneAndTotal(
                count1.withPhone + count2.withPhone,
                count1.total + count2.total));
    System.out.println("Employees with phone number: "
        + countWithPhoneAndTotal.withPhone);
    System.out.println("Total employees: "
        + countWithPhoneAndTotal.total);

    Этот вариант, конечно же, кошмар. Во-первых, он слишком огромный, во-вторых, неэффективный, так как на каждом шагу создаётся новый экземпляр CountWithPhoneAndTotal. Если когда-нибудь доделают Валгаллу, то можно будет пометить класс CountWithPhoneAndTotal как inline, но первая проблема всё равно останется.


    На этом мои идеи закончились. Если вдруг кто-то придумает, как сделать такой подсчёт в Java 8 коротким и эффективным, то напишите в комментариях. А я расскажу, как это можно сделать в Java 12 с помощью метода Collectors.teeing():


    Entry<Long, Long> countWithPhoneAndTotal = employees
        .collect(teeing(
            filtering(employee -> employee.getPhoneNumber() != null, counting()),
            counting(),
            Map::entry
        ));

    И всё.


    С методом Collectors.teeing() была очень интересная история: когда ему придумывали имя, то долго не могли прийти к консенсусу из-за огромного количество предложенных вариантов. Чего там только не было: toBoth, collectingToBoth, collectingToBothAndThen, pairing, bifurcate, distributing, unzipping, forking,… В итоге его назвали teeing от английского слова tee, которое само произошло от буквы T, напоминающую по форме раздваиватель. В этом и есть суть имени метода: он раздваивает поток на две части.


    6. Runtime.version()


    Появился в: Java 9


    Иногда нужно узнать версию Java во время выполнения. Помните ли вы, как это сделать? Скорее всего, вы полезете искать название нужного свойства в интернете. Возможно некоторые вспомнят, что оно называется java.version. А ещё вроде бы есть java.specification.version… На самом деле, таких свойств как минимум пять:


    for (String key : Arrays.asList(
            "java.version",
            "java.runtime.version",
            "java.specification.version",
            "java.vm.version",
            "java.vm.specification.version")) {
        System.out.println(key + " = " + System.getProperty(key));
    }

    Если запустить код на Java 8, то он выведет примерно следующее:


    java.version = 1.8.0_192
    java.runtime.version = 1.8.0_192-b12
    java.specification.version = 1.8
    java.vm.version = 25.192-b12
    java.vm.specification.version = 1.8

    Как отсюда вытащить цифру 8? Наверное, надо взять java.specification.version, отбросить 1., потом сконвертировать строку в число… Но не торопитесь, потому что на Java 9 это всё сломается:


    java.version = 9.0.1
    java.runtime.version = 9.0.1+11
    java.specification.version = 9
    java.vm.version = 9.0.1+11
    java.vm.specification.version = 9

    Однако не печальтесь, потому что в Java 9 появилось нормальное API для получения версий и было немного допилено в Java 10. С этим API больше не нужно ничего «вытаскивать» и парсить, а можно просто позвать метод Runtime.version(). Этот метод возвращает объект типа Runtime.Version, у которого можно запросить все нужные части версии:


    Runtime.Version version = Runtime.version();
    System.out.println("Feature = " + version.feature());
    System.out.println("Interim = " + version.interim());
    System.out.println("Update = " + version.update());
    System.out.println("Patch = " + version.patch());

    Например, вот что он вернёт, если его позвать на JDK 11.0.5:


    Feature = 11
    Interim = 0
    Update = 5
    Patch = 0

    7. Optional.isEmpty()


    Появился в: Java 11


    Я не стану утверждать, что этот метод изменит вашу жизнь радикальным образом, но всё же в некоторых случаях он сможет избавить вас от ненужных отрицаний:


    if (!stream.findAny().isPresent()) {
        System.out.println("Stream is empty");
    }

    Используя метод Optional.isEmpty(), код можно немножко упростить:


    if (stream.findAny().isEmpty()) {
        System.out.println("Stream is empty");
    }

    Также этот метод позволяет заменить лямбды на ссылки на методы в некоторых случаях:


    Stream<Optional<Integer>> stream = Stream.of(
        Optional.of(1),
        Optional.empty(),
        Optional.of(2));
    
    long emptyCount = stream
        .filter(Optional::isEmpty) // Было opt -> !opt.isPresent()
        .count();

    8. HTTP-клиент


    Появился в: Java 11


    Долгое время единственным API для клиентского HTTP был класс HttpURLConnection, который существовал в Java практически с момента её появления. Спустя два десятилетия стало очевидно, что он больше не отвечает современным требованиям: он неудобен в использовании, не поддерживает HTTP/2 и веб-сокеты, работает только в блокирующем режиме, а ещё его очень трудно поддерживать. Поэтому было принятое решение создать новый HTTP Client, который попал в Java 9 в качестве инкубационного модуля, а позже был стандартизован в Java 11.


    Новый клиент находится в модуле java.net.http, и его использование осуществляется через главный класс HttpClient. Приведём пример, как можно сделать простой HTTP-запрос с сайта и получить содержимое страницы с кодом ответа:


    HttpClient client = HttpClient.newHttpClient();
    
    HttpRequest request = HttpRequest
        .newBuilder(new URI("https://minijug.ru"))
        .build();
    
    HttpResponse<Stream<String>> response = client.send(request,
        HttpResponse.BodyHandlers.ofLines());
    
    System.out.println("Status code = " + response.statusCode());
    System.out.println("Body = ");
    // Первые 4 строки
    response.body().limit(4).forEach(System.out::println);

    Вывод:


    Status code = 200
    Body =
    <!doctype html>
    <html>
      <head>
        <title>miniJUG</title>

    В модуле java.net.http большое количество возможностей, и на их описание уйдёт много времени, поэтому сегодня мы ограничимся только примером выше.


    9. Lookup.defineClass()


    Появился в: Java 9


    Приходилось ли вам загружать классы во время выполнения? Если да, вы наверняка знаете, что в Java 8 без нового загрузчика класса это сделать нельзя. Ну или ещё можно использовать Unsafe.defineClass() или Unsafe.defineAnonymousClass(), но это нестандартное API, которое крайне не рекомендуется использовать.


    Однако есть хорошая новость: если вам нужно загрузить класс в том же пакете, не создавая новый загрузчик класса, то для этого можно использовать стандартный метод MethodHandles.Lookup.defineClass(), который появился в Java 9. Этому методу достаточно передать массив байтов класса:


    // Main.java
    import java.lang.invoke.MethodHandles;
    import java.nio.file.Files;
    import java.nio.file.Path;
    
    public class Main {
        public static void main(String[] args) throws Exception {
            byte[] bytes = Files.readAllBytes(Path.of("Temp.class"));
            Class<?> clazz = MethodHandles.lookup().defineClass(bytes);
            Object obj = clazz.getDeclaredConstructor().newInstance();
            System.out.println(obj);
        }
    }

    // Temp.java
    class Temp {
        @Override
        public String toString() {
            return "Hello from Temp!";
        }
    }

    Теперь скомпилируем класс Temp, а затем скомпилируем и запустим класс Main


    > javac Temp.java
    
    > javac Main.java
    
    > java Main
    Hello from Temp!

    Повторюсь, чтобы это сработало, класс Temp и класс Main должны находиться в одном пакете (в данном случае они оба находятся в дефолтном пакете, поэтому всё хорошо). Если класс Temp будет находиться в другом пакете, то понадобится завести специальный класс-делегат в том же пакете, что и Temp, и осуществлять загрузку через него.


    Да, пример выше совсем простой, но это сделано исключительно для краткости и простоты демонстрации. Так как defineClass() принимает массив байтов, то загружать класс можно откуда угодно, а не только с файловой системы. Можно даже загрузить класс, скомпилированный в память во время исполнения. Для этого можно использовать ToolProvider.getSystemJavaCompiler(), который находится в модуле java.compiler (конкретную реализацию я оставлю в качестве упражнения для читателя).


    10. ByteArrayOutputStream.writeBytes()


    Появился в: Java 11


    Метод ByteArrayOutputStream.writeBytes() – это дублёр метода ByteArrayOutputStream.write() с одним важным отличием: в сигнатуре write() есть throws IOException, а в сигнатуре writeBytes() – нету (IOException есть во write(), потому что этот метод наследуется от OutputStream). Это значит, что начиная с Java 11, использование ByteArrayOutputStream становится немножко проще:


    private static byte[] concat(Stream<byte[]> stream) {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        // stream.forEach(out::write); (Не скомпилируется)
        stream.forEach(out::writeBytes);
        return out.toByteArray();
    }

    Бонус: конструктор IndexOutOfBoundsException(int)


    Появился в: Java 9


    Сегодняшний рассказ хочу завершить мелким улучшением в Java 9: если вам надо выбросить IndexOutOfBoundsException с указанием неправильного индекса, то теперь можно просто передать этот индекс в конструктор, и он сам сгенерирует сообщение:


    private static void doAtIndex(int index) {
        if (index < 0) {
            throw new IndexOutOfBoundsException(index);
        }
        // ...
    }
    
    public static void main(String[] args) {
        // java.lang.IndexOutOfBoundsException: Index out of range: -1
        doAtIndex(-1);
    }

    Заключение


    Итак, мы рассмотрели ещё 10 (+1) новых API, которые появились в новых версиях Java. Всё ещё не хотите обновляться? Если нет, то тогда ждите следующую часть.

    Похожие публикации

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 41

      –8

      Если бы они ещё не сломали совместимость с Windows XP… 8-ка нормально запускается, 11 уже не запускается.

        +3
        В 9 сильно поменяли работу с памятью и многопоточность. Не работает даже на Висте.
          0

          Тут пишут:


          As part of the natural order of things when building Zulu 9 for Windows, we had switched from Visual C++ 2010 to the more modern Visual C++ 2013. Because of changes in the way the compilers work to support Windows XP it would be necessary to use Managed Multi-Targeting. To simplify our build procedures, we don’t do this, which is why Zulu 8 would work on Windows XP, whereas Zulu 9 won’t.

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

            0
            Хм, Oracle Open JDK 9 пашет на висте. Похоже, Zulu еще больше обленились.
          +5
          Это очень актуально, особенно после новости, что прекращена поддержка Windows 7:)
          Или это был троллинг?
            –1
            Ага, вот прекратили поддержку, и мы все дружно бежим выбрасывать старые компы и покупать новые, чтобы на них новая ОС завелась…

            Я вот не вижу никаких принципиальных причин, почему невозможно продолжать писать ПО (и не только Java, а вообще), не используя добавившиеся в новых версиях ОС API (вроде бы, их не так уж много? и там нет ничего мега-необходимого? или я чего-то не знаю?). Ну, кроме стандартного программистского «ой, я тут фреймворк зачем-то обновил, а он старую ОС не поддерживает — ну и фиг с ней!»
              0
              Ага, вот прекратили поддержку, и мы все дружно бежим выбрасывать старые компы и покупать новые, чтобы на них новая ОС завелась…

              Так M$ и на это рассчитывает...


              Я вот не вижу никаких принципиальных причин, почему невозможно продолжать писать ПО

              Весь этот груз под названием "легаси" всё труднее и труднее поддерживать так, чтобы при этом ещё новые плюшки завести. Желательно без костылей

                0
                Вот если бы речь шла о выкидывании поддержки IE6 из какого-нибудь веб-фреймворка, то тут можно было бы говорить про тяжёлый груз поддержки «легаси». А в случае винды, когда старое API не меняется, а только добавляется немножко новых функций, это не так.
              0

              Нет, это не троллинг, это реальная причина, по которой я не могу пока перейти на Java 9+. Не всем критична поддержка. Иногда на новых версиях ОС просто не работает нужный софт и всё оно требует Windows XP (точней 2003 но это не существенно). Например в данном случае это Oracle 9i (на который, о ужас, поддержка тоже кончилась) и некий самописный софт. Поддержка кончилась, а система работает, новые функции в ней появляются и в принципе неплохо было бы новую Java использовать.

            +1

            Первый пример: почему не


            Files.list(srcDir).peek(src-> { ...

            ?

              0
              И какую проблему это решит? А что после peek будет?
                +1

                API Note: This method exists mainly to support debugging, where you want to see the elements as they flow past a certain point in a pipeline

                  +2

                  Если использовать peek просто так, то ничего не произойдёт, ибо он ленивый и не вычислится. Если после него есть какая-нибудь терминирующая операция, то сработает. (Вопрос насчёт этого, конечно. Можно задействовать map и возвращать параметр лямбды без изменений).


                  Но для замены обычного цикла лучше всего Files.list(srcDir).forEach(src-> { ... });, если хочется заменить, конечно.

                    0
                    peek работает не на всех терминальных операциях (например count)
                    0
                    (Кстати, когда уже наконец Stream отнаследуют от Iterable? Хочется просто писать for (Path file: Files.list(dir)), а не возиться с промежуточными списками.)

                    Батюшка, да куда же Вам обновляться с java 8, коль вы основную фичу java 8 ещё не поняли? :)

                      –2
                      Какую фичу?
                        +2

                        Думаю, речь про отказ от for в пользу стримов в данном конкретном примере

                          –2
                          Если переписать на Stream, то придётся ловить IOException. В этом то и проблема.
                            0

                            А чего бы forEach не применить?

                              –1
                              Если переписать на Stream, то придётся ловить IOException. В этом то и проблема.
                                0

                                Вас смущает, что не получится просто добавить в метод throws IOException, а придётся обрабатывать его внутри forEach? Но ведь вы всё равно не будете использовать проверяемые исключения в своём коде, если это не тестовый метод или учебный пример. Исключение же всё равно надо будет завернуть в какой-нибудь RuntimeException внутри метода. Тем более, что try блок у вас там уже есть.

                      +1

                      Для случаев, когда есть только один is*, можно использовать Predicate.not вместо отрицания:


                      long emptyCount = stream
                          .filter(not(Optional::isPresent))
                          .count();

                      Как отсюда вытащить цифру 8? Наверное, надо взять java.specification.version, отбросить 1., потом сконвертировать строку в число… Но не торопитесь, потому что на Java 9 это всё сломается:

                      Однако не печальтесь, потому что в Java 9 появилось нормальное API для получения версий

                      Поэтому на восьмёрке продолжаем строить велосипед.

                        0

                        не туда написал

                          0
                          Поэтому на восьмёрке продолжаем строить велосипед.

                          А можно использовать чужой велосипед, например JavaVersion из org.apache.commons:commons-lang3.

                            0

                            Кому-то не захочется/не понравится тащить ещё одну зависимость в проект. Тем более если велосипед в пару строк, а может быть даже и тестом покрыт.

                            +1
                            Predicate.not и Optional.isEmpty появились одновременно в Java 11, поэтому в данном случае not не даёт погоды. Но в других случаях, да, он может быть полезен, например: not(Collection::isEmpty).
                            +1
                            (Кстати, когда уже наконец Stream отнаследуют от Iterable? Хочется просто писать for (Path file: Files.list(dir)), а не возиться с промежуточными списками.)

                            Вообще стоит понимать что Stream, возвращаемый из java.nio.file.Files#list(Path), рекомендуется оборачивать в try-with-resources так как он потребляет ресурсы:


                            This method must be used within a try-with-resources statement or similar control structure to ensure that the stream's open directory is closed promptly after the stream's operations have completed.
                              0
                              Спасибо, это важное замечание. Исправил.
                              0
                              в сигнатуре write() есть throws IOException

                              Интересно, что мешало в BAOS переопределить write(byte[]) так, чтобы он не мог кидать исключение.

                                0
                                Потому что это сломало бы код у тех, кто пишет так:

                                class MyBAOS extends ByteArrayOutputStream {
                                    @Override
                                    public void write(byte[] b) throws IOException {
                                        // код, который может кинуть IOException
                                    }
                                }
                                  0

                                  О, точно, можно же так делать. Да, получилось бы плохо.
                                  Ох уж эти промахи прошлого.

                                    0
                                    Пример выше может бы и не сломал много кода, потому что так очень редко кто делает, но вот такой код точно сломается много у кого:

                                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
                                    try {
                                        baos.write(bytes);
                                    } catch (IOException ex) {
                                    }


                                    Если у write убрать IOException, то catch больше не будет компилироваться.
                                +1
                                Кстати, когда уже наконец Stream отнаследуют от Iterable?

                                Никогда. Iterable (за довольно редким исключением) можно обойти по итератору больше одного раза. Со Stream такие фокусы не пройдут. Следовательно, то, что у обоих есть метод iterator() — это скорее совпадение.


                                Но в принципе, можно так:


                                Stream<String> s = Stream.of("GNU", "is not", "Unix");
                                
                                Iterable<String> iterableStream = s::iterator;
                                for(String : iterableStream) {
                                   ...
                                }

                                Но всё равно нужно помнить, что это больше хак, чем фича.

                                  0
                                  Никогда

                                  Вообще-то в багтрекере OpenJDK и рассылке много обсуждали, сделать ли цикл for для Stream: bugs.openjdk.java.net/browse/JDK-8148917
                                    0

                                    Поддержка for:each для Stream — это всё-таки не то же самое, что "унаследовать Stream от Itrable". Кроме того, даже по ссылке как раз ничего не говорится про прямое наследование, потому что прямое наследование не сработает — они пытаются выделить отдельный подтип, который уже можно применить и внутри конструкции, и может быть даже утащить под неё же DirectoryStream.

                                      0
                                      Какая разница, прямое наследование или нет? Да, хотят сделать IterableOnce, но это общей картины не меняет: Stream<T> <: IterableOnce<T> <: Iterable<T>. И почему прямое наследование не сработает? Аргументы предоставьте.
                                        0

                                        Я ведь уже говорил, почему не сработает (и не только я, даже в вашей ссылке есть). Iterable — это фабрика независимых друг от друга итераторов. В отличии от Stream. Введение IterableOnce extends Iterable сломает ожидания в немалом количестве кода — компилироваться не прекратит, но работать без исключений уже не сможет.
                                        Например, образцово показательно окажется сломанным com.google.common.collect.Iterables.cycle — а библиотека вовсе не маленькая и не редкая. По второй ссылке видно, что нависнет проблема с библиотекой AssertJ. Подобная же проблема будет с Hamcrest. И так далее и тому подобное.
                                        И вся эта возня костылями по песку чтобы получить break и continue? Что такого они могут чего не сумеют .filter() и .takeWhile().count()? Пример из пропозала вообще решается через teeingCollector — и, прошу заметить, без введения неопределённости в поведение интерфейса из единственного метода.

                                          0
                                          По второй ссылке видно, что нависнет проблема с библиотекой AssertJ

                                          Какой второй ссылке? Какая проблема?
                                            0
                                            Какой второй ссылке?

                                            Да вот этой самой, которая IterableOnce. В части Analysis.


                                            Есть немалая категория кода, которая ожидает, что из Iterable можно получить сколько угодно итераторов. Сам пропозал называет один, я назвал ещё один. Пока все реализации ограничены сравнительно редким DirectoryStream, проблема небольшая. Если сюда же внезапно добавить крайне распространённые BaseStream сотоварищи, частота вырастет многократно, причём, будет нарушен principle of least astonishment (мало вам Collection::add и Collections.unmodifiable*, черти?). С Iterable, к сожалению, сложилась такая ситуация, когда параллельно официально закреплённым гарантиям есть неформальное соглашение, и именно с неформальной частью люди сталкиваются намного чаще.

                                              0
                                              Есть немалая категория кода, которая ожидает, что из Iterable можно получить сколько угодно итераторов.

                                              Не вижу проблемы. Значит, такому коду не надо передавать Stream и всё.
                                              В любом случае, наследование Stream от Iterable принесёт во много раз больше пользы, чем вреда.
                                              Кстати, если вы почитаете javadoc к Iterable, то увидите, что там нигде не написано, что iterator() можно вызывать сколько угодно раз. Так что те, кто завязался на то, что обойти Iterable можно много раз – сами себе злобные Буратины.
                                                0
                                                наследование Stream от Iterable принесёт во много раз больше пользы, чем вреда.

                                                Всё-таки, основное назначение Stream — это внутренняя обработка данных в лямбдах, которая из-за этого хорошо параллелится, и ленивость обработки в целом, когда не надо материализовывать больше, чем запросили. И если ленивость ещё как-то ложится на этот forEach, то вот способности параллелиться и прочему наследование от Iterable строго ортогонально. Иными словами — я в этом пользы не вижу, только увеличение семантической нагрузки, звоночком о чём является наше с вами копание в javadoc, к примеру. Значительно больше пользы погут принести остальные JEP. Вот к примеру разработка человеческого лица для Panama принесёт куда больше пользы. Вдвойне больше пользы принесёт возможность JIT рекомпилировать блоки кода так, чтобы они использовали Panama для ускорения числодробилок, даже если явных вызовов изначально в коде нет. И так далее, и тому подобное.

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

                                Самое читаемое