Публичный аккаунт WeChat: стек червоточины bugstack | Исходный код:GitHub.com/заместитель комиссара/…
Осаждайте, делитесь, расширяйте, фокусируйтесь на оригинальных частных случаях, делитесь знаниями самым простым способом изучения программирования, чтобы вы и другие могли извлечь выгоду. Темы, которые были завершены на данный момент, включают: практический пример Netty4.x, реализацию JVM с Java, полноканальный мониторинг на основе JavaAgent, рукописную структуру RPC, пример проектирования архитектуры [Ing] и т. д.
предисловие
Давно хотел разобраться в новых фичах jdk1.8, и случайно увидел гит иностранцев (ссылка есть в конце статьи) В этой структуре я продолжил улучшать описание и функции, и сделал 41 модульный тест, чтобы помочь новичкам учиться. Следующее содержание очень сухое.Для новичка, изучающего новые функции jdk1.8, вы можете в основном знать 7788 после прочтения его один раз.Прочитав его дважды и написав его снова, вы можете использовать его в реальных проектах. но! Новые функции, хотя приятно. Но если вы хотите использовать его, вы должны смотреть на соответствующий исходный код и больше практиковаться, иначе будет очень легко запутаться, и его будет трудно читать.
Ноль, просмотрите абстрактный класс
До jdk1.8, поскольку интерфейс может выполнять только определение метода и не может иметь реализацию метода, мы обычно реализуем метод по умолчанию в абстрактном классе {обычно этот метод по умолчанию является общедоступным методом после абстракции и не требует каждого наследования. Все для реализации , просто позвони}. как показано ниже;
на момент определения;
public abstract class AFormula {
abstract double calculate(int a);
// 平方
double sqrt(int a) {
return Math.sqrt(a);
}
}
во время использования;
@Test
public void test_00() {
AFormula aFormula = new AFormula() {
@Override
double calculate(int a) {
return a * a;
}
};
System.out.println(aFormula.calculate(2)); //求平方:4
System.out.println(aFormula.sqrt(2)); //求开方:1.4142135623730951
}
1. Обеспечьте реализации методов по умолчанию в интерфейсах (что-то вроде абстрактных классов).
В jdk1.8 внутри вы можете не только определять интерфейсы, но и предоставлять реализации по умолчанию в интерфейсе. Это небольшое изменение позволяет изменить весь абстрактный дизайн!
в определенное время; {ключевое слово по умолчанию должно}
public interface IFormula {
double calculate(int a);
// 平方
default double sqrt(int a) {
return Math.sqrt(a);
}
}
во время использования (1);
@Test
public void test_01() {
IFormula formula = new IFormula() {
@Override
public double calculate(int a) {
return a * a;
}
};
System.out.println(formula.calculate(2));
System.out.println(formula.sqrt(2));
}
При использовании (два); если только так используется, то не очень интересно. Я всегда говорил: хороший код — отстой!
- a; a — имя входного параметра и может быть любым другим именем
- ->a*a; Стрелка указывает на конкретную реализацию
- Однако это на самом деле не подходит для добавления журналов.
@Test
public void test_02() {
// 入参a 和 实现
IFormula formula = a -> a * a;
System.out.println(formula.calculate(2));
System.out.println(formula.sqrt(2));
}
2. Лямбда-выражения
Поскольку существуют интерфейсы, которые могут добавлять реализации методов по умолчанию, Java должна быть спроектирована таким образом, чтобы упростить разработку. Таким образом, вы увидите реализации методов по умолчанию из всех наших предыдущих интерфейсов List, Set и т. д.
Начните со знакомой последовательности сортировки
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);
}
});
Вспомогательный класс Collections предоставляет метод сортировки статического метода, который принимает коллекцию List и Comparator для сортировки данной коллекции List. Приведенный выше пример кода создает анонимный внутренний класс в качестве входного параметра, и такого рода операции можно увидеть повсюду в нашей повседневной работе.
Эта нотация больше не рекомендуется в Java 8 в пользу лямбда-выражений:
Collections.sort(names, (String a, String b) -> {
return b.compareTo(a);
});
Приведенный выше блок кода с той же функцией намного короче и чище. Так же, как свекровь и невестка, вы можете сначала не привыкнуть к этому, но вам нравится, когда вы к нему прикасаетесь. Потому что он также может быть короче и лучше;
Collections.sort(names, (String a, String b) -> b.compareTo(a));
Чтобы достичь конечного, мы также можем сделать его короче: {конечно, ваша реализация не является строкой кода, поэтому вы не можете этого сделать}
names.sort((a, b) -> b.compareTo(a));
В коллекцию java.util.List теперь добавлен метод сортировки. Кроме того, компилятор Java может определить тип параметра в соответствии с механизмом вывода типа, так что вы можете опустить тип входного параметра.Как насчет этого, вы не очень сердитесь!
java.util.List.sort
default void sort(Comparator<? super E> c) {
Object[] a = this.toArray();
Arrays.sort(a, (Comparator) c);
ListIterator<E> i = this.listIterator();
for (Object e : a) {
i.next();
i.set((E) e);
}
}
Хорошо! Вы думали, что это закончилось, нет! Можно и короче! (Благодаря методу стека по умолчанию, также представленному в интерфейсе Comparator, то есть интерфейс может иметь не только реализацию по умолчанию по умолчанию, но и статические методы)
names.sort(Comparator.reverseOrder());
2. Функциональные интерфейсы
How does lambda expressions fit into Java's type system? Each lambda corresponds to a given type, specified by an interface. A so called functional interface must contain exactly one abstract method declaration. Each lambda expression of that type will be matched to this abstract method. Since default methods are not abstract you're free to add default methods to your functional interface.
Из вышеприведенного примера видно, что логику той же функции можно реализовать через Lambda, но код очень простой, так как же JVM выполняет вывод типов и находит соответствующий метод?
Благодаря официальному представлению и нашему использованию мы обнаружили, что не каждый интерфейс может быть сокращен как метод разработки лямбда-выражений. На самом деле только эти функциональные интерфейсы могут быть сокращены как лямбда-выражения.
Функциональный интерфейс — это объявление, содержащее только один абстрактный метод. Все лямбда-выражения для этого типа интерфейса будут соответствовать этому абстрактному методу. {Кроме того, просто добавление значения по умолчанию в интерфейс не является абстрактным методом}
Резюме: чтобы гарантировать, что интерфейс четко определен как функциональный интерфейс (Functional Interface), нам нужно добавить к интерфейсу аннотацию: @FunctionalInterface. Таким образом, как только вы добавите второй абстрактный метод, компилятор немедленно выдаст ошибку. {Не заполнять, но можно и просто написать по умолчанию}
Определительный интерфейс с комментарием @functionaliNterface
@FunctionalInterface
public interface IConverter<F, T> {
T convert(F from);
}
- Давайте начнем с традиционного способа, который легко понять, потому что я к нему привык.
IConverter<String, Integer> converter01 = new IConverter<String, Integer>() {
@Override
public Integer convert(String from) {
return Integer.valueOf(from);
}
- Немного упрощенно, составьте & (форма), можно опустить только один параметр, круглые скобки
IConverter<String, Integer> converter02 = (from) -> {
return Integer.valueOf(from);
};
- Продолжайте упрощать, потому что его реализация состоит всего из одной строки кода и может быть еще короче.
IConverter<String, Integer> converter03 = from -> Integer.valueOf(from);
- Это может быть короче, на самом деле этот вариант относится к содержанию следующего абзаца, давайте поставим это первым, чтобы получить впечатление
IConverter<Integer, String> converter04 = String::valueOf;
3. Удобные ссылки на методы и конструкторы
Выше мы впервые добавили фрагмент Impression XX :: XX, который также является удобным эталоном для новых функций Java8. Вы, возможно, видели эти четыре очка на других языках.
IConverter<Integer, String> converter04 = String::valueOf;
String converted04 = converter04.convert(11);
System.out.println(converted04);
Ключевые слова из этих четырех точек :: могут относиться не только к методам и конструкторам, но и к обычным методам.
public class Something{
public String startsWith(String s) {
return String.valueOf(s.charAt(0));
}
}
IConverter<String, String> converter01 = s -> String.valueOf(s.charAt(0)); //[参照物]直接把逻辑放到这调用
IConverter<String, String> converter02 = something::startsWith; //引用的方法体里面逻辑可以更多,否则只是一句代码并不能适合所有的情况
System.out.println(converter01.convert("Java"));
System.out.println(converter02.convert("Java"));
Далее мы используем эти четыре пункта, чтобы увидеть, как обращаться к конструктору класса. Сначала мы создаем такой класс;
public class Person {
String firstName;
String lastName;
Person() {}
Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
Затем мне также нужен фабричный класс для создания объекта Person;
@FunctionalInterface
public interface IPersonFactory<P extends Person> {
P create(String firstName, String lastName);
}
Теперь пришло время использовать четыре торта::;
IPersonFactory<Person> personFactory = Person::new; //[参照物]:(firstName, lastName) -> new Person(firstName, lastName);
Person person = personFactory.create("Peter", "Parker");
Напоминание; в заводской функции может быть только одна функция, иначе будет сообщено об ошибке
Fourcakes::, позволяет нам напрямую ссылаться на конструктор класса Person, а затем компилятор Java может выбрать правильный конструктор на основе сигнатуры класса для реализации метода PersonFactory.create.
Четыре, лямбда
Accessing outer scope variables from lambda expressions is very similar to anonymous objects. You can access final variables from the local outer scope as well as instance fields and static variables.
Лямбда-выражения обращаются к внешним переменным (локальным переменным, переменным-членам, статическим переменным, методам интерфейсов по умолчанию), которые очень похожи на анонимные внутренние классы, обращающиеся к внешним переменным.
1. Доступ к локальным переменным
Мы можем прочитать последнюю локальную переменную num из внешней области лямбда-выражения;
int num = 1;
IConverter<Integer, String> stringConverter = from -> String.valueOf(from + num);
String convert = stringConverter.convert(2);
System.out.println(convert); // 3
Но это число является неизменяемым значением, поэтому изменение значения приведет к ошибке;
int num = 1;
IConverter<Integer, String> stringConverter =
(from) -> String.valueOf(from + num);
num = 3;
Variable used in lambda expression should be final or effectively final
Кроме того, изменение внутри лямбда-выражения не допускается;
int num = 1;
IConverter<Integer, String> converter = (from) -> {
String value = String.valueOf(from + num);
num = 3;
return value;
};
Variable used in lambda expression should be final or effectively final
2. Доступ к переменным-членам и статическим переменным
Доступ к локальным переменным в лямбдах выражениях. В отличие от локальных переменных, переменные элементов и статические переменные имеют разрешения на чтение и запись в лямбдах выражения:
public class Lambda4 {
// 静态变量
static int outerStaticNum;
// 成员变量
int outerNum;
void testScopes() {
IConverter<Integer, String> stringConverter1 = (from) -> {
// 对成员变量赋值
outerNum = 23;
return String.valueOf(from);
};
IConverter<Integer, String> stringConverter2 = (from) -> {
// 对静态变量赋值
outerStaticNum = 72;
return String.valueOf(from);
};
}
}
3. Доступ к методам интерфейса по умолчанию
Помните пример IFormula из первого раздела?
public interface IFormula {
double calculate(int a);
// 平方
default double sqrt(int a) {
return Math.sqrt(a);
}
}
В то время мы определили метод квадратного корня sqrt с реализацией по умолчанию в интерфейсе, и мы можем легко получить доступ к этому методу в анонимном внутреннем классе:
IFormula formula = new IFormula() {
@Override
public double calculate(int a) {
return a * a;
}
};
Но вы не можете получить доступ к методам по умолчанию через лямбда-выражения, и такой код не будет компилироваться;
IFormula formula = (a) -> sqrt(a * a);
Доступ к методам интерфейса с реализациями по умолчанию в лямбда-выражениях недоступен, и приведенный выше код не будет компилироваться.
5. Встроенный функциональный интерфейс
API JDK 1.8 включает множество встроенных функциональных интерфейсов. К ним относятся Comparator и Runnable, которые мы часто видим в старых версиях, а в Java 8 к ним добавлены аннотации @FunctionalInterface для поддержки лямбда-выражений.
Например, в дополнение к Comparator и Runnable, обычно используемым в нашей старой версии Jdk, есть также некоторые новые функциональные интерфейсы, которые могут реализовать поддержку Lamdba через аннотации функций, многие из которых заимствованы из известныхGoogle Guavaбиблиотека.
Даже если вы уже знакомы с этой библиотекой классов, вам следует обратить пристальное внимание на то, как эти интерфейсы расширяются некоторыми полезными расширениями методов:
1. Утверждение предиката
Predicate — это функциональный интерфейс, который может указывать тип входного параметра и возвращать логическое значение. Он внутренне предоставляет некоторые методы с реализациями по умолчанию, которые можно использовать для объединения сложных логических суждений (и, или, отрицания):
@Test
public void test11() {
Predicate<String> predicate = (s) -> s.length() > 0;
boolean foo0 = predicate.test("foo"); // true
boolean foo1 = predicate.negate().test("foo"); // negate否定相当于!true
Predicate<Boolean> nonNull = Objects::nonNull;
Predicate<Boolean> isNull = Objects::isNull;
Predicate<String> isEmpty = String::isEmpty;
Predicate<String> isNotEmpty = isEmpty.negate();
}
2. Functions
Функция функционального интерфейса Функция заключается в том, что мы можем предоставить ему сырье, а он будет производить конечный продукт. С помощью методов по умолчанию он обеспечивает компоновку, обработку цепочки строк (композицию и затем):
@Test
public void test12() {
Function<String, Integer> toInteger = Integer::valueOf; //转Integer
Function<String, String> backToString = toInteger.andThen(String::valueOf); //转String
Function<String, String> afterToStartsWith = backToString.andThen(new Something()::startsWith); //截取第一位
String apply = afterToStartsWith.apply("123");// "123"
System.out.println(apply);
}
3. Suppliers
В отличие от Function, Supplier не принимает входные параметры и напрямую выдает нам указанный результат, что немного похоже на паттерн производителя:
@Test
public void test13() {
Supplier<Person> personSupplier0 = Person::new;
personSupplier0.get(); // new Person
Supplier<String> personSupplier1 = Something::test01; //这个test方法是静态的,且无入参
personSupplier1.get(); // hi
Supplier<String> personSupplier2 = new Something()::test02;
}
4. Consumers
Для Consumer нам нужно предоставить входные параметры для использования, как показано в следующем примере кода:
@Test
public void test14() {
// 参照物,方便知道下面的Lamdba表达式写法
Consumer<Person> greeter01 = new Consumer<Person>() {
@Override
public void accept(Person p) {
System.out.println("Hello, " + p.firstName);
}
};
Consumer<Person> greeter02 = (p) -> System.out.println("Hello, " + p.firstName);
greeter02.accept(new Person("Luke", "Skywalker")); //Hello, Luke
Consumer<Person> greeter03 = new MyConsumer<Person>()::accept; // 也可以通过定义类和方法的方式去调用,这样才是实际开发的姿势
greeter03.accept(new Person("Luke", "Skywalker")); //Hello, Luke
}
5. Comparators
Компаратор был более распространен до Java 8. Помимо обновления до функционального интерфейса, Java 8 также расширяет для него некоторые методы по умолчанию:
@Test
public void test15(){
Comparator<Person> comparator01 = (p1, p2) -> p1.firstName.compareTo(p2.firstName);
Comparator<Person> comparator02 = Comparator.comparing(p -> p.firstName); //等同于上面的方式
Person p1 = new Person("John", "Doe");
Person p2 = new Person("Alice", "Wonderland");
comparator01.compare(p1, p2); // > 0
comparator02.reversed().compare(p1, p2); // < 0
}
6. Опции
Во-первых, Optional — это не функциональный интерфейс, он предназначен для предотвращения NullPointerException, печально известного в программировании на Java.
Давайте кратко рассмотрим, как используется необязательный параметр! Вы можете думать о необязательном как о контейнере, который обертывает объекты (которые могут быть или не быть нулевыми). когда вы определяете
Когда метод, объект, возвращаемый этим методом, может быть пустым или непустым, вы можете обернуть его опциональным, что также является рекомендуемой практикой в Java 8.
@Test
public void test16(){
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"
Optional<Person> optionalPerson = Optional.of(new Person());
optionalPerson.ifPresent(s -> System.out.println(s.firstName));
}
Семь, Потоковое вещание
Что такое потоковый поток?
Проще говоря, мы можем использовать java.util.Stream для выполнения различных операций над коллекцией, содержащей один или несколько элементов. Эти операции могут быть промежуточными операциями или терминальными операциями. Терминальные операции возвращают результат, а промежуточные операции возвращают поток.
Обратите внимание, что вы можете выполнять потоковые операции только с классами, которые реализуют интерфейс java.util.Collection.
Потоковые потоки поддерживают синхронное выполнение, а также параллельное выполнение.
Примечание. Карта не поддерживает поток Stream, но его ключ и значение поддерживаются!
Давайте сначала посмотрим, как работает Stream. Сначала мы создаем пример в виде списка строк;
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");
1. Фильтр
Входным параметром Filter является Predicate.Как упоминалось выше, Predicate является промежуточной операцией утверждения, которая может помочь нам отфильтровать нужные нам элементы множества. Его возвращаемый параметр также является потоком Stream, и мы можем распечатать отфильтрованные элементы с помощью операции терминала foreach:
@Test
public void test17(){
stringCollection
.stream()
.filter((s) -> s.startsWith("a"))
.forEach(System.out::println);
}
2. Сортировка
Sorted также является промежуточной операцией, и ее возвращаемый параметр — поток Stream. Кроме того, мы можем передать компаратор для настройки сортировки, если нет, используются правила сортировки по умолчанию.
@Test
public void test18() {
stringCollection
.stream()
.sorted()
.filter((s) -> s.startsWith("a"))
.forEach(System.out::println);
}
Примечание: это sorted только делает отсортированное представление для вывода и фактически не сортирует данные в списке.
System.out.println(stringCollection);
// ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1
3. Преобразование карты
Карта промежуточных операций преобразует каждый элемент в другой объект с заданной функцией. Например, в следующем примере с помощью карты мы преобразуем каждую строку в верхний регистр:
@Test
public void test19(){
stringCollection
.stream()
.map(String::toUpperCase)
.sorted(Comparator.reverseOrder()) //等同于(a, b) -> b.compareTo(a)
.forEach(System.out::println);
}
Это можно использовать для преобразования объекта данных DTO.При разработке дизайна, управляемого предметной областью, DTO преобразуется в DO и передается в фоновом режиме.
4. Матч
Как следует из названия, match используется для операций сопоставления, а возвращаемое значение имеет логический тип. С помощью match мы можем легко проверить, существует ли в списке элемент определенного типа.
@Test
public void test20(){
// anyMatch:验证 list 中 string 是否有以 a 开头的, 匹配到第一个,即返回 true
boolean anyStartsWithA =
stringCollection
.stream()
.anyMatch((s) -> s.startsWith("a"));
System.out.println(anyStartsWithA); // true
// allMatch:验证 list 中 string 是否都是以 a 开头的
boolean allStartsWithA =
stringCollection
.stream()
.allMatch((s) -> s.startsWith("a"));
System.out.println(allStartsWithA); // false
// noneMatch:验证 list 中 string 是否都不是以 z 开头的
boolean noneStartsWithZ =
stringCollection
.stream()
.noneMatch((s) -> s.startsWith("z"));
System.out.println(noneStartsWithZ); // true
}
5. Считать
count — терминальная операция, которая подсчитывает общее количество элементов в потоке, а возвращаемое значение имеет тип long.
@Test
public void test21() {
// count:先对 list 中字符串开头为 b 进行过滤,让后统计数量
long startsWithB =
stringCollection
.stream()
.filter((s) -> s.startsWith("b"))
.count();
System.out.println(startsWithB); // 3
}
6. Reduce
Уменьшить Китайский перевод: уменьшить, уменьшить. С помощью переданной функции мы можем сократить список до значения. Его возвращаемый тип является необязательным.
@Test
public void test22() {
Optional<String> reduced =
stringCollection
.stream()
.sorted()
.reduce((s1, s2) -> s1 + "#" + s2);
reduced.ifPresent(System.out::println);
// aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2
}
Восемь, параллельный поток Parallel-Streams
Как упоминалось выше, потоки могут быть последовательными или параллельными. Операции с последовательными потоками выполняются в одном потоке, а операции с параллельными потоками выполняются одновременно в нескольких потоках.
В следующем примере показано, как легко использовать параллельные потоки для повышения производительности. Тест улучшил производительность в 1 раз!
Во-первых, мы создаем больший список:
int max = 1000000;
List<String> values = new ArrayList<>(max);
for (int i = 0; i < max; i++) {
UUID uuid = UUID.randomUUID();
values.add(uuid.toString());
}
1. Последовательная сортировка
@Test
public void test23() {
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("顺序流排序耗时: %d ms", millis));
//顺序流排序耗时: 712 ms
}
2. Параллельная сортировка Сортировка параллельного потока
@Test
public void test24(){
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.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: 385 ms
}
Как видите, два фрагмента кода почти идентичны, но параллельная сортировка выполняется примерно на 50 % быстрее. Вам просто нужно изменить stream() на parallelStream().
9. Коллекция карт
Как упоминалось ранее, Map не поддерживает потоки Stream, потому что интерфейс Map не определяет метод stream(), такой как интерфейс Collection. Однако мы можем использовать потоковые операции с его ключом, значениями, записью, например map.keySet().stream(), map.values().stream() и map.entrySet().stream().
Кроме того, JDK 8 предоставляет некоторые другие новые функции для карты:
@Test
public void test25() {
Map<Integer, String> map = new HashMap<>();
for (int i = 0; i < 10; i++) {
// 与老版不同的是,putIfAbent() 方法在 put 之前, 不用在写if null continue了
// 会判断 key 是否已经存在,存在则直接返回 value, 否则 put, 再返回 value
map.putIfAbsent(i, "val" + i);
}
// forEach 可以很方便地对 map 进行遍历操作
map.forEach((key, value) -> System.out.println(value));
}
Затем мы делаем вывод преобразования объекта Map; (определить два класса BeanA, BeanB)
@Test
public void test26() {
Map<Integer, BeanA> map = new HashMap<>();
for (int i = 0; i < 10; i++) {
// 与老版不同的是,putIfAbent() 方法在 put 之前, 不用在写if null continue了
// 会判断 key 是否已经存在,存在则直接返回 value, 否则 put, 再返回 value
map.putIfAbsent(i, new BeanA(i, "明明" + i, i + 20, "89021839021830912809" + i));
}
Stream<BeanB> beanBStream00 = map.values().stream().map(new Function<BeanA, BeanB>() {
@Override
public BeanB apply(BeanA beanA) {
return new BeanB(beanA.getName(), beanA.getAge());
}
});
Stream<BeanB> beanBStream01 = map.values().stream().map(beanA -> new BeanB(beanA.getName(), beanA.getAge()));
beanBStream01.forEach(System.out::println);
}
В дополнение к вышеупомянутым putIfAbsent() и forEach() мы также можем легко выполнять связанные операции со значением ключа:
@Test
public void test27() {
// 如下:对 key 为 3 的值,内部会先判断值是否存在,存在,则做 value + key 的拼接操作
map.computeIfPresent(3, (num, val) -> val + num);
map.get(3); // val33
// 先判断 key 为 9 的元素是否存在,存在,则做删除操作
map.computeIfPresent(9, (num, val) -> null);
map.containsKey(9); // false
// computeIfAbsent(), 当 key 不存在时,才会做相关处理
// 如下:先判断 key 为 23 的元素是否存在,不存在,则添加
map.computeIfAbsent(23, num -> "val" + num);
map.containsKey(23); // true
// 先判断 key 为 3 的元素是否存在,存在,则不做任何处理
map.computeIfAbsent(3, num -> "bam");
map.get(3); // val33
}
Что касается операций удаления, в JDK 8 доступен новый API remove():
@Test
public void test28() {
map.remove(3, "val3");
map.get(3); // val33
map.remove(3, "val33");
map.get(3); // null
}
Как и в приведенном выше коде, операция удаления будет выполнена только в том случае, если заданный ключ и значение точно совпадают.
Что касается добавления методов, то в JDK 8 предусмотрен метод getOrDefault() со значениями по умолчанию:
@Test
public void test29() {
// 若 key 42 不存在,则返回 not found
map.getOrDefault(42, "not found"); // not found
}
Операция слияния для значения также становится проще:
@Test
public void test30() {
// merge 方法,会先判断进行合并的 key 是否存在,不存在,则会添加元素
map.merge(9, "val9", (value, newValue) -> value.concat(newValue));
map.get(9); // val9
// 若 key 的元素存在,则对 value 执行拼接操作
map.merge(9, "concat", (value, newValue) -> value.concat(newValue));
map.get(9); // val9concat
}
10. API даты
Новый API даты был добавлен в Java 8 в пакете java.time.Joda-TimeБиблиотеки похожи, но не идентичны. Далее я представлю наиболее важные функции нового API с помощью примера кода:
1. Clock
Часы обеспечивают доступ к текущей дате и времени. Мы можем использовать его для замены метода System.currentTimeMillis(). Кроме того, мгновенный экземпляр можно получить с помощью clock.instant(), Этот экземпляр можно легко преобразовать в более старый объект java.util.Date.
@Test
public void test31(){
Clock clock = Clock.systemDefaultZone();
long millis = clock.millis();
Instant instant = clock.instant();
Date legacyDate = Date.from(instant); // 老版本 java.util.Date
}
2. Часовые пояса
ZoneID представляет класс часового пояса. Он легко получается через статический заводский метод, и мы можем пройти в определенном коде часового пояса. Кроме того, класс часового пояса также определяет смещение, которое используется для преобразования между текущим моментом или временем и целевым временем часового пояса.
@Test
public void test32() {
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());
//[Asia/Aden, America/Cuiaba, Etc/GMT+9, Etc/Gada/Atlantic, Atlantic/St_Helena, Australia/Tasmania, Libya, Europe/Guernsey, America/Grand_Turk, US/Pacific-New, Asia/Samarkand, America/Argentina/Cordoba, Asia/Phnom_Penh, Africa/Kigali, Asia/Almaty, US/Alaska, Asi...
// ZoneRules[currentStandardOffset=+01:00]
// ZoneRules[currentStandardOffset=-03:00]
}
3. LocalTime
LocalTime представляет класс времени без указания часового пояса, например, 22:00 или 17:30:15.В следующем примере кода два LocalTimes будут созданы с использованием объекта часового пояса, созданного выше. Затем мы сравниваем два времени и вычисляем разницу в часах и минутах между ними.
@Test
public void test33(){
ZoneId zone1 = ZoneId.of("Europe/Berlin");
ZoneId zone2 = ZoneId.of("Brazil/East");
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 предоставляет ряд статических фабричных методов для упрощения создания и работы с экземплярами объекта времени, включая операцию синтаксического анализа строк времени.
@Test
public void test34(){
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
}
4. LocalDate
LocalDate — это объект даты, например: 2014-03-11. Это объект окончательного типа, такой как LocalTime. В следующем примере показано, как вычислить новую дату путем сложения и вычитания дней, месяцев, лет и т. д.
@Test
public void test35(){
LocalDate today = LocalDate.now();
// 今天加一天
LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS);
// 明天减两天
LocalDate yesterday = tomorrow.minusDays(2);
// 2014 年七月的第四天
LocalDate independenceDay = LocalDate.of(2014, Month.JULY, 4);
DayOfWeek dayOfWeek = independenceDay.getDayOfWeek();
System.out.println(dayOfWeek); // 星期五
}
Вы также можете проанализировать строку даты напрямую, чтобы сгенерировать экземпляр LocalDate. (так же просто, как операция LocalTime)
@Test
public void test36(){
DateTimeFormatter germanFormatter =
DateTimeFormatter
.ofLocalizedDate(FormatStyle.MEDIUM)
.withLocale(Locale.GERMAN);
LocalDate xmas = LocalDate.parse("24.12.2014", germanFormatter);
System.out.println(xmas); // 2014-12-24
}
5. LocalDateTime
LocalDateTime — это объект даты и времени. Вы также можете думать об этом как о комбинации LocalDate и LocalTime. В эксплуатации примерно так же.
@Test
public void test37(){
LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59);
DayOfWeek dayOfWeek = sylvester.getDayOfWeek();
System.out.println(dayOfWeek); // 星期三
Month month = sylvester.getMonth();
System.out.println(month); // 十二月
// 获取改时间是该天中的第几分钟
long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY);
System.out.println(minuteOfDay); // 1439
}
Если добавляется информация о часовом поясе, LocalDateTime также можно преобразовать в экземпляр Instance. Экземпляр можно преобразовать в более старый объект java.util.Date.
@Test
public void test38(){
LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59);
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. Помимо использования предопределенных форматов, вы также можете настроить форматированный вывод.
@Test
public void test39(){
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
}
Unlike java.text.NumberFormat the new DateTimeFormatter is immutable and thread-safe.
For details on the pattern syntax read here.
11. Аннотации
Аннотации в Java8 повторяются. Давайте погрузимся прямо в пример, чтобы решить эту проблему. {Вы можете увидеть этот тип аннотации в классе запуска SpringBoot}
Во-первых, мы определяем аннотацию-оболочку, которая содержит массив фактических аннотаций:
@Repeatable(Hints.class)
public @interface Hint {
String value();
}
public @interface Hints {
Hint[] value();
}
Java 8 позволяет нам использовать несколько аннотаций одного типа, объявляя аннотацию @Repeatable.
Первая форма: использовать контейнер аннотаций (старый метод)
@Test
public void test40() {
@Hints({@Hint("hint1"), @Hint("hint2")})
class Person {
}
}
Вторая форма: использование повторяющихся аннотаций (новый метод)
@Test
public void test41() {
@Hint("hint1")
@Hint("hint2")
class Person {
}
}
Компилятор Java использует переменную 2 для неявной установки аннотации @Hints под капотом. Это важно для чтения информации аннотаций посредством отражения.
@Test
public void test41() {
@Hint("hint1")
@Hint("hint2")
class Person {
}
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) . Кроме того, метод getAnnotationsByType более удобен, поскольку он дает прямой доступ ко всем методам, аннотированным @Hints.
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
@interface MyAnnotation {}
Подводить итоги
- Новые функции jdk8 включают в себя: Lambda, функциональный интерфейс, вызовы четырех круговых диаграмм ::, встроенные функции (утверждения, функции, производители, потребители), потоки потоков, функции сбора карт, даты, аннотации и т. д.
- Разумное сочетание запуска новых функций может сократить объем кода и сделать его чище.
- В некоторых новых фреймворках, если вы посмотрите на исходный код в SpringBoot, вы увидите, что используется много новых функций.
- источник дела;GitHub.com/winter be/Джиа ...{Английский}
- вклад исходного кода;GitHub.com/заместитель комиссара/…