《JDK8特性深入讲解》学习笔记

news2024/12/26 14:57:27

Lambda表达式

如果不适用lambda表达式的话,可以使用匿名内部类,但是代码会显得有点多

lambda表达式其实就是匿名内部类

Lambda表达式简化了匿名内部类的使用,语法更加简单。

语法规则

(参数类型 参数名称) -> {
    代码体;
}

Lambda表达式使用的接口只能有一个抽象方法,可以使用@FunctionalInterface修饰

Lambda表达式本质

1. Lambda表达式在程序运行的时候会形成一个类

2. 在类中新增了一个方法,这个方法的方法体就是Lambda表达式中的代码

3. 还会形成一个匿名内部类,实现接口,重写抽象方法

4. 在重写的方法中,会调用新生成的方法

XJad:反编译工具,可以查看生成的代码

Lambda表达式省略写法

1. 小括号内的参数类型可以省略

2. 如果小括号内有且仅有一个参数,则小括号可以省略

3. 如果大括号内有且仅有一个语句,可以同时省略大括号,return 关键字及语句分号。

Lambda表达式的使用前提

1. 方法的参数或局部变量类型必须为接口才能使用Lambda

2. 接口中有且仅有一个抽象方法(@FunctionalInterface)

Lambda表达式和匿名内部类的区别

  1. 所需类型不一样

    • 匿名内部类的类型可以是 类,抽象类,接口

    • Lambda表达式需要的类型必须是接口

  2. 抽象方法的数量不一样

    • 匿名内部类所需的接口中的抽象方法的数量是随意的

    • Lambda表达式所需的接口中只能有一个抽象方法

  3. 实现原理不一样

    • 匿名内部类是在编译后形成一个class

    • Lambda表达式是在程序运行的时候动态生成class

Jdk8中接口改变

jdk8之前,接口中只能有静态常量和抽象方法;

jdk8之后,接口中可以有静态方法默认方法

默认方法

为什么要新增默认方法

如果没有默认方法,一个接口中新增一个抽象方法,所有实现类都必须重写该方法,不利于接口的扩展

默认方法使用default修饰

如果实现类没有重写默认方法,也可以直接调用接口中的默认方法

实现类也可以重写默认方法,这样调用的就是重写方法了

静态方法

静态方法也是为了接口扩展

静态方法不能被实现类重写,只能通过接口类型直接调用

函数式接口

使用Lambda表达式的前提是需要有函数式接口,而Lambda表达式使用时不关心接口名,抽象方法名。只关心抽象方法的参数列表和返回值类型。

为了更好的使用Lambda表达式,在JDK中提供了大量常用的函数式接口

Supplier

无参,有返回值

需要提供一个返回值类型T

@FunctionalInterface
public interface Supplier<T> {

    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}

Consumer

有参无返回值

需要提供一个泛型来定义参数类型

@FunctionalInterface
public interface Consumer<T> {
    void accept(T var1);

    default Consumer<T> andThen(Consumer<? super T> var1) {
        Objects.requireNonNull(var1);
        return (var2) -> {
            this.accept(var2);
            var1.accept(var2);
        };
    }
}

consumer有一个默认方法andThen

如果一个方法的参数和返回值全部是Consumer类型,那么就可以实现效果,消费一个数据的时候,首先做一个操作,然后再做一个操作,实现组合,而这个方法就是Consumer接口中的default方法 andThen方法

Function

有参有返回值

T是参数,R是返回值

@FunctionalInterface
public interface Function<T, R> {
    R apply(T var1);

    default <V> Function<V, R> compose(Function<? super V, ? extends T> var1) {
        Objects.requireNonNull(var1);
        return (var2) -> {
            return this.apply(var1.apply(var2));
        };
    }

    default <V> Function<T, V> andThen(Function<? super R, ? extends V> var1) {
        Objects.requireNonNull(var1);
        return (var2) -> {
            return var1.apply(this.apply(var2));
        };
    }

    static <T> Function<T, T> identity() {
        return (var0) -> {
            return var0;
        };
    }
}

andThen用于组合操作

compose方法的作用顺序和andThen方法刚好相反

静态方法identity,输入什么参数就返回什么参数

Predicate

有参,且返回值为boolean

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T var1);

    default Predicate<T> and(Predicate<? super T> var1) {
        Objects.requireNonNull(var1);
        return (var2) -> {
            return this.test(var2) && var1.test(var2);
        };
    }

    default Predicate<T> negate() {
        return (var1) -> {
            return !this.test(var1);
        };
    }

    default Predicate<T> or(Predicate<? super T> var1) {
        Objects.requireNonNull(var1);
        return (var2) -> {
            return this.test(var2) || var1.test(var2);
        };
    }

    static <T> Predicate<T> isEqual(Object var0) {
        return null == var0 ? Objects::isNull : (var1) -> {
            return var0.equals(var1);
        };
    }
}

方法引用

在lambda表达式中进行其他方法调用

public class YinYong {

    public static void main(String[] args) {
        testTotal(arr -> total(arr) );
    }

    public static void total(int[] arr){
        int sum = 0;
        for(int i: arr){
            sum += i;
        }
        System.out.println("sum:" + sum);
    }

    public static void testTotal(Consumer<int[]> consumer){
        int arr[] = {1,2,3,4,5};
        consumer.accept(arr);
    }
}

使用方法引用可以简写为:

public class YinYong2 {

    public static void main(String[] args) {
        testTotal(YinYong2::total);
    }

    public static void total(int[] arr){
        int sum = 0;
        for(int i: arr){
            sum += i;
        }
        System.out.println("sum:" + sum);
    }

    public static void testTotal(Consumer<int[]> consumer){
        int arr[] = {1,2,3,4,5};
        consumer.accept(arr);
    }
}

方法引用应用场景

如果Lambda表达式所要实现的方案,已经有其他方法存在相同的方案,那么则可以使用方法引用。

方法引用的使用方式

instanceName::methodName 对象::方法名

        Date date2 = new Date(2021,10,9);
        Supplier<Integer> dateSup = date2::getYear;
        Integer year = dateSup.get();
        System.out.println(year);

ClassName::staticMethodName 类名::静态方法

ClassName::methodName 类名::普通方法

        // Function<String, Integer>中,String是参数类型,Integer是返回值类型
        Function<String, Integer> function = String::length;
        Integer length = function.apply("abcd");
        System.out.println(length);

        // BiFunction<String, Integer, String>中第一个String是BiFunction的apply方法的第一个参数hello
        // Integer是apply方法的第二个参数3
        // String 是返回值str
        BiFunction<String, Integer, String> substringFunction = String::substring;
        String str = substringFunction.apply("hello", 3);
        System.out.println(str);

ClassName::new 类名::new 调用的构造器

        Supplier<LocalDate> now = LocalDate::now;
        LocalDate localDate = now.get();
        System.out.println(localDate);

由于构造器的名称和类名完全一致,所以构造器引用使用::new的格式使用

        // Long是Date的构造函数的参数类型,Date是构造函数返回值
        Function<Long, Date> dateFunction = Date::new;
        Date date = dateFunction.apply(123456789110L);
        System.out.println(date);

TypeName[]::new String[]::new 调用数组的构造器

        // Integer是数组长度
        Function<Integer, String[]> stringArr = String[]::new;
        String[] strArr = stringArr.apply(5);

方法引用是对Lambda表达式符合特定情况下的一种缩写方式,它使得我们的Lambda表达式更加的精简

Stream

没有stream之前,要对集合元素进行处理,就需要一遍遍的循环处理,很麻烦,代码量也比较大

这里的stream流和IO流没有任何关系

Stream流不是一种数据结构,不保存数据,而是对数据进行加工处理

获取stream流

通过Collection获取

Collection接口下的所有的实现都可以通过steam方法来获取Stream流,因为java.util.Collection 接口中加入了default方法 stream

        List<String> list = new ArrayList<>();
        list.stream();
        
        Set<String> set = new HashSet<>();
        set.stream();

Map没有实现Collection接口,不能直接获取stream流,只能获取key、value或者entry的stream流

通过Stream的of方法

获取数组的stream流(基本数据类型的数组是不可以的int[])

        String[] arr = {"a","b","c"};
        Stream.of(arr);

Stream流常用方法

方法名方法作用返回值类型方法种类
count统计个数long终结
forEach逐一处理void终结
filter过滤Stream函数拼接
limit取用前几个Stream函数拼接
skip跳过前几个Stream函数拼接
map映射Stream函数拼接
concat组合Stream函数拼接

终结方法:返回值类型不再是 Stream 类型的方法,不再支持链式调用。本小节中,终结方法包括 count 和forEach 方法。

非终结方法:返回值类型仍然是 Stream 类型的方法,支持链式调用。(除了终结方法外,其余方法均为非终结方法。)

Stream注意事项(重要)

  1. Stream只能操作一次

  2. Stream方法返回的是新的流

  3. Stream不调用终结方法,中间的操作不会执行

forEach方法


    /**
     * Performs an action for each element of this stream.
     *
     * <p>This is a <a href="package-summary.html#StreamOps">terminal
     * operation</a>.
     *
     * <p>The behavior of this operation is explicitly nondeterministic.
     * For parallel stream pipelines, this operation does <em>not</em>
     * guarantee to respect the encounter order of the stream, as doing so
     * would sacrifice the benefit of parallelism.  For any given element, the
     * action may be performed at whatever time and in whatever thread the
     * library chooses.  If the action accesses shared state, it is
     * responsible for providing the required synchronization.
     *
     * @param action a <a href="package-summary.html#NonInterference">
     *               non-interfering</a> action to perform on the elements
     */
    void forEach(Consumer<? super T> action);

对流中的元素进行遍历

        // 打印list中的元素
        list.stream().forEach(System.out::println);

count

count方法用来统计其中的元素个数的

    /**
     * Returns the count of elements in this stream.  This is a special case of
     * a <a href="package-summary.html#Reduction">reduction</a> and is
     * equivalent to:
     * <pre>{@code
     *     return mapToLong(e -> 1L).sum();
     * }</pre>
     *
     * <p>This is a <a href="package-summary.html#StreamOps">terminal operation</a>.
     *
     * @return the count of elements in this stream
     */
    long count();
        long count = list.stream().count();

filter

filter方法的作用是用来过滤数据的,返回符合条件的数据

可以通过filter方法将一个流转换成另一个子集流

    /**
     * Returns a stream consisting of the elements of this stream that match
     * the given predicate.
     *
     * <p>This is an <a href="package-summary.html#StreamOps">intermediate
     * operation</a>.
     *
     * @param predicate a <a href="package-summary.html#NonInterference">non-interfering</a>,
     *                  <a href="package-summary.html#Statelessness">stateless</a>
     *                  predicate to apply to each element to determine if it
     *                  should be included
     * @return the new stream
     */
    Stream<T> filter(Predicate<? super T> predicate);
        // 返回list中包含a的元素并打印出来
        list.stream().filter(s-> s.contains("a")).forEach(System.out::println);

limit

limit方法可以对流进行截取处理,只取前n个数据

    /**
     * Returns a stream consisting of the elements of this stream, truncated
     * to be no longer than {@code maxSize} in length.
     *
     * <p>This is a <a href="package-summary.html#StreamOps">short-circuiting
     * stateful intermediate operation</a>.
     *
     * @apiNote
     * While {@code limit()} is generally a cheap operation on sequential
     * stream pipelines, it can be quite expensive on ordered parallel pipelines,
     * especially for large values of {@code maxSize}, since {@code limit(n)}
     * is constrained to return not just any <em>n</em> elements, but the
     * <em>first n</em> elements in the encounter order.  Using an unordered
     * stream source (such as {@link #generate(Supplier)}) or removing the
     * ordering constraint with {@link #unordered()} may result in significant
     * speedups of {@code limit()} in parallel pipelines, if the semantics of
     * your situation permit.  If consistency with encounter order is required,
     * and you are experiencing poor performance or memory utilization with
     * {@code limit()} in parallel pipelines, switching to sequential execution
     * with {@link #sequential()} may improve performance.
     *
     * @param maxSize the number of elements the stream should be limited to
     * @return the new stream
     * @throws IllegalArgumentException if {@code maxSize} is negative
     */
    Stream<T> limit(long maxSize);

如果集合当前长度大于参数就进行截取,否则不操作  

        // 返回list中前5个元素并打印出来
        list.stream().limit(5).forEach(System.out::println);

skip

skip方法用于跳过前n个元素

    /**
     * Returns a stream consisting of the remaining elements of this stream
     * after discarding the first {@code n} elements of the stream.
     * If this stream contains fewer than {@code n} elements then an
     * empty stream will be returned.
     *
     * <p>This is a <a href="package-summary.html#StreamOps">stateful
     * intermediate operation</a>.
     *
     * @apiNote
     * While {@code skip()} is generally a cheap operation on sequential
     * stream pipelines, it can be quite expensive on ordered parallel pipelines,
     * especially for large values of {@code n}, since {@code skip(n)}
     * is constrained to skip not just any <em>n</em> elements, but the
     * <em>first n</em> elements in the encounter order.  Using an unordered
     * stream source (such as {@link #generate(Supplier)}) or removing the
     * ordering constraint with {@link #unordered()} may result in significant
     * speedups of {@code skip()} in parallel pipelines, if the semantics of
     * your situation permit.  If consistency with encounter order is required,
     * and you are experiencing poor performance or memory utilization with
     * {@code skip()} in parallel pipelines, switching to sequential execution
     * with {@link #sequential()} may improve performance.
     *
     * @param n the number of leading elements to skip
     * @return the new stream
     * @throws IllegalArgumentException if {@code n} is negative
     */
    Stream<T> skip(long n);
        // 跳过list中前5个元素,并打印后面的元素
        list.stream().skip(5).forEach(System.out::println);

map

map方法可以将流中的元素映射到另一个流中


    /**
     * Returns a stream consisting of the results of applying the given
     * function to the elements of this stream.
     *
     * <p>This is an <a href="package-summary.html#StreamOps">intermediate
     * operation</a>.
     *
     * @param <R> The element type of the new stream
     * @param mapper a <a href="package-summary.html#NonInterference">non-interfering</a>,
     *               <a href="package-summary.html#Statelessness">stateless</a>
     *               function to apply to each element
     * @return the new stream
     */
    <R> Stream<R> map(Function<? super T, ? extends R> mapper);

将T类型的数据转换为R类型的数据

        // 将list中所有元素拼接上abc并打印
        list.stream().map(s-> s+"abc").forEach(System.out::println);

sorted

sorted方法用于排序

    /**
     * Returns a stream consisting of the elements of this stream, sorted
     * according to natural order.  If the elements of this stream are not
     * {@code Comparable}, a {@code java.lang.ClassCastException} may be thrown
     * when the terminal operation is executed.
     *
     * <p>For ordered streams, the sort is stable.  For unordered streams, no
     * stability guarantees are made.
     *
     * <p>This is a <a href="package-summary.html#StreamOps">stateful
     * intermediate operation</a>.
     *
     * @return the new stream
     */
    Stream<T> sorted();

    /**
     * Returns a stream consisting of the elements of this stream, sorted
     * according to the provided {@code Comparator}.
     *
     * <p>For ordered streams, the sort is stable.  For unordered streams, no
     * stability guarantees are made.
     *
     * <p>This is a <a href="package-summary.html#StreamOps">stateful
     * intermediate operation</a>.
     *
     * @param comparator a <a href="package-summary.html#NonInterference">non-interfering</a>,
     *                   <a href="package-summary.html#Statelessness">stateless</a>
     *                   {@code Comparator} to be used to compare stream elements
     * @return the new stream
     */
    Stream<T> sorted(Comparator<? super T> comparator);
        // 将list中的数据进行排序
        list.stream().sorted().forEach(System.out::println);
        // 根据Comparator进行排序(字符串长度倒序)
        list.stream().sorted((o1,o2)-> o2.length() - o1.length()).forEach(System.out::println);

distinct

distinct方法用于去除重复数据

    /**
     * Returns a stream consisting of the distinct elements (according to
     * {@link Object#equals(Object)}) of this stream.
     *
     * <p>For ordered streams, the selection of distinct elements is stable
     * (for duplicated elements, the element appearing first in the encounter
     * order is preserved.)  For unordered streams, no stability guarantees
     * are made.
     *
     * <p>This is a <a href="package-summary.html#StreamOps">stateful
     * intermediate operation</a>.
     *
     * @apiNote
     * Preserving stability for {@code distinct()} in parallel pipelines is
     * relatively expensive (requires that the operation act as a full barrier,
     * with substantial buffering overhead), and stability is often not needed.
     * Using an unordered stream source (such as {@link #generate(Supplier)})
     * or removing the ordering constraint with {@link #unordered()} may result
     * in significantly more efficient execution for {@code distinct()} in parallel
     * pipelines, if the semantics of your situation permit.  If consistency
     * with encounter order is required, and you are experiencing poor performance
     * or memory utilization with {@code distinct()} in parallel pipelines,
     * switching to sequential execution with {@link #sequential()} may improve
     * performance.
     *
     * @return the new stream
     */
    Stream<T> distinct();
        // 去除list中重复元素
        list.stream().distinct().forEach(System.out::println);

Stream流中的distinct方法对于基本数据类型是可以直接出重的,但是对于自定义类型,需要重写hashCode和equals方法来移除重复元素。

match

match方法用于判断数据是否匹配指定的条件

    boolean anyMatch(Predicate<? super T> predicate);

    boolean allMatch(Predicate<? super T> predicate);

    boolean noneMatch(Predicate<? super T> predicate);
        // 判断list中是否所有元素都包含a
        boolean a = list.stream().allMatch(s -> s.contains("a"));
        // 判断list中是否有元素包含a
        boolean a2 = list.stream().anyMatch(s -> s.contains("a"));
        // 判断list中是否所有元素都不包含a
        boolean a3 = list.stream().noneMatch(s -> s.contains("a"));

find

find方法用于查找某些数据

    Optional<T> findFirst();

    Optional<T> findAny();
        // 获取list中第一个元素
        Optional<String> first = list.stream().findFirst();
        System.out.println(first.get());

        // 获取list中任意元素
        Optional<String> any = list.stream().findAny();
        System.out.println(any.get());

max

获取最大值


    /**
     * Returns the maximum element of this stream according to the provided
     * {@code Comparator}.  This is a special case of a
     * <a href="package-summary.html#Reduction">reduction</a>.
     *
     * <p>This is a <a href="package-summary.html#StreamOps">terminal
     * operation</a>.
     *
     * @param comparator a <a href="package-summary.html#NonInterference">non-interfering</a>,
     *                   <a href="package-summary.html#Statelessness">stateless</a>
     *                   {@code Comparator} to compare elements of this stream
     * @return an {@code Optional} describing the maximum element of this stream,
     * or an empty {@code Optional} if the stream is empty
     * @throws NullPointerException if the maximum element is null
     */
    Optional<T> max(Comparator<? super T> comparator);
        // 获取list中最大值
        Optional<String> max = list.stream().max((Comparator.comparingInt(Integer::parseInt)));
        System.out.println(max.get());

min

获取最小值


    /**
     * Returns the minimum element of this stream according to the provided
     * {@code Comparator}.  This is a special case of a
     * <a href="package-summary.html#Reduction">reduction</a>.
     *
     * <p>This is a <a href="package-summary.html#StreamOps">terminal operation</a>.
     *
     * @param comparator a <a href="package-summary.html#NonInterference">non-interfering</a>,
     *                   <a href="package-summary.html#Statelessness">stateless</a>
     *                   {@code Comparator} to compare elements of this stream
     * @return an {@code Optional} describing the minimum element of this stream,
     * or an empty {@code Optional} if the stream is empty
     * @throws NullPointerException if the minimum element is null
     */
    Optional<T> min(Comparator<? super T> comparator);
        // 获取list中最小值
        Optional<String> min = list.stream().min((Comparator.comparingInt(Integer::parseInt)));
        System.out.println(min.get());

reduce

reduce方法将所有数据归纳得到一个数据

    T reduce(T identity, BinaryOperator<T> accumulator);

   
    Optional<T> reduce(BinaryOperator<T> accumulator);

    <U> U reduce(U identity,
                 BiFunction<U, ? super T, U> accumulator,
                 BinaryOperator<U> combiner);

        // 将list中所有元素拼接起来,x的初始值是identity的值,后面x的值是上一次的计算结果
        // y的值是list中的每个元素
        String pinjie = list.stream().reduce("", (x, y) -> x + y);
        // 获取list中长度最长的元素
        String maxLength = list.stream().reduce("", (x, y) -> x.length() > y.length() ? x : y);

reduce方法经常和map方法一起使用

先使用map方法把数据转换成想要的格式,再使用reduce进行处理

        // 获取list中最长元素的长度
        list.stream().map(x -> x.length()).reduce(0, (x,y) -> x > y ? x : y);

mapToInt

将集合中的包装类型转换为基本数据类型,提高程序代码的效率

    IntStream mapToInt(ToIntFunction<? super T> mapper);

    LongStream mapToLong(ToLongFunction<? super T> mapper);

    DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper);
        List<Integer> integerList = Arrays.asList(1,2,3,4,5);
        // 将List<Integer>中的Integer转成int类型
        integerList.stream().mapToInt(s -> s.intValue()).forEach(System.out::println);
        integerList.stream().map(s -> s.intValue()).forEach(System.out::println);
        OptionalInt min1 = integerList.stream().mapToInt(Integer::valueOf).min();
        System.out.println(min1.getAsInt());
        // 会报错
        // OptionalInt min2 = integerList.stream().map(Integer::valueOf).min();

concat

将2个流合并为1个流


    /**
     * Creates a lazily concatenated stream whose elements are all the
     * elements of the first stream followed by all the elements of the
     * second stream.  The resulting stream is ordered if both
     * of the input streams are ordered, and parallel if either of the input
     * streams is parallel.  When the resulting stream is closed, the close
     * handlers for both input streams are invoked.
     *
     * @implNote
     * Use caution when constructing streams from repeated concatenation.
     * Accessing an element of a deeply concatenated stream can result in deep
     * call chains, or even {@code StackOverflowException}.
     *
     * @param <T> The type of stream elements
     * @param a the first stream
     * @param b the second stream
     * @return the concatenation of the two input streams
     */
    public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) {
        Objects.requireNonNull(a);
        Objects.requireNonNull(b);

        @SuppressWarnings("unchecked")
        Spliterator<T> split = new Streams.ConcatSpliterator.OfRef<>(
                (Spliterator<T>) a.spliterator(), (Spliterator<T>) b.spliterator());
        Stream<T> stream = StreamSupport.stream(split, a.isParallel() || b.isParallel());
        return stream.onClose(Streams.composedClose(a, b));
    }
        // 将两个list的流合并为一个新的流
        Stream.concat(list.stream(), integerList.stream()).forEach(System.out::println);

数据收集到集合中

        // 将list中的数据流手机到Set<Integer>中
        Set<Integer> setList = integerList.stream().collect(Collectors.toSet());
        // 将list中的数据流手机到List<Integer>中
        List<Integer> integers = integerList.stream().collect(Collectors.toList());
        // 将list中的数据流手机到指定类型的集合中ArrayList中
        ArrayList<String> strList = integerList.stream().map(s -> s + "a").collect(Collectors.toCollection(ArrayList::new));

数据收集到数组中

        // 将数据收集到Object数组中
        Object[] objArr = integerList.stream().toArray();

        // 将数据收集到String数组中
        String[] strArr = integerList.stream().toArray(String[]::new);

聚合

        // 求和
        Integer sum = list.stream().collect(Collectors.summingInt(Integer::valueOf));

        // 平均值
        Double avg = list.stream().collect(Collectors.averagingInt(Integer::valueOf));

        // 总量
        Long count2 = list.stream().collect(Collectors.counting());

分组操作

根据某个属性,对数据进行分组

List<Person> list = new ArrayList<>();
        list.add(new Person(1, "tom", 12));
        list.add(new Person(2, "tina", 18));
        list.add(new Person(3, "tom", 19));
        list.add(new Person(4, "bob", 21));
        list.add(new Person(5, "tom", 21));
        // 根据name进行分组
        Map<String, List<Person>> nameMap = list.stream().collect(Collectors.groupingBy(Person::getName));
        // 根据年龄分组 >=18一组,没满18一组
        Map<String, List<Person>> ageMap = list.stream().collect(Collectors.groupingBy(p -> p.getAge() >= 18 ? "1" : "0"));

        // 多级分组,先根据name分组,再根据成年未成年分组
        Map<String, Map<String, List<Person>>> group = list.stream().collect(Collectors.groupingBy(Person::getName,
                Collectors.groupingBy(p -> p.getAge() >= 18 ? "成年" : "未成年")));

        group.forEach((k,v)->{
            System.out.println(k+":");
            v.forEach((k2,v2)->{
                System.out.println("  " + k2 + "  :"+v2);
            });
        });

    
class Person{
    private int id;
    private String name;
    private int age;

