Серия «Основы Java» — через Java8

Java

Хотя Java8 выпустил очень долгое время, и в Java8 есть много функций, которые могут улучшить эффективность и коды безопасности, но большинство программистов Java не пересекали Java8 этого хребта, BenjaminЯ думаю, что это вводное руководство по Java8, написанное в 2014 году, очень хорошо, возможно, оно поможет вам преодолеть барьер Java8.


Этот учебник поможет вам шаг за шагом изучить новые функции Java8. По порядку эта статья включает следующее: Интерфейсыdefaultметод,lambdaВыражения, ссылки на методы, многократно используемые аннотации и некоторые обновления API.streams, функциональный интерфейс,mapрасширения и новыеDateAPI.

В этой статье не большой кусок текста, только фрагмент с комментариями, надеюсь вам понравится!

метод интерфейса по умолчанию

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

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

В приведенном выше примереFormualИнтерфейс определяетdefaultметодsqrt, класс реализации интерфейса должен реализовать толькоcalculateметод,sqrtМетод работает из коробки.

Formula formula = new Formula() {
    @Override
    public double calculate(int a) {
        return sqrt(a * 100);
    }
};

formula.calculate(100);     // 100.0
formula.sqrt(16);           // 4.0

Приведенный выше код анонимно реализует формальный интерфейс. Код довольно многословен, и для реализации функции sqrt(a * 100) потребовалось 6 строк кода. В следующем разделе вы можете пройтиJava8Функция элегантно дополняет эту функцию.

Лямбда выражение

Давайте посмотрим, как функция сортировки списка строк была реализована в предыдущих версиях Java:

List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");

Collections.sort(names, new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return b.compareTo(a);
    }
 });

Статический метод Collection.sort принимает список строк и список строк.ComparatorИспользуется для сравнения входящего списка строк. Обычной практикой является реализация анонимного компаратора и передача его в метод сортировки.

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

Collections.sort(names, (String a, String b) -> {
    return b.compareTo(a);
 });

Этот код уже намного короче, чем предыдущий анонимный метод, но этот код может быть еще короче:

Collections.sort(names, (String a, String b) -> b.compareTo(a));

Примечание: используйтеCollections.sort(names, (a,b)->b.compareTo(a));также может

Метод реализован одной строкой кода, опущенной{}иreturnключевые слова. Но можно и короче:

Collections.sort(names, (a, b) -> b.compareTo(a));

Компилятор Java может определить тип параметра на основе контекста, поэтому вы также можете опустить тип параметра. Давайте рассмотрим более сложные способы использования лямбда-выражений.

функциональный интерфейс

лямбда-выражения и как они соотносятся с системой типов Java? Каждому лямбда-выражению интерфейс присваивает тип, поэтому каждоефункциональный интерфейсоба объявляют хотя бы одинabstractметод. Типы параметров каждого лямбда-выражения должны соответствовать параметрам абстрактного метода. так какdefaultМетод, определяемый ключевым словом, не является абстрактным методом, и в интерфейс можно добавить любое количество методов по умолчанию.

Примечание. Каждая лямбда — это функциональный интерфейс, поэтому интерфейс, использующий @FunctionInterface, может иметь только один абстрактный метод.

Любой интерфейс, содержащий только один абстрактный метод, можно рассматривать как лямбда-выражение. Для того, чтобы интерфейс соответствовал требованиям, необходимо добавить@FunctionalInterface注解,如果加上注解接口中不止一个虚拟方法,编译器就会报错。 Следующий пример:

@FunctionalInterface
interface Converter<F, T> {
    T convert(F from);
 }
Converter<String, Integer> converter = (from) -> Integer.valueOf(from);
Integer converted = converter.convert("123");
System.out.println(converted);    // 123

но опустить@FunctionalInterfaceПосле этой аннотации код тоже работает нормально.

ссылка на метод

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

Converter<String, Integer> converter = Integer::valueOf;
Integer converted = converter.convert("123");
System.out.println(converted);   // 123

Java8 позволяет использовать::Ссылка для вызова статических методов и конструкторов. В приведенном выше коде показано, как ссылаться на статический метод. Таким же образом можно ссылаться на методы объекта:

class Something {
    String startsWith(String s) {
        return String.valueOf(s.charAt(0));
    }
}
Something something = new Something();
Converter<String, String> converter = something::startsWith;
String converted = converter.convert("Java");
System.out.println(converted);    // "J"

Примечание. На это ссылается System.out::println.printlnНе статический метод, потому что System.out является объектом

Давайте взглянем::как это работает в конструкторе. Сначала определите класс с другим конструкторомPerson:

class Person {
    String firstName;
    String lastName;

    Person() {}

    Person(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
 }

Затем определите интерфейс фабрики Person для создания новых объектов Person:

interface PersonFactory<P extends Person> {
    P create(String firstName, String lastName);
}

Нет необходимости вручную реализовывать фабрику, но завершить новое создание через ссылку конструктораPersonОбъект:

PersonFactory<Person> personFactory = Person::new;
Person person = personFactory.create("Peter", "Parker");

пройти черезPerson::newПрийти, чтобы получитьPersonСправочник по конструктору класса. Затем компилятор JavaPersonFactory::createпараметры для автоматического выбора соответствующего конструктора.

Примечание: лямбда, ссылка на метод и ссылка на конструктор генерируются экземпляром @FunctionalInterface. Интерфейс только с одним абстрактным методом по умолчанию является @FunctionalInterface, а интерфейс, аннотированный @FunctionalInterface, может иметь только один абстрактный метод.

Область доступа Lambda

По сравнению с анонимно реализованными объектами лямбда-выражения очень просты для доступа к внешним переменным. Лямбда-выражение может обращаться к конечным переменным, переменным-членам и статическим переменным за пределами локального.

доступ к локальным переменным

Лямбда-выражения могут обращаться к внешним локальным конечным переменным:

final int num = 1;
Converter<Integer, String> stringConverter =
        (from) -> String.valueOf(from + num);
stringConverter.convert(2);     // 3

В отличие от анонимного метода, переменную num нельзя определить как final, и следующий код также будет работать:

int num = 1;
Converter<Integer, String> stringConverter =
        (from) -> String.valueOf(from + num);

stringConverter.convert(2);     // 3

Однако переменная num будет неявно скомпилирована в final в процессе компиляции, и следующий код вызовет ошибку компиляции:

int num = 1;
Converter<Integer, String> stringConverter =
        (from) -> String.valueOf(from + num);
num = 3;

Значение num также не может быть изменено в лямбда-выражении.

Доступ к переменным-членам и статическим переменным

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

class Lambda4 {
    static int outerStaticNum;
    int outerNum;

    void testScopes() {
        Converter<Integer, String> stringConverter1 = (from) -> {
            outerNum = 23;
            return String.valueOf(from);
        };

        Converter<Integer, String> stringConverter2 = (from) -> {
            outerStaticNum = 72;
            return String.valueOf(from);
        };
    }
}

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

Доступ к методам интерфейса по умолчанию

Помните приведенный ранее пример с формулой? Интерфейс Formula определяет метод sqrt по умолчанию, к которому можно получить доступ в каждом экземпляре Formula (включая анонимно реализованные объекты). Но этот способ не работает в лямбда-выражениях.

Доступ к методам по умолчанию через лямбда-выражения недоступен, и следующий код не будет компилироваться:

Formula formula = (a) -> sqrt( a * 100);

Встроенный функциональный интерфейс

Java8 включает множество встроенных функциональных интерфейсов. Есть несколько широко используемых интерфейсов, таких как Comparator и Runnable. Эти существующие интерфейсы расширены с помощью @FunctionalInterface для поддержки лямбда-выражений.

Но в Java8 также есть несколько совершенно новых функциональных интерфейсов, упрощающих написание кода. Некоторые из них происходят изGoogle Guavaбиблиотека. Даже если вы уже знакомы с библиотекой, вам следует обратить пристальное внимание на то, как эти интерфейсы расширяются некоторыми полезными методами.

Predicates

Предикат — это логическая функция с одним аргументом. Этот интерфейс предоставляет множество функций по умолчанию для составления сложных логических операций (И, НЕ).

Predicate<String> predicate = (s) -> s.length() > 0;

predicate.test("foo");              // true
predicate.negate().test("foo");     // false

Predicate<Boolean> nonNull = Objects::nonNull;
Predicate<Boolean> isNull = Objects::isNull;

Predicate<String> isEmpty = String::isEmpty;
Predicate<String> isNotEmpty = isEmpty.negate();
Functions

Функция принимает аргумент и возвращает результат. Методы по умолчанию можно использовать в цепочках методов, состоящих из нескольких методов.

Function<String, Integer> toInteger = Integer::valueOf;
Function<String, String> backToString = toInteger.andThen(String::valueOf);

backToString.apply("123");     // "123"
Suppliers

Поставщик создает объект на основе заданных свойств класса, Поставщик не поддерживает входящие параметры.

Supplier<Person> personSupplier = Person::new;
personSupplier.get();   // new Person
Consumers

Потребительская серия предопределенных параметров процесса для обработки ввода.

Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.firstName);
greeter.accept(new Person("Luke", "Skywalker"));
Comparators

Компаратор — это интерфейс в более старых версиях Java, который часто использовался, Java8 добавил много методов по умолчанию в этот интерфейс.

Comparator<Person> comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName);

Person p1 = new Person("John", "Doe");
Person p2 = new Person("Alice", "Wonderland");

comparator.compare(p1, p2);             // > 0
comparator.reversed().compare(p1, p2);  // < 0
Optionals

Необязательный — это не функциональный интерфейс, но хороший способ убить исключение NullPointerException. Это следующий раздел, в котором основное внимание будет уделено его принципу, давайте посмотрим, как работает опцион.

Необязательный — это контейнер, содержащий значение, которое может быть или не быть нулевым. Учтите, что метод может возвращать ненулевое значение или ничего. В Java8 вы можете сделать так, чтобы он не возвращал null или возвращал необязательный объект.

Optional<String> optional = Optional.of("bam");

optional.isPresent();           // true
optional.get();                 // "bam"
optional.orElse("fallback");    // "bam"

optional.ifPresent((s) -> System.out.println(s.charAt(0)));     // "b"

Примечание: Эти встроенные функциональные интерфейсы аннотированы @FuncationalInterface, который является синтаксическим сахаром, обеспечивающим удобный способ для различных типов функциональных методов без переопределения на следующих этапах программирования Stream.Все интерфейсы разные, и эти встроенные интерфейсы также готовы для потокового программирования.

Streams

java.util.Stream представляет собой последовательность элементов, которые могут выполнять одну или несколько операций.StreamОперации могут быть промежуточными операциями или конечными операциями. Терминальные операции возвращают типизированные результаты. Промежуточная операция возвращает сам объект Stream, и вы можете продолжать вызывать другие цепочки методов в той же строке кода.

Объекты потока могут быть созданы из объектов java.util.collection. По сравнению с различными типами списков и наборов (отображениям в настоящее время не поддерживаются), поток может поддерживать серию и параллельные операции.

Давайте сначала рассмотрим операцию конкатенации, создав объект Stream из объекта List:

List<String> stringCollection = new ArrayList<>();
stringCollection.add("ddd2");
stringCollection.add("aaa2");
stringCollection.add("bbb1");
stringCollection.add("aaa1");
stringCollection.add("bbb3");
stringCollection.add("ccc");
stringCollection.add("bbb2");
stringCollection.add("ddd1");

Коллекции в Java 8 были расширены для создания объектов Stream с помощью Collection.stream() или Collection.parallelStream() Далее будут представлены наиболее часто используемые операции Stream.

Filter

FilterПринимает предикат для фильтрации всех элементов в потоке. Эта операция является промежуточной, и для отфильтрованного результата может быть вызвана другая операция Stream (например, forEach). ForEach принимает параметр Consumer и выполняет его для каждого отфильтрованного элемента Stream. ForEach — это терминальная операция, поэтому после этой операции нельзя вызывать другие потоковые операции.

stringCollection
    .stream()
    .filter((s) -> s.startsWith("a"))
    .forEach(System.out::println);
// "aaa2", "aaa1"

Примечание: каждый поток выполняетсяforEachДождавшись работы терминала, продолжить подключение нельзяfilterДождитесь промежуточной операции.

Sorted

SortedЭто промежуточная операция, которая возвращает STREAM строки. Эти элементы будут отсортированы в естественной последовательности, если вы не передаете пользовательский COMPARATOR.

stringCollection
    .stream()
    .sorted()
    .filter((s) -> s.startsWith("a"))
    .forEach(System.out::println);
// "aaa1", "aaa2"

Обратите внимание, что только конвекция внутри Sorted Сортирует элементы, а не менять порядок элементов в исходной коллекции, после выполнения операций Sorted, чтобы элементы stringCollection не изменились:

System.out.println(stringCollection);
// ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1
Map

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

stringCollection
    .stream()
    .map(String::toUpperCase)
    .sorted((a, b) -> b.compareTo(a))
    .forEach(System.out::println);

// "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"
Match

Различные типыMatchОперации можно использовать, чтобы определить, соответствует ли данный предикат элементу в потоке. Операция Match является терминальной операцией и возвращает логическое значение.

boolean anyStartsWithA =
    stringCollection
        .stream()
        .anyMatch((s) -> s.startsWith("a"));

System.out.println(anyStartsWithA);      // true

boolean allStartsWithA =
    stringCollection
        .stream()
        .allMatch((s) -> s.startsWith("a"));

System.out.println(allStartsWithA);      // false

boolean noneStartsWithZ =
    stringCollection
        .stream()
        .noneMatch((s) -> s.startsWith("z"));

System.out.println(noneStartsWithZ);      // true
Count

Countэто терминальная операция, которая возвращает длинное значение, представляющее количество элементов в потоке.

long startsWithB =
    stringCollection
        .stream()
        .filter((s) -> s.startsWith("b"))
        .count();

System.out.println(startsWithB);    // 3
Reduce

ReduceЭто терминальная операция, которая работает со всеми элементами в потоке в соответствии с заданным методом и возвращает значение необязательного типа.

Optional<String> reduced =
    stringCollection
        .stream()
        .sorted()
        .reduce((s1, s2) -> s1 + "#" + s2);

reduced.ifPresent(System.out::println);// "aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2"

Примечание: метод ifPresent принимает объект типа Consumer, System.out::println — это ссылка на метод, а println — функция, которая принимает один параметр и не возвращает значение, что просто соответствует определению Consumer.

Параллельные потоки

упомянутый вышеStreamМожно последовательно или параллельно. Последовательная операция Stream выполняется в одном потоке, а параллельная операция выполняется одновременно в нескольких потоках.

В следующем примере демонстрируется использование параллельных потоков для повышения производительности программы.

Сначала инициализируйте список со многими элементами, где каждый элемент уникален:

int max = 1000000;
List<String> values = new ArrayList<>(max);for (int i = 0; i < max; i++) {
   UUID uuid = UUID.randomUUID();
   values.add(uuid.toString());
}

Затем проверьте время, необходимое для работы с этим списком в последовательном и параллельном потоках соответственно.

тандемная сортировка:

long t0 = System.nanoTime();

long count = values.stream().sorted().count();
System.out.println(count);
long t1 = System.nanoTime();
long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
System.out.println(String.format("sequential sort took: %d ms", millis));

// sequential sort took: 899 ms

параллельная сортировка:

long t0 = System.nanoTime();

long count = values.parallelStream().sorted().count();
System.out.println(count);

long t1 = System.nanoTime();

long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
System.out.println(String.format("parallel sort took: %d ms", millis));

// parallel sort took: 472 ms

Как показывают результаты, при выполнении этого почти идентичного кода параллельная сортировка выполняется примерно на 50% быстрее, вам просто нужно изменить stream() на parallelStream().

Map

упомянутый ранееMapStream не поддерживается, но Map уже поддерживает множество новых и полезных методов для обычных задач.

Map<Integer, String> map = new HashMap<>();
for (int i = 0; i < 10; i++) {
    map.putIfAbsent(i, "val" + i);
}
map.forEach((id, val) -> System.out.println(val));

