Обработка строк
Два новых метода могут быть использованы для работы со строками: join и chars. Первый метод использует указанный разделитель для объединения любого количества строк в одну строку.
String.join(":", "foobar", "foo", "bar");
// => foobar:foo:bar
Второй метод, chars, создаёт поток данных из всех символов строки, так что вы можете использовать потоковые операции на этих символах.
"foobar:foo:bar"
.chars()
.distinct()
.mapToObj(c -> String.valueOf((char)c))
.sorted()
.collect(Collectors.joining());
// => :abfor
Не только строки, но и регулярные выражения также могут извлечь выгоду из потоков данных. Мы можем разделить любое регулярное выражение и создать поток данных для их обработки, вместо того чтобы разбивать строку на отдельные символы потока, как показано ниже:
Pattern.compile(":")
.splitAsStream("foobar:foo:bar")
.filter(s -> s.contains("bar"))
.sorted()
.collect(Collectors.joining(":"));
// => bar:foobar
Кроме того, регулярные выражения можно преобразовать в предикаты. Эти предикаты можно использовать для фильтрации потока строк следующим образом:
Pattern pattern = Pattern.compile(".*@gmail\\.com");
Stream.of("bob@gmail.com", "alice@hotmail.com")
.filter(pattern.asPredicate())
.count();
// => 1
Приведённое выше регулярное выражение принимает любую строку, заканчивающуюся на @gmail.com, и затем используется как предикат Java8 для фильтрации потока электронных адресов.
Обработка чисел
Java8 добавляет дополнительную поддержку для беззнаковых чисел. Числа в Java всегда являются знаковыми, например, давайте рассмотрим Integer:
int может представлять максимум 2^32 числа. Числа по умолчанию являются знаковыми в Java, поэтому последний двоичный разряд представляет знак (0 для положительных чисел, 1 для отрицательных). Таким образом, начиная с десятичного 0, максимальное положительное целое число со знаком равно 2^31 - 1.
Вы можете получить доступ к нему через Integer.MAX_VALUE:
System.out.println(Integer.MAX_VALUE); // 2147483647
System.out.println(Integer.MAX_VALUE + 1); // -2147483648
Java8 добавляет поддержку анализа беззнаковых целых чисел, давайте посмотрим, как это работает:
long maxUnsignedInt = (1l << 32) - 1;
String string = String.valueOf(maxUnsignedInt);
int unsignedInt = Integer.parseUnsignedInt(string, 10);
String string2 = Integer.toUnsignedString(unsignedInt, 10);
Как вы видите, теперь можно анализировать максимальное беззнаковое число 2^32 - 1 как целое число. Кроме того, вы можете преобразовать это числовое значение обратно в строковое представление беззнакового числа.
Это было невозможно сделать ранее с помощью parseInt, как показывает этот пример:
try {
Integer.parseInt(string, 10);
}
catch (NumberFormatException e) {
System.err.println("could not parse signed int of " + maxUnsignedInt);
}
Это число не может быть проанализировано как знаковое целое число, поскольку оно превышает максимальный диапазон 2^31 - 1.
Арифметические операции
Math tool class добавил несколько методов для обработки числовых переполнений. Что это значит? Мы уже видели, что у всех типов чисел есть максимальное значение. Так что же произойдёт, если результат арифметической операции не может быть представлен в этом диапазоне?
System.out.println(Integer.MAX_VALUE); // 2147483647
System.out.println(Integer.MAX_VALUE + 1); // -2147483648
Как видно, произошло переполнение целого числа, чего мы обычно не хотим видеть.
Java8 добавила поддержку строгих математических операций для решения этой проблемы. Math расширил некоторые методы, все они заканчиваются на exact, такие как addExact. Когда результат операции не может быть представлен типом числа, эти методы обрабатывают переполнение, выбрасывая исключение ArithmeticException.
try {
Math.addExact(Integer.MAX_VALUE, 1);
}
catch (ArithmeticException e) {
System.err.println(e.getMessage());
// => integer overflow
}
Аналогичное исключение может быть выброшено при попытке преобразовать длинное целое число в целое число с помощью toIntExact:
try {
Math.toIntExact(Long.MAX_VALUE);
}
catch (ArithmeticException e) {
System.err.println(e.getMessage());
// => integer overflow
}
Работа с файлами
Files tool class был впервые представлен в Java7 как часть NIO. JDK8 API добавил несколько дополнительных методов, которые позволяют использовать файлы в потоках функций. Давайте более подробно рассмотрим некоторые примеры кода.
Метод Files.list преобразует пути указанного каталога в поток, который можно легко использовать с такими операциями потока, как filter и sorted.
try (Stream<Path> stream = Files.list(Paths.get(""))) {
String joined = stream
.map(String::valueOf)
.filter(path -> !path.startsWith("."))
.sorted()
.collect(Collectors.joining("; "));
System.out.println("List: " + joined);
}
В приведённом выше примере перечислены все файлы текущего рабочего каталога, а затем каждый путь отображается в виде строки. Затем результаты фильтруются, сортируются и, наконец, объединяются в одну строку. Если вы ещё не знакомы с функциональными потоками, вам следует прочитать мой учебник по потокам Java8 (ch2.md).
Возможно, вы уже заметили, что поток данных заключён в оператор try-with. Потоки данных реализуют AutoCloseable, и здесь нам нужно явно закрыть поток данных, потому что он основан на операциях ввода-вывода.
Возвращаемый поток данных является обёрткой DirectoryStream. Если вам необходимо своевременно обрабатывать файловые ресурсы, то следует использовать структуру try-with, чтобы гарантировать, что метод close потока данных вызывается после завершения потоковых операций. Поиск файлов
Метод find принимает три параметра:
Мы можем использовать метод Files.walk для выполнения того же действия. Этот метод перебирает каждый файл без необходимости передавать предикат поиска.
Path start = Paths.get("");
int maxDepth = 5;
try (Stream<Path> stream = Files.walk(start, maxDepth)) {
String joined = stream
.map(String::valueOf)
.filter(path -> path.endsWith(".js"))
.sorted()
.collect(Collectors.joining("; "));
System.out.println("walk(): " + joined);
}
В этом примере мы используем потоковую операцию filter для выполнения той же операции, что и в предыдущем примере.
Чтение и запись файлов
Чтение текстового файла в память и запись строки в текстовый файл в Java 8 — простая задача. Больше не нужно возиться с ридерами. Метод Files.readAllLines считывает все строки из указанного файла в список строк. Вы можете легко изменить этот список и записать его в другой файл с помощью метода Files.write:
List<String> lines = Files.readAllLines(Paths.get("res/nashorn1.js"));
lines.add("print('foobar');");
Files.write(Paths.get("res/nashorn1-modified.js"), lines);
Следует отметить, что эти методы не очень эффективны с точки зрения памяти, поскольку весь файл считывается в память. Чем больше файл, тем больше используется область кучи.
Вы можете использовать метод Files.lines в качестве более эффективного с точки зрения использования памяти решения. Этот метод считывает каждую строку и использует функциональный поток данных для её потоковой обработки, а не считывает сразу все строки в память.
try (Stream<String> stream = Files.lines(Paths.get("res/nashorn1.js"))) {
stream
.filter(line -> line.contains("print"))
.map(String::trim)
.forEach(System.out::println);
}
Если вам нужен более точный контроль, вы можете создать новый BufferedReader вместо этого:
Path path = Paths.get("res/nashorn1.js");
try (BufferedReader reader = Files.newBufferedReader(path)) {
System.out.println(reader.readLine());
}
Или, если вам нужно записать в файл, просто создайте новый BufferedWriter:
Path path = Paths.get("res/output.js");
try (BufferedWriter writer = Files.newBufferedWriter(path)) {
writer.write("print('Hello World');");
}
BufferedReader также может получить доступ к функциональному потоку данных. Метод lines создаёт поток данных над всеми строками:
Path path = Paths.get("res/nashorn1.js");
try (BufferedReader reader = Files.newBufferedReader(path)) {
long countPrints = reader
.lines()
.filter(line -> line.contains("print"))
.count();
System.out.println(countPrints);
}
На данный момент вы видите, что Java8 предоставляет три простых метода для чтения каждой строки текстового файла, делая обработку файлов более удобной.
К сожалению, вам необходимо явно использовать оператор try-with для закрытия файлового потока, что делает примеры кода несколько запутанными. Я ожидаю, что функциональные потоки данных смогут автоматически закрываться при вызове таких методов, как count и collect, потому что вы не можете вызвать завершающий метод дважды для одного и того же потока данных.
Надеюсь, вам понравится эта статья. Все примеры кода размещены на Github, а также есть множество фрагментов кода из других моих статей о Java8. Если эта статья была вам полезна, пожалуйста, добавьте мой репозиторий в избранное и подпишитесь на меня в Twitter.
Продолжайте программировать!
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )