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

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{

}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值