Как видно из приведенного выше кода, putIfAbsent не может выполнять проверку на нулевое значение, forEach принимает Consumer для обхода каждого элемента на карте.

В следующем коде показано, как заставить встроенные методы map выполнять вычисления:

map.computeIfPresent(3, (num, val) -> val + num);
map.get(3);             // val33

map.computeIfPresent(9, (num, val) -> null);
map.containsKey(9);     // false

map.computeIfAbsent(23, num -> "val" + num);
map.containsKey(23);    // true

map.computeIfAbsent(3, num -> "bam");
map.get(3);             // val33

Давайте узнаем, как удалить значение, соответствующее ключу, только если введенное значение соответствуетMapРавные по значению для удаления:

map.remove(3, "val3");
map.get(3);             // val33

map.remove(3, "val33");
map.get(3);             // null

Также полезен следующий метод:

map.getOrDefault(42, "not found");  // not found

Объединение значений в Map также довольно простое:

map.merge(9, "val9", (value, newValue) -> value.concat(newValue));
map.get(9);             // val9

map.merge(9, "concat", (value, newValue) -> value.concat(newValue));
map.get(9);             // val9concat

Если значение, соответствующее текущему ключу, не существует, входное значение будет помещено непосредственно в карту, в противном случае будет вызвана функция слияния для изменения существующего значения.

Date API

Java8 вjava.timeВ пакете есть совершенно новые API-интерфейсы даты и времени. Эти новые API даты полностью сопоставимыJoda-Time, но не совсем то же самое. Ниже описаны наиболее важные части этих новых API.

Clock

ClockКласс можно использовать для доступа к текущей дате и времени. Часы могут получить текущий часовой пояс, могут заменить System.currentTimeMillis(), чтобы получить текущие миллисекунды. Моменты на текущей временной шкале могут быть представлены с помощью класса Instant, который также может создавать примитивные объекты java.util.Date.

Clock clock = Clock.systemDefaultZone();long millis = clock.millis();

Instant instant = clock.instant();
Date legacyDate = Date.from(instant);   // legacy java.util.Date
Timezones

часовой пояс пройденzoneIdчтобы указать, что доступ к zoneId можно получить с помощью статического фабричного метода. Класс часового пояса также определяет смещение для преобразования между текущим моментом или временем и временем целевого часового пояса.

System.out.println(ZoneId.getAvailableZoneIds());// prints all available timezone ids

ZoneId zone1 = ZoneId.of("Europe/Berlin");
ZoneId zone2 = ZoneId.of("Brazil/East");
System.out.println(zone1.getRules());
System.out.println(zone2.getRules());

// ZoneRules[currentStandardOffset=+01:00]
// ZoneRules[currentStandardOffset=-03:00]
LocalTime

LocalTimeПредставляет время без часового пояса, например 22:00 или 17:30:15. В следующем примере создаются два местных времени для ранее определенного часового пояса. Затем сравните два времени и вычислите разницу в часах и минутах между двумя временами.

LocalTime now1 = LocalTime.now(zone1);
LocalTime now2 = LocalTime.now(zone2);

System.out.println(now1.isBefore(now2));  // false

long hoursBetween = ChronoUnit.HOURS.between(now1, now2);
long minutesBetween = ChronoUnit.MINUTES.between(now1, now2);

System.out.println(hoursBetween);       // -3
System.out.println(minutesBetween);     // -239

Экземпляры локального времени могут быть созданы с помощью ряда фабричных методов, включая преобразование строк для получения экземпляров:

LocalTime late = LocalTime.of(23, 59, 59);
System.out.println(late);       // 23:59:59

DateTimeFormatter germanFormatter =
    DateTimeFormatter
        .ofLocalizedTime(FormatStyle.SHORT)
        .withLocale(Locale.GERMAN);

LocalTime leetTime = LocalTime.parse("13:37", germanFormatter);
System.out.println(leetTime);   // 13:37
LocalDate

LocalDateПредставляет явную дату, например 2017-03-11. Он неизменен, точно так же, как LocalTime. В следующем примере показано, как добавить или вычесть дни, месяцы или годы из даты. Обратите внимание, что после каждого вычисления возвращается новый экземпляр.