    public Person(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

分区操作

 Collectors.partitioningBy会根据值是否为true,把集合中的数据分割为两个列表,一个true列表,一个false列表

        // 根据是否成员分区
        Map<Boolean, List<Person>> agePart = list.stream().collect(Collectors.partitioningBy(p -> p.getAge() >= 18));
        agePart.forEach((k,v)->{
            System.out.println(k+":"+ v);
        });

拼接

Collectors.joining会根据指定的连接符,将所有的元素连接成一个字符串

        // 将list中所有id拼接起来:12345
        String ids = list.stream().map(p -> String.valueOf(p.getId())).collect(Collectors.joining());
        // 将list中所有id通过-拼接起来:1-2-3-4-5
        String ids2 = list.stream().map(p -> String.valueOf(p.getId())).collect(Collectors.joining("-"));
        // 将list中所有id加前缀、后缀和拼接符拼接起来 @1-2-3-4-5#
        String ids3 = list.stream().map(p -> String.valueOf(p.getId())).collect(Collectors.joining("-", "@","#"));
      

 并行流

前面写的都是串行流,在一个线程执行

parallelStream其实就是一个并行执行的流,它通过默认的ForkJoinPool,可以提高多线程任务的速度。

获取并行流

        List<String> list = Arrays.asList("a","b","c");
        // 获取并行流的两种方式
        Stream<String> stringStream = list.parallelStream();
        Stream<String> parallel = list.stream().parallel();

使用并行流可能会有数据安全问题

解决方案

  1. 加同步锁

  2. 使用线程安全的容器

  3. 通过Stream中的toArray/collect操作

Optional

Optional是一个没有子类的工具类,Optional是一个可以为null的容器对象,它的主要作用就是为了避免Null检查,防止NullpointerException

        String str = "";
        /* 获取optional对象 */
        // of方法不能传入null值,会报NPE
        Optional<String> op1 = Optional.of(str);
        // ofNullable能传入null值
        Optional<String> op2 = Optional.ofNullable(str);

        // 通过empty方法直接创建一个空的Optional对象
        Optional<Object> op3 = Optional.empty();

        /* isPresent */
        // 判断op1中是否有值
        if(op1.isPresent()){
            // 获取op1中的值,如果为空会报错,所以需要使用isPresent进行判断
            String s1 = op1.get();
        }

        /* orElse */
        // 如果op1中是空值,那么返回值是xxx;如果op1中本来有值,那么返回值是原值
        String s2 = op1.orElse("xxx");

        /* orElseGet */
        // 如果op1中是空值,那么返回值是lambda表达式返回值
        String s3 = op1.orElseGet(() -> "Hello");

        /* ifPresent  */
        // 如果op1中有值,做lambda表达式中的操作
        op1.ifPresent((s)->{
            System.out.println(s);
        });

        // 获取Person中的name,并将name转为大写,person和name都有可能为空
        Person1 person1 = new Person1();
        Optional<Person1> op4 = Optional.of(person1);
        if(op4.isPresent()){
            String s4 = op4.map(Person1::getName)
                    .map(String::toUpperCase)
                    .orElse("");
            System.out.println("s4:"+s4);
        }

日期

老的日期存在的问题

java.util.Date是线程不安全的,

构造方法的year是从1990年开始的

 没有时区支持

LocalDate、LocalTime、LocalDateTime常见用法

        // 根据年月日生成LocalDate
        LocalDate localDate = LocalDate.of(2023,12,31);

        // 获取当前日期
        LocalDate now = LocalDate.now();

        // 生成指定的LocalTime
        LocalTime localTime = LocalTime.of(13, 45, 56, 123456789);

        // 获取当前时间
        LocalTime now1 = LocalTime.now();

        // 根据LocalDate和LocalTime生成时间
        LocalDateTime localDateTime = LocalDateTime.of(now, now1);

        // 生成指定的LocalDateTime
        LocalDateTime localDateTime1 = LocalDateTime.of(2021, 11, 22, 9, 59, 23, 2345);

        // 获取当前时间
        LocalDateTime now2 = LocalDateTime.now();

        int year = now2.getYear();
        int month = now2.getMonthValue();
        int day = now2.getDayOfMonth();
        int week = now2.getDayOfWeek().getValue();
        int hour = now2.getHour();
        int minute = now2.getMinute();
        int second = now2.getSecond();
        int nano = now2.getNano();
        System.out.println("year:" + year);
        System.out.println("month:" + month);
        System.out.println("day:" + day);
        System.out.println("week:" + week);
        System.out.println("hour:" + hour);
        System.out.println("minute:" + minute);
        System.out.println("second:" + second);
        System.out.println("nano:" + nano);


        /* 修改日期 */
        // 修改localDateTime1的year为1998,生成一个新的对象,不会更改老对象的值
        LocalDateTime localDateTime2 = localDateTime1.withYear(1998);

        // 对日期时间进行加减
        // 对localDateTime1加2天
        LocalDateTime localDateTime3 = localDateTime1.plusDays(2);

        // 对localDateTime1减3年
        LocalDateTime localDateTime4 = localDateTime1.minusYears(3);
        LocalDateTime localDateTime5 = localDateTime1.plusYears(-3);
        Assert.assertEquals(localDateTime4,localDateTime5);

        /* 比较日期  */
        // 判断localDateTime2是否大于localDateTime3
        boolean isAfter = localDateTime2.isAfter(localDateTime3);

        // 判断localDateTime4是否小于localDateTime5
        boolean isBefore = localDateTime4.isBefore(localDateTime5);

        // 判断localDateTime4是否等于localDateTime5
        boolean isEqual = localDateTime4.isEqual(localDateTime5);

时间格式化、解析

        LocalDateTime now = LocalDateTime.now();
        // 将LocalDateTime转换为String
        String format = now.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
        System.out.println(format);

        String format1 = now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        System.out.println(format1);

        // 将String转换为LocalDateTime
        LocalDateTime localDateTime = LocalDateTime.parse("2023-06-19 22:56:14", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        System.out.println(localDateTime);

Instant

时间戳,内部保存了从1970年1月1日 00:00:00以来的秒和纳秒

        // 获取当前时间
        Instant now1 = Instant.now();
        // do something
        // ...
        Instant now2 = Instant.now();
        // 时间差
        int time = now2.getNano() - now1.getNano();

时间差

Duration:计算LocalTime的时间差

Period:计算LocalDate的时间差

        LocalTime time1 = LocalTime.of(12, 23, 34, 45);
        LocalTime time2 = LocalTime.of(23, 34, 45, 56);
        // 计算time2和time1的时间差 用后面的减前面的
        Duration duration = Duration.between(time2, time1);
        // 将时间差转换为天数
        long days = duration.toDays();
        // 将时间差转换为小时
        long hours = duration.toHours();
        // 将时间差转换为分钟
        long minutes = duration.toMinutes();
        // 将时间差转换为秒
        long millis = duration.toMillis();
        // 将时间差转换为纳秒
        long nanos = duration.toNanos();

        LocalDate date1 = LocalDate.of(2023, 12, 31);
        LocalDate date2 = LocalDate.of(2021, 10, 1);
        // 计算date2和date1的时间差
        Period period = Period.between(date1, date2);

        // 相差的年数
        int years = period.getYears();
        // 相差的月数
        int months = period.getMonths();
        // 相差的天数
        int days1 = period.getDays();

TemporalAdjuster 时间校正器

        LocalDateTime dateTime = LocalDateTime.of(2023,6,19,20,58,12);
        // 获取dateTime下个月第一天的日期
        LocalDateTime dateTime2 = dateTime.with(TemporalAdjusters.firstDayOfNextMonth());
        System.out.println("dateTime2 = " + dateTime2);

        // 也可以自己实现时间修改逻辑
        LocalDateTime dateTime3 = dateTime.with((temporal -> {
            LocalDateTime localDateTime = (LocalDateTime)temporal;
            return localDateTime.plusMonths(1).withDayOfMonth(1);
        }));
        System.out.println("dateTime3 = " + dateTime3);

时区

Java8 中加入了对时区的支持,LocalDate、LocalTime、LocalDateTime是不带时区的,带时区的日期时间类分别为:ZonedDate、ZonedTime、ZonedDateTime

        // 获取所有时区
        Set<String> zoneIds = ZoneId.getAvailableZoneIds();

        // 获取标准的时间(我国属于东八区,比标准时间快8个小时)
        ZonedDateTime utc = ZonedDateTime.now(Clock.systemUTC());
        System.out.println("utc = " + utc);
        // 计算机默认的时区  Asia/Shanghai
        ZonedDateTime defultTime = ZonedDateTime.now();
        System.out.println("defultTime = " + defultTime);
        ZonedDateTime americaTime = ZonedDateTime.now(ZoneId.of("America/Marigot"));
        System.out.println("americaTime = " + americaTime);

JDK新的日期和时间API的优势

  1. 新版日期时间API中,日期和时间对象是不可变,操作日期不会影响原来的值,而是生成一个新的实例

  2. 提供不同的两种方式,有效的区分了人和机器的操作

  3. TemporalAdjuster可以更精确的操作日期,还可以自定义日期调整期

  4. 线程安全

重复注解

JDK 8引入了重复注解的概念,允许在同一个地方多次使用同一个注解。在JDK 8中使用@Repeatable注解定义重复注解。

@MyAnnotation("zhangsan")
@MyAnnotation("lisi")
public class AnnotationTest {

    @MyAnnotation("abc")
    @MyAnnotation("def")
    public void test(){
        
    }

    public static void main(String[] args) throws NoSuchMethodException {
        Class<AnnotationTest> clazz = AnnotationTest.class;
        // 获取类上的注解
        MyAnnotation[] clazzAnnotations = clazz.getDeclaredAnnotationsByType(MyAnnotation.class);
        // 获取clazzAnnotations中第一个注解的值
        String value = clazzAnnotations[0].value();

        // 获取方法上的注解
        MyAnnotation[] methodAnnotations = clazz.getMethod("test").getAnnotationsByType(MyAnnotation.class);
    }
}

@Repeatable(MyAnnotations.class)
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation{
    String value();
}

@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotations{
    MyAnnotation[] value();
}

类型注解

JDK 8为@Target元注解新增了两种类型: TYPE_PARAMETER , TYPE_USE 。

  • TYPE_PARAMETER :表示该注解能写在类型参数的声明语句中。 类型参数声明如: <T> 

  • TYPE_USE :表示注解可以再任何用到类型的地方使用。

public class AnnotationTest2 {
    
    /**
     * <T>
     * <T>
     * @param t
     * @param <T> 表示是个泛型方法,就像有static修饰的方法是个静态方法一样;
     *           表示传入参数有泛型,<T>存在的作用,是为了保证参数中能够出现T这种数据类型。
     */
    public <T> void test(T t){

    }

    public <@A T> void test2(T t){

    }

    public void test3(@B String name, @B Integer age){

    }

}

// 表示该注解能写在泛型中
@Target(ElementType.TYPE_PARAMETER)
@interface A{

}

// 在任何用到类型的地方使用
@Target(ElementType.TYPE_USE)
@interface B{

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/682513.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Unreal 5 实现Data Table 数据表格的使用

数据是一个游戏重要的组成部分&#xff0c;按分工&#xff0c;数据会交由策划去配置&#xff0c;所以&#xff0c;你的数据不可能直接写到蓝图里面&#xff0c;而是有一个专门的数据表格去配置。 大家通用的方式是策划在Excel内编辑完成&#xff0c;然后通过工具生成游戏需要使…

RabbitMQ安装以及SpringBoot整合RabbitMQ

SpringBootMQ整合Rabbit RabbitMQ安装以及SpringBoot整合 1、Docker安装RabbitMQ #拉取rabbitmq镜像 docker pull rabbitmq#启动RabbitMQ docker run -d --hostname my-rabbit --name rabbit -p 15672:15672 -p 5672:5672 rabbitmq#安装图形化插件 #进入容器 docker exec -i…

ubantu16.04搭建Qt环境

目录 ubantu16.04搭建Qt环境 下载opensource 安装qmake 安装g以及依赖库 配置环境 编译原码 安装make 查看qmake是否安装 配置环境变量 qmake编译c/c代码 安装qtcreator Linux编译Qt代码 ubantu16.04搭建Qt环境 下载opensource 「下载 qt-everywhere-opensource-s…

用于配电室的内部环境监控系统设计与产品选型

摘要&#xff1a;配电室是配电系统的重要组成部分&#xff0c;影响着电力用户的用电需求&#xff0c;需要保障其安全、可靠运行。针对配电室内部环境进行了深入分析&#xff0c;设计了一种内部环境监控系统。通过该系统可以采集配电室内部的温度、湿度、液位、SF6气体和烟雾&am…

伪随机码详解

本专栏包含信息论与编码的核心知识&#xff0c;按知识点组织&#xff0c;可作为教学或学习的参考。markdown版本已归档至【Github仓库&#xff1a;https://github.com/timerring/information-theory 】或者公众号【AIShareLab】回复 信息论 获取。 文章目录 伪随机码伪随机序列…

期末复习【网络安全】

期末复习【网络安全】 前言推荐期末复习第1章 引言1.1 计算机安全概念1.2 OSI安全体系结构 61.3 安全攻击 71.3.1 被动攻击1.3.2 主动攻击 第2章 对称加密和消息机密性2.1 对称加密原理 232.1.3 Feistel密码结构 25 2.2 对称分组加密算法 272.2.1 数据加密标准2.2.2 三重DES2.2…

【C#】并行编程实战:任务并行性(下)

本章继续介绍任务并行性&#xff0c;因篇幅所限&#xff0c;本章为下篇。 6、处理任务异常 所有优秀的程序员都擅长高效地处理异常&#xff0c;这也是并行编程最重要的方面之一。任务并行库&#xff08;TPL&#xff09;提供了一种高效的设计来处理异常&#xff1a;任务中发生的…

山西电力市场日前价格预测【2023-06-26】

日前价格预测 预测明日&#xff08;2023-06-26&#xff09;山西电力市场全天平均日前电价为395.92元/MWh。其中&#xff0c;最高日前价格为477.34元/MWh&#xff0c;预计出现在19: 00。最低日前电价为309.90元/MWh&#xff0c;预计出现在13: 00。以上预测仅供学习参考&#xff…

Android中的EventBus变迁史

Android中的EventBus变迁史 什么是EventBus&#xff1f; EventBus是软件架构中使用的一种通信机制&#xff0c;用于促进系统内不同组件或服务之间的事件交换。它为组件提供了一种发布和订阅事件的方法&#xff0c;从而实现松散耦合和异步通信。 EventBus充当中央集线器或消息…

SpringBoot 性能优化的主要组件及特点

SpringBoot 性能优化的主要组件及特点 简介 在开发应用程序时&#xff0c;性能是一个非常重要的因素。如果应用程序的性能不佳&#xff0c;那么它可能会影响用户的体验&#xff0c;甚至会导致系统崩溃。而在 SpringBoot 应用程序中&#xff0c;我们可以通过一些组件来优化应用…

PCB板为什么要做表面处理?你知道吗

PCB板为什么要做表面处理&#xff1f; 由于PCB上的铜层很容易被氧化&#xff0c;因此生成的铜氧化层会严重降低焊接质量&#xff0c;从而降低最终产品的可靠性和有效性&#xff0c;为了避免这种情况的发生&#xff0c;需要对PCB进行表面处理。 常见的表面处理方式包括OSP、喷…

Java虚拟机(JVM)面试题

JVM 说一下 JVM 的主要组成部分及其作用&#xff1f; 总体来说&#xff0c;方法区和堆是所有线程共享的内存区域&#xff1b;而虚拟机栈、本地方法栈和程序计数器的运行是线程私有的内存区域&#xff0c;运行时数据区域就是我们常说的JVM的内存 类加载子系统&#xff1a;根据…

SpringBoot 使用 MockMvc 进行 Web 集成测试

SpringBoot 使用 MockMvc 进行 Web 集成测试 在 SpringBoot 应用程序中&#xff0c;我们可以使用 MockMvc 进行 Web 集成测试。MockMvc 是一个测试框架&#xff0c;可以模拟 HTTP 请求和响应&#xff0c;并且可以使用 Spring MVC 的控制器进行测试。MockMvc 可以让我们测试 Sp…

Apollo分布式配置中心(一)

目录 什么是配置&#xff1f; 什么是配置中心&#xff1f; 配置中心需要满足条件 什么是Apollo&#xff1f; Apollo特性 Apollo执行流程 Apollo安装 Git地址&#xff1a; 用户手册&#xff1a; 环境准备&#xff1a; 使用idea 的git clone 拉取源码 修改Apollo\scripts…

面对AI,要(能)当“杜兰特”吗?

大家好&#xff0c;我是老三&#xff0c;先说一下这篇文章没什么干货&#xff0c;就是一篇随笔。 今天闲来无事&#xff0c;在掘金上刷到一篇文章&#xff1a;GPT-4都来了&#xff0c;我们还需要刷算法题和背八股文吗&#xff1f;&#xff1a;https://juejin.cn/post/72111208…

总结Linux 中实时查看日志的3种方法

一、前言 我们大家应该都知道如何在 Linux 中查看文件&#xff0c;比如可以使用 cat 或者 less 命令。 这对于查看静态文件来说是可以的&#xff1b;日志文件是动态的&#xff0c;其内容随时会变化&#xff0c;要监测日志文件&#xff0c;需要在日志文件内容改变时也能实时看到…

配置右键菜单功能 :一键csv转excel

配置右键菜单功能 &#xff1a;一键csv转excel 无需点开文件&#xff0c;双击即可以生成新的excel文件 步骤&#xff1a; 1、配置Python&#xff0c;安装依赖库 pip install openpyxl pip install pandas2、创建Python文件 csv_to_excel.py # -*- coding:utf-8 -*- impor…

【光伏集群、需求响应】市场模式下光伏用户群的电能共享与需求响应模型(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

团体程序设计天梯赛-练习集L2篇⑤

&#x1f680;欢迎来到本文&#x1f680; &#x1f349;个人简介&#xff1a;Hello大家好呀&#xff0c;我是陈童学&#xff0c;一个与你一样正在慢慢前行的普通人。 &#x1f3c0;个人主页&#xff1a;陈童学哦CSDN &#x1f4a1;所属专栏&#xff1a;PTA &#x1f381;希望各…

Vue+leaflet笔记之克里金插值

Vueleaflet笔记之克里金插值 文章目录 Vueleaflet笔记之克里金插值开发环境代码简介组件库简介安装依赖库实现思路turf生成随机数据方法一&#xff1a;kriging库插值方法二&#xff1a;kriging-contour插值(矢量)方法三&#xff1a;kriging-contour插值(栅格) 本文介绍了Web端使…