API, ради которых наконец-то стоит обновиться с Java 8. Часть 3 |
||
|
МЕНЮ Главная страница Поиск Регистрация на сайте Помощь проекту Архив новостей ТЕМЫ Новости ИИ Голосовой помощник Разработка ИИГородские сумасшедшие ИИ в медицине ИИ проекты Искусственные нейросети Искусственный интеллект Слежка за людьми Угроза ИИ ИИ теория Внедрение ИИКомпьютерные науки Машинное обуч. (Ошибки) Машинное обучение Машинный перевод Нейронные сети начинающим Психология ИИ Реализация ИИ Реализация нейросетей Создание беспилотных авто Трезво про ИИ Философия ИИ Big data Работа разума и сознаниеМодель мозгаРобототехника, БПЛАТрансгуманизмОбработка текстаТеория эволюцииДополненная реальностьЖелезоКиберугрозыНаучный мирИТ индустрияРазработка ПОТеория информацииМатематикаЦифровая экономика
Генетические алгоритмы Капсульные нейросети Основы нейронных сетей Распознавание лиц Распознавание образов Распознавание речи Творчество ИИ Техническое зрение Чат-боты Авторизация |
2021-04-26 13:55 Какие есть причины переходить на новые версии Java? Кто-то это сделает из-за новых языковых возможностей вроде В предыдущих двух частях мы уже рассмотрели по 10 новых API, которые появились в Java 9 и более поздних версиях (часть 1, часть 2). Сегодня мы рассмотрим ещё 10. 1. Stream.toList() Появился в: Java 16 Для какой задачи чаще всего используется List<T> targetList = sourceList .stream() // промежуточные операции .collect(Collectors.toList()); Нельзя сказать, что List<T> targetList = sourceList .stream() // промежуточные операции .toList(); Есть ли какая-то разница между Однако Давайте напишем несколько бенчмарков. Для начала рассмотрим самый простейший случай: замерим, как быстро создаётся копия исходного списка, т.е. проверим цепочку вообще без промежуточных операций. Так как для такого сценария Полный код JMH-бенчмарка import org.openjdk.jmh.annotations.*; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import java.util.stream.IntStream; @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS) @State(Scope.Thread) public class ToList { @Param({"10", "100", "1000"}) private int size; private List<Integer> sourceList; @Setup public void setup() { sourceList = IntStream .range(0, size) .boxed() .collect(Collectors.toList()); } @Benchmark public List<Integer> newArrayList() { return new ArrayList<>(sourceList); } @Benchmark public List<Integer> toList() { return sourceList.stream().toList(); } @Benchmark public List<Integer> copyOf() { return List.copyOf(sourceList); } @Benchmark public List<Integer> collectToList() { return sourceList.stream().collect(Collectors.toList()); } @Benchmark public List<Integer> collectToUnmodifiableList() { return sourceList.stream().collect(Collectors.toUnmodifiableList()); } }Детали запуска бенчмарка OpenJDK 64-Bit Server VM (build 16+36-2231, mixed mode, sharing) Intel Core i5-9500 3.00GHZ Опции запуска: -f 3 -wi 3 -w 5 -i 5 -r 5 -t 6 -jvmArgs -XX:+UseParallelGC ![]() Результаты говорят нам о том, что Теперь рассмотрим случай, когда точный размер неизвестен. Например, добавим одну промежуточную операцию Полный код JMH-бенчмарка import org.openjdk.jmh.annotations.*; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import java.util.stream.IntStream; @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS) @State(Scope.Thread) public class ToListFilter { @Param({"10", "100", "1000"}) private int size; private List<Integer> sourceList; @Setup public void setup() { sourceList = IntStream .range(0, size) .boxed() .collect(Collectors.toList()); } @Benchmark public List<Integer> toList() { return sourceList.stream().filter(i -> i % 2 == 0).toList(); } @Benchmark public List<Integer> newArrayList() { var list = new ArrayList<Integer>(); for (var i : sourceList) { if (i % 2 == 0) { list.add(i); } } return list; } @Benchmark public List<Integer> collectToList() { return sourceList.stream().filter(i -> i % 2 == 0).collect(Collectors.toList()); } @Benchmark public List<Integer> collectToUnmodifiableList() { return sourceList.stream().filter(i -> i % 2 == 0).collect(Collectors.toUnmodifiableList()); } }Детали запуска бенчмарка OpenJDK 64-Bit Server VM (build 16+36-2231, mixed mode, sharing) Intel Core i5-9500 3.00GHZ Опции запуска: -f 3 -wi 3 -w 5 -i 5 -r 5 -t 6 -jvmArgs -XX:+UseParallelGC ![]() В этом случае мы тоже получили большое ускорение! И в этот раз ![]() Также Вывод: 2. String: formatted(), stripIndent() и translateEscapes() Появились в: Java 15 В Java 15 появились блоки текста – строковые литералы, которые могут состоять из одной или нескольких линий: String str = """ Привет, Юзер!"""; При этом довольно часто блоки будут использоваться в качестве шаблонов с последующей заменой: String str = String.format(""" Привет, %s!""", user);Не кажется ли вам, что код выше выглядит несколько громоздким? Мне вот тоже кажется. Но у нас есть способ, как сделать его немножко чище. Это новый метод String str = """ Привет, %s!""".formatted(user); Вы, конечно, скажете, что разница лишь в трёх символах сэкономленного кода, но, во-первых, второй вариант гораздо проще печатать на клавиатуре (проверьте сами), а во-вторых, он более читабелен. Кстати, String str = "Привет, %s!".formatted(user); Лично мне метод очень нравится, и я планирую взять его на вооружение в качестве основного рабочего варианта. Второй метод, появившийся в Java 15 – это Привет, Юзер! Тогда чтобы убрать пробелы слева, как раз и можно воспользоваться String str = Files.readString(Path.of("hello.txt")).stripIndent(); System.out.println(str);Вывод: Привет, Юзер! Наконец, третий метод – это Например, есть файл hello.txt: Привет, Юзер! String str = Files.readString(Path.of("hello.txt")).translateEscapes(); System.out.println(str);Вывод: Привет, Юзер! 3. CharSequence.isEmpty(), CharSequence.compare() и StringBuilder.compareTo() Появились в: Java 15 / Java 11 Если уж мы начали тему строк, то давайте добьём её до конца. Тот, кто писал на Java 1.5 или на более старых версиях, должен помнить, что в классе if (str.length() != 0) { ... }Это было не совсем удобно, и в Java 1.6 метод if (!str.isEmpty()) { ... }Однако про то, что if (stringBuilder.length() != 0) { ... }Но спустя 14 лет всё-таки решили исправить и это: начиная с Java 15, метод if (!stringBuilder.isEmpty()) { ... }Также иногда приходится тестировать два Однако, начиная с Java 11, всё это не нужно, потому что появился метод if (CharSequence.compare(charSeq1, charSeq2) == 0) { ... }Метод Также в Java 11 класс if (stringBuilder1.compareTo(stringBuilder2) == 0) { ... } 4. Collectors.filtering() и Collectors.flatMapping() Появились в: Java 9 Часто ли вам приходится использовать record Movie(String title, String genre, double rating) { }Допустим, вы хотите сгруппировать фильмы по жанру: Stream<Movie> allMovies = Stream.of( new Movie("Коммандо", "Боевик", 7.385), new Movie("Терминатор", "Боевик", 7.974), new Movie("Терминатор 2", "Боевик", 8.312), new Movie("Молчание ягнят", "Триллер", 8.33), new Movie("Криминальное чтиво", "Триллер", 8.619), new Movie("Титаник", "Мелодрама", 8.363), new Movie("Семьянин", "Комедия", 7.699) ); Map<String, List<Movie>> groups = allMovies.collect( Collectors.groupingBy(Movie::genre)); groups.forEach((genre, movies) -> { System.out.println(genre + ":"); movies.forEach(movie -> System.out.printf(" %s: %.2f%n", movie.title(), movie.rating())); });Вывод: Мелодрама: Титаник: 8.36 Боевик: Коммандо: 7.39 Терминатор: 7.97 Терминатор 2: 8.31 Триллер: Молчание ягнят: 8.33 Криминальное чтиво: 8.62 Комедия: Семьянин: 7.70 Однако, допустим, вы не хотите видеть все фильмы, а только те, у кого рейтинг выше 8. Какой метод вы в этом случае используете? Конечно же, Map<String, List<Movie>> groups = allMovies .filter(movie -> movie.rating() > 8) .collect(Collectors.groupingBy(Movie::genre)); Мелодрама: Титаник: 8.36 Боевик: Терминатор 2: 8.31 Триллер: Молчание ягнят: 8.33 Криминальное чтиво: 8.62 Но вот проблема: вам вдруг захотелось видеть все жанры, даже те, в которые не попало ни одного фильма с рейтингом выше 8. Что делать? Ответ: перейти на новую версию Java, потому что в ней есть Map<String, List<Movie>> groups = allMovies.collect( Collectors.groupingBy(Movie::genre, Collectors.filtering(movie -> movie.rating() > 8, Collectors.toList()))); groups.forEach((genre, movies) -> { System.out.println(genre + ":"); if (movies.isEmpty()) { System.out.println(" <Фильмов с рейтингом выше 8 нет>"); } else { movies.forEach(movie -> System.out.printf(" %s: %.2f%n", movie.title(), movie.rating())); } });В этом случае фильтрация будет перенесена внутрь Мелодрама: Титаник: 8.36 Боевик: Терминатор 2: 8.31 Триллер: Молчание ягнят: 8.33 Криминальное чтиво: 8.62 Комедия: <Фильмов с рейтингом выше 8 нет> Очень хорошо. Теперь добавим в фильмы актёров: record Movie(String title, String genre, double rating, List<String> actors) { }И теперь хотите увидеть всех актёров с группировкой по жанру: Stream<Movie> allMovies = Stream.of( new Movie("Коммандо", "Боевик", 7.385, List.of("Шварценеггер", "Чонг", "Хедайя")), new Movie("Терминатор", "Боевик", 7.974, List.of("Шварценеггер", "Бин", "Хэмилтон")), new Movie("Терминатор 2", "Боевик", 8.312, List.of("Шварценеггер", "Хэмилтон", "Ферлонг", "Патрик")), new Movie("Молчание ягнят", "Триллер", 8.33, List.of("Фостер", "Хопкинс")), new Movie("Криминальное чтиво", "Триллер", 8.619, List.of("Траволта", "Уиллис", "Джексон", "Турман")), new Movie("Титаник", "Мелодрама", 8.363, List.of("ДиКаприо", "Уинслет")), new Movie("Семьянин", "Комедия", 7.699, List.of("Кейдж", "Леони")) );Но какой коллектор нужно подсунуть в Map<String, Set<List<String>>> groups = allMovies.collect( Collectors.groupingBy(Movie::genre, Collectors.mapping(Movie::actors, Collectors.toSet()))); Но смотрите, у нас получилось множество списков, а нужно просто множество. Что же делать? И тут на помощь приходит Map<String, Set<String>> groups = allMovies.collect( Collectors.groupingBy(Movie::genre, Collectors.flatMapping(movie -> movie.actors().stream(), Collectors.toSet()))); И вот сейчас тип правильный! Если вывести это, то получится: Мелодрама: ДиКаприо Уинслет Боевик: Бин Ферлонг Хедайя Патрик Шварценеггер Хэмилтон Чонг Триллер: Траволта Уиллис Хопкинс Фостер Джексон Турман Комедия: Кейдж Леони Что и требовалось. 5. StackWalker Появился в: Java 9 Приходилось ли вам иметь дело со стеками? Не со стеками в смысле структур данных, а со стеком потоков? Например, вы пишете простенький логгер: public final class MyLogger { public static void log(String message) { System.out.println(message); } }Однако вы хотите писать в консоль не просто голое сообщение, а ещё имя класса, метода, файла и номер строки, откуда вызывается метод public static void log(String message) { StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); StackTraceElement stackTraceElement = stackTrace[2]; String msg = stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName() + "(" + stackTraceElement.getFileName() + ":" + stackTraceElement.getLineNumber() + ") " + message; System.out.println(msg); }Можно предположить, что такой способ получения получения номеров строк является довольно дорогим. Ведь нам надо заполнить полностью весь стек, который может быть очень глубоким (особенно в энтерпрайзе, где фреймворк на фреймворке), а потом ещё и сконвертировать внутренние структуры JVM в Java-массив. И всё это ради того, чтобы отбросить его почти полностью и достать только второй элемент. Давайте замерим производительность такого подхода: @Benchmark public String stackTrace() { StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); StackTraceElement stackTraceElement = stackTrace[2]; return stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName() + "(" + stackTraceElement.getFileName() + ":" + stackTraceElement.getLineNumber() + ")"; }Детали запуска бенчмарка OpenJDK 64-Bit Server VM (build 16+36-2231, mixed mode, sharing) Intel Core i5-9500 3.00GHZ Опции запуска: -f 1 -wi 3 -w 3 -i 5 -r 5 -t 6 Benchmark Mode Cnt Score Error Units Stack.stackTrace avgt 5 103,704 ? 1,123 us/op 104 микросекунды на каждый вызов! Это невероятно медленно! Есть ли возможность это ускорить? Есть: с помощью нового класса
После того, как мы получил объект
Для нашего логгера мы воспользуемся третьим вариантом. Вот как будет выглядеть реализация метода public static void log(String message) { String msg = StackWalker .getInstance() .walk((Stream<StackFrame> frames) -> { StackFrame frame = frames.skip(2).findFirst().get(); return frame.getClassName() + "." + frame.getMethodName() + "(" + frame.getFileName() + ":" + frame.getLineNumber() + ") " + message; }); System.out.println(msg); }Теперь давайте замерим производительность варианта со @Benchmark public String stackWalker() { return StackWalker .getInstance() .walk(frames -> { StackFrame frame = frames.skip(2).findFirst().get(); return frame.getClassName() + "." + frame.getMethodName() + "(" + frame.getFileName() + ":" + frame.getLineNumber() + ")"; }); }Детали запуска бенчмарка OpenJDK 64-Bit Server VM (build 16+36-2231, mixed mode, sharing) Intel Core i5-9500 3.00GHZ Опции запуска: -f 1 -wi 3 -w 3 -i 5 -r 5 -t 6 Benchmark Mode Cnt Score Error Units Stack.stackTrace avgt 5 103,704 ? 1,123 us/op Stack.stackWalker avgt 5 2,781 ? 0,156 us/op Скорость выросла в 37 раз! Это огромный выигрыш. Конечно, 2.8 микросекунды это всё ещё далеко не бесплатно, но такой вариант кажется уже вполне приемлемым, чтобы включить его в боевом приложении. Так как метод package org.mylogger; public final class MyLogger { public enum Level { ERROR, WARN, INFO } public static void error(String message) { log(Level.ERROR, message); } public static void warn(String message) { log(Level.WARN, message); } public static void info(String message) { log(Level.INFO, message); } public static void log(Level level, String message) { ... } }Сейчас мы уже не можем использовать конструкцию public static void log(Level level, String message) { String msg = StackWalker .getInstance() .walk((Stream<StackFrame> frames) -> { StackFrame frame = frames .dropWhile(f -> f.getClassName().startsWith("org.mylogger")) .findFirst() .get(); return level + " " + frame.getClassName() + "." + frame.getMethodName() + "(" + frame.getFileName() + ":" + frame.getLineNumber() + ") " + message; }); System.out.println(msg); }Какие ещё применения есть у Как вы знаете, в Java 9 появились модули. Но мало кто использует их в своих проектах, и подавляющее большинство всё ещё препочитает класть всё в classpath. Но тогда у нас теряется весьма ценная возможность – экспортировать из модуля часть пакетов, а остальные скрывать. Представим, что у нас есть пакет package org.example.mylib.internal; public final class Handler { public static void handle() { ... } }Класс package org.example.mylib.internal; public final class Handler { public static void handle() { if (!StackWalker .getInstance(Option.RETAIN_CLASS_REFERENCE) .getCallerClass() .getPackageName() .startsWith("org.example.mylib.")) { throw new RuntimeException("Security error"); } ... } }Здесь мы использовали опцию 6. System.Logger Появился в: Java 9 Если уж мы начали говорить про логирование, то нельзя не рассказать про новое стандартное API для логирования, которое появилось в Java 9. Это API очень маленькое и состоит всего из трёх классов: интерфейса Использовать public final class Main { private static final Logger LOGGER = System.getLogger(""); public static void main(String[] args) { LOGGER.log(Level.ERROR, "Critical error!"); } }Вывод: апр. 17, 2021 6:24:57 PM org.example.Main main SEVERE: Critical error!
При этом нам ничего не мешает для SLF4J использовать другой бэкенд, например, Log4j или <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.14.1</version> <!-- Последняя версия на момент написания статьи --> <scope>runtime</scope> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.14.1</version> <scope>runtime</scope> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-jpl</artifactId> <version>2.14.1</version> <scope>runtime</scope> </dependency> При этом саму программу изменять не потребуется: то, что 18:24:57.941 [main] ERROR - Critical error! После этого модуль К сожалению, адаптера Несколько примеров использования LOGGER.log(Level.INFO, "Information"); LOGGER.log(Level.DEBUG, "Sum of {} and {} is {}:", 2, 3, 2+3); LOGGER.log(Level.TRACE, () -> "Lazy message"); LOGGER.log(Level.ERROR, "Log exception", new Exception()); 7. Lookup.defineHiddenClass() Появился в: Java 15 В прошлый раз мы рассказывали про метод Скрытые классы создаются с помощью нового метода Скрытые классы имеют следующие особенности:
Кстати, вы уже должны быть хорошо знакомы со скрытыми классами, поскольку используете их каждый день. Это лямбды: jshell> Runnable runnable = () -> {} runnable ==> $Lambda$26/0x0000000800c0aa00@443b7951 jshell> runnable.getClass().isHidden() $2 ==> trueДавайте создадим небольшой примерчик и определим свой скрытый класс «с нуля». Пусть он для простоты складывает два Для начала нужно создать представление класса в байт-коде в виде массива байтов: byte[] bytes = new ByteBuddy() .subclass(Object.class) .name("org.example.Temp") .defineMethod("sum", int.class, Modifier.PUBLIC) .withParameters(int.class, int.class) .intercept(new Implementation.Simple( MethodVariableAccess.INTEGER.loadFrom(1), MethodVariableAccess.INTEGER.loadFrom(2), Addition.INTEGER, MethodReturn.INTEGER)) .make() .getBytes();По сути мы скомпилировали вот такой класс, но сделали это в рантайме непосредственно через манипуляции с байт-кодом: package org.example; public class Temp { public int sum(int x, int y) { return x + y; } }Теперь, когда у нас есть байт-код класса, можно его загружать и что-то с ним делать: Lookup lookup = MethodHandles .lookup() .defineHiddenClass(bytes, false); // Для разнообразия будем использовать MethodHandle вместо reflection Object obj = lookup .findConstructor(lookup.lookupClass(), MethodType.methodType(void.class)) .invoke(); MethodHandle sumHandle = lookup.findVirtual(lookup.lookupClass(), "sum", MethodType.methodType(int.class, int.class, int.class)); // Вызовем метод sum. Должен напечатать 5 System.out.println(sumHandle.invoke(obj, 3, 2)); Вот и всё. Кстати, так как скрытые классы необнаружимы, то загружать один и тот же класс можно сколько угодно раз. По сути каждый раз будет определяться новый уникальный скрытый класс: Lookup lookup1 = MethodHandles.lookup().defineHiddenClass(bytes, false); Lookup lookup2 = MethodHandles.lookup().defineHiddenClass(bytes, false); Lookup lookup3 = MethodHandles.lookup().defineHiddenClass(bytes, false); System.out.println(lookup1.lookupClass()); // class org.example.Temp/0x0000000800cb4000 System.out.println(lookup2.lookupClass()); // class org.example.Temp/0x0000000800cb4400 System.out.println(lookup3.lookupClass()); // class org.example.Temp/0x0000000800cb4800 8. Новые методы в Math Появились в: Java 9 / Java 15 Наверное, практически все, кто начинал работать с большими числами в Java, совершал вот такую ошибку: int x = ... int y = ... long z = x * y; Это один из тех примеров в Java, когда можно угодить в ловушку даже на простом умножении: произведение двух long z = (long) x * y; В то время как такое решение абсолютно рабочее, у меня в моём перфекционистском подсознании остаётся какой-то мелкий осадок. Во-первых, мне не нравится этот явный каст, который применяется то ли к первому множителю, то ли ко всему произведению. Во-вторых, не нравится эта асимметрия, что одна из переменных кастуется, а другая нет. В общем, я хочу кристальную ясность и отсутствие магии. Способ написать вот такое: Возьми два int и перемножь их в long с учётом переполнения И с Java 9 такой способ есть. Это метод long z = Math.multiplyFull(x, y); Вообще обработка переполнений в Java реализуется довольно муторно, и чтобы облегчить жизнь программистам, в Java 8 появилась целая пачка методов для этого в классе
Все эти методы выбрасывают ошибку в случае переполнения, что во многих случаях лучше, чем просто тихое переполнение – лучше уж упадёт сразу, чем где-нибудь позже с совсем другой ошибкой. Но все ли возможные случаи тут покрыты? Похоже, что нет. Например, я не вижу модуль, который хоть и в очень редком случае, но всё же может переполниться: jshell> Math.abs(Integer.MIN_VALUE) $1 ==> -2147483648 Как же так, ведь модуль – это положительное число? Это так, но дело в том, что 2147483648 просто не влезает в jshell> Math.absExact(Integer.MIN_VALUE) | Exception java.lang.ArithmeticException: Overflow to represent absolute value of Integer.MIN_VALUE | at Math.absExact (Math.java:1392) | at (#1:1) jshell> Math.absExact(Long.MIN_VALUE) | Exception java.lang.ArithmeticException: Overflow to represent absolute value of Long.MIN_VALUE | at Math.absExact (Math.java:1438) | at (#2:1) А знаете ли вы, сколько будет, если найти целое от деления -11 на 3? А остаток? Давайте проверим: jshell> -11 / 3 $1 ==> -3 jshell> -11 % 3 $2 ==> -2 Ну вроде бы логично, ведь >>> -11 / 3 -4 >>> -11 % 3 1 И этот результат тоже по-своему верный: jshell> Math.floorDiv(-11, 3) $1 ==> -4 jshell> Math.floorMod(-11, 3) $2 ==> 1 Также для совсем упоротых математиков в Java 9 появились два метода jshell> Math.fma(2.99, 5.91, 7.1) $1 ==> 24.7709 jshell> 2.99 * 5.91 + 7.1 $2 ==> 24.770900000000005 9. Аннотация java.io.Serial Появилась в: Java 14 Используете ли вы стандартную сериализацию в Java? Этот механизм далеко не идеальный и со своими недостатками, но иногда он может быть очень удобным, потому что позволяет из коробки очень просто сделать сереализацию и десериализацию Java-объектов. Рассмотрим пример: public class Point { private static final long serialVersionUID = 1L; public int x; public int y; }Чтобы сконвертировать объект var point = new Point(); point.x = 1; point.y = 2; var baos = new ByteArrayOutputStream(); try (var oos = new ObjectOutputStream(baos)) { oos.writeObject(point); } byte[] bytes = baos.toByteArray();Очень удобно. Кстати, вы заметили ошибку в моём коде? Конечно же, я забыл реализовать интерфейс Правильный код будет таким: public class Point implements Serializable { private static final long serialVersionUID = 1; public int x; public int y; }И вот это как раз и есть одна из главных проблем сериализации – при её использовании можно очень легко допустить ошибку: забыть Этой аннотацией теперь рекомендуется помечать все поля и методы, относящиеся к механизму сериализации: public class Point implements Serializable { @Serial private static final long serialVersionUID = 1; ... }Теперь, если будет допущена ошибка, то появится предупреждение: public class Point { @Serial // Annotated member is not a part of the serialization mechanism private static final long serialVersionUID = 1; ... }Или: public class Point implements Serializable { @Serial // Annotated member is not a part of the serialization mechanism private static final int serialVersionUID = 1; ... }Аннотация будет делать проверки на всех полях и методах, которые относятся к сериализации: К сожалению, на текущий момент предупреждения хорошо работают только в IntelliJ IDEA. В компиляторе JDK 16 проверки выполняются только с включённым флагом > javac -Xlint:serial Point.java Point.java:6: warning: [serial] serialVersionUID must be of type long in class Point private static final int serialVersionUID = 1; ^ Возможно, это исправят в Java 17. 10. Методы Objects: checkIndex(), checkFromIndexSize(), checkFromToIndex() Появились в: Java 9 / Java 16 Завершим нашу статью несколькими полезными методами для проверки индексов. Иногда приходится писать функции, принимающие в качестве входных параметров индексы или диапазоны индексов, и чтобы начать использовать эти индексы, нужно сначала убедиться, что они не выходят за границы. То есть приходится писать подобные проверки в начале методов: private static void getAt(int index, int length) { if (index < 0) { throw new IllegalArgumentException("index < 0"); } if (index >= length) { throw new IllegalArgumentException("index >= length"); } ... }Если подобных функций в проекте становится уже несколько, то чтобы не повторяться, такие проверки удобнее вынести в отдельные утилитные методы: public final class PreconditionUtils { public static void checkIndex(int index, int length) { if (index < 0) { throw new IllegalArgumentException("index < 0"); } if (index >= length) { throw new IllegalArgumentException("index >= length"); } } }Но с Java 9 теперь это больше не нужно, потому что в классе Метод jshell> Objects.checkIndex(-3, 10) | Exception java.lang.IndexOutOfBoundsException: Index -3 out of bounds for length 10 | at Preconditions.outOfBounds (Preconditions.java:64) | at Preconditions.outOfBoundsCheckIndex (Preconditions.java:70) | at Preconditions.checkIndex (Preconditions.java:248) | at Objects.checkIndex (Objects.java:372) | at (#1:1) jshell> Objects.checkIndex(10, 10) | Exception java.lang.IndexOutOfBoundsException: Index 10 out of bounds for length 10 | at Preconditions.outOfBounds (Preconditions.java:64) | at Preconditions.outOfBoundsCheckIndex (Preconditions.java:70) | at Preconditions.checkIndex (Preconditions.java:248) | at Objects.checkIndex (Objects.java:372) | at (#2:1) Метод jshell> Objects.checkFromIndexSize(3, 8, 10) | Exception java.lang.IndexOutOfBoundsException: Range [3, 3 + 8) out of bounds for length 10 | at Preconditions.outOfBounds (Preconditions.java:64) | at Preconditions.outOfBoundsCheckFromIndexSize (Preconditions.java:82) | at Preconditions.checkFromIndexSize (Preconditions.java:343) | at Objects.checkFromIndexSize (Objects.java:424) | at (#3:1) jshell> Objects.checkFromIndexSize(-2, 8, 10) | Exception java.lang.IndexOutOfBoundsException: Range [-2, -2 + 8) out of bounds for length 10 | at Preconditions.outOfBounds (Preconditions.java:64) | at Preconditions.outOfBoundsCheckFromIndexSize (Preconditions.java:82) | at Preconditions.checkFromIndexSize (Preconditions.java:343) | at Objects.checkFromIndexSize (Objects.java:424) | at (#4:1) jshell> Objects.checkFromIndexSize(3, -4, 10) | Exception java.lang.IndexOutOfBoundsException: Range [3, 3 + -4) out of bounds for length 10 | at Preconditions.outOfBounds (Preconditions.java:64) | at Preconditions.outOfBoundsCheckFromIndexSize (Preconditions.java:82) | at Preconditions.checkFromIndexSize (Preconditions.java:343) | at Objects.checkFromIndexSize (Objects.java:424) | at (#5:1) Наконец, метод jshell> Objects.checkFromToIndex(3, 11, 10) | Exception java.lang.IndexOutOfBoundsException: Range [3, 11) out of bounds for length 10 | at Preconditions.outOfBounds (Preconditions.java:64) | at Preconditions.outOfBoundsCheckFromToIndex (Preconditions.java:76) | at Preconditions.checkFromToIndex (Preconditions.java:295) | at Objects.checkFromToIndex (Objects.java:398) | at (#6:1) jshell> Objects.checkFromToIndex(-4, 8, 10) | Exception java.lang.IndexOutOfBoundsException: Range [-4, 8) out of bounds for length 10 | at Preconditions.outOfBounds (Preconditions.java:64) | at Preconditions.outOfBoundsCheckFromToIndex (Preconditions.java:76) | at Preconditions.checkFromToIndex (Preconditions.java:295) | at Objects.checkFromToIndex (Objects.java:398) | at (#7:1) jshell> Objects.checkFromToIndex(6, 4, 10) | Exception java.lang.IndexOutOfBoundsException: Range [6, 4) out of bounds for length 10 | at Preconditions.outOfBounds (Preconditions.java:64) | at Preconditions.outOfBoundsCheckFromToIndex (Preconditions.java:76) | at Preconditions.checkFromToIndex (Preconditions.java:295) | at Objects.checkFromToIndex (Objects.java:398) | at (#8:1) Кроме того, в Java 16 появились перегрузки этих функций для
Заключение Сегодня я рассказал про 10 интересных API, некоторые из которых появились в буквально только что вышедшей Java 16, а некоторые уже присутствуют довольно давно ещё с 9-й версии. Надеюсь, что после прочтения данной статьи вы стали более заинтересованными в миграции на последнюю версию Java. Помните, что в новых версиях Java появляются не только новые возможности, но и изменения, ломающие обратную совместимость (1, 2, 3, 4, 5, 6, 7, 8). И чем больше вы тянете с переходом с Java 8 на последнюю версию, тем сложнее вам будет осуществить этот переход. Продолжение следует… Источник: m.vk.com Комментарии: |
|