LocalDate today = LocalDate.now();
LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS);
LocalDate yesterday = tomorrow.minusDays(2);

LocalDate independenceDay = LocalDate.of(2014, Month.JULY, 4);
DayOfWeek dayOfWeek = independenceDay.getDayOfWeek();
System.out.println(dayOfWeek);    // FRIDAY

Преобразование LocalDate из строки так же просто, как LocalTime.

DateTimeFormatter germanFormatter =
    DateTimeFormatter
        .ofLocalizedDate(FormatStyle.MEDIUM)
        .withLocale(Locale.GERMAN);

LocalDate xmas = LocalDate.parse("24.12.2014", germanFormatter);
System.out.println(xmas);   // 2014-12-24
LocalDateTime

LocalDateTimeПредставляет конкретную дату и время, которая объединяет дату и время в приведенном выше примере. LocalDateTime неизменяем и используется так же, как LocalDate и LocalTime. Некоторые свойства экземпляра LocalDateTime можно получить с помощью методов.

LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59);

DayOfWeek dayOfWeek = sylvester.getDayOfWeek();
System.out.println(dayOfWeek);      // WEDNESDAY

Month month = sylvester.getMonth();
System.out.println(month);          // DECEMBER

long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY);
System.out.println(minuteOfDay);    // 1439

Чтобы получить другую информацию о часовом поясе, можно преобразовать его из объекта Instant. Мгновенные экземпляры можно легко преобразовать в объекты java.util.Date.

Instant instant = sylvester
        .atZone(ZoneId.systemDefault())
        .toInstant();

Date legacyDate = Date.from(instant);
System.out.println(legacyDate);     // Wed Dec 31 23:59:59 CET 2014

Форматирование объектов LocalDateTime такое же, как форматирование объектов LocalDate и LocalTime, и вы можете использовать настраиваемый формат, не определяя его заранее.

DateTimeFormatter formatter =
    DateTimeFormatter
        .ofPattern("MMM dd, yyyy - HH:mm");

LocalDateTime parsed = LocalDateTime.parse("Nov 03, 2014 - 07:13", formatter);
String string = formatter.format(parsed);
System.out.println(string);     // Nov 03, 2014 - 07:13

В отличие от java.text.NumberFormat, новый DateTimeFormatter является неизменяемым и потокобезопасным.

Для получения дополнительной информации о синтаксисе форматирования см.здесь.

аннотация

Аннотации в Java 8 можно использовать повторно, и вот несколько примеров, демонстрирующих эту функцию.

Во-первых, определите оболочку аннотаций, которая упаковывает массив аннотаций:

@Retention(RetentionPolicy.RUNTIME)
@Target(value={ElementType.TYPE})
@interface Hints {
    Hint[] value();
}

@Repeatable(Hints.class)
@interface Hint {
    String value();
}

Java8 позволяет использовать несколько аннотаций для одного и того же типа через @Repeatable.

старое использование: Аннотировать с помощью контейнера

@Hints({@Hint("hint1"), @Hint("hint2")})
class Person {}

новое использование: использовать многоразовые аннотации

@Hint("hint1")@Hint("hint2")
class Person {}

Компилятор Java неявно использует аннотацию @Hints при использовании нового использования. Это важно для чтения аннотаций через отражение.


Hint hint = Person.class.getAnnotation(Hint.class);
System.out.println(hint);                   // null

Hints hints1 = Person.class.getAnnotation(Hints.class);
System.out.println(hints1.value().length);  // 2

Hint[] hints2 = Person.class.getAnnotationsByType(Hint.class);
System.out.println(hints2.length);          // 2

Хотя аннотация @Hints не объявлена ​​в классе Person, она доступна через getAnnotation(Hints.class) . Однако более удобным способом является получение всех аннотаций с помощью @Hint напрямую через getAnnotationByType.

Кроме того, использование аннотаций в Java8 может быть расширено до двух новых целей.

@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
@interface MyAnnotation {}

(Заканчивать)

оригинальный

Обратите внимание на публичный аккаунт WeChat, пообщайтесь о других