07-java8剖析(一)

2022-07-18

07-java8剖析(一)

java8剖析(一)

一. lambda表达式

1.1 lambda表达式

在java中,我们无法将函数作为参数传递给一个方法,也无法声明返回一个函数的方法

  • 基本结构

    (param1, param2, param3) -> {
    
    }
    
  • lamda表达式仍然是对象

  • lambda表达式传递行为,而不仅仅是值

    • 提升抽象层次
    • API重用性更好
    • 更加灵活

1.2 函数式编程

函数式编程的核心在于,由方法内部实现的业务逻辑改为由调用者声明。方法内部仅提供公用的逻辑。

是一种更高层次的抽象

1.3 方法引用

是一种语法糖,对lamda表达式语法的一种简写

List<String> list = Arrays.asList("mhn", "nhh");
list.forEach(System.out:: println);
  • 类名:: 静态方法名

  • 引用名::实例方法名

  • 类名:: 实例方法名

  • 构造方法引用

    类名::new

二. 函数式接口

  • 如果一个接口只有一个抽象方法,那么该接口就是一个函数式接口
  • 如果我们在一个接口上声明了FunctionalInterface注解,那么编译器就会按照函数式接口的定义来要求该接口
  • 如果一个接口只有一个抽象方法,但我们没有声明FunctionalInterface注解。编译器仍会看作函数式接口

重写的object类中的方法不累计

从jdk8开始,接口也可以拥有方法实现,即default方法或者静态方法,这种类型的方法也不计数

函数式接口的创建可以通过三种方式创建

<p>Note that instances of functional interfaces can be created with
* lambda expressions, method references, or constructor references.
  1. lamda表达式

    List<Integer> list = Arrays.asList(1, 2, 3);
    list.forEach(j -> System.out.println(j));
    
  2. 方法引用

    List<Integer> list = Arrays.asList(1, 2, 3);
    list.forEach(System.out::println);
    
  3. 构造方法引用

2.1 Consumer接口

@FunctionalInterface
public interface Consumer<T> {

    void accept(T t);

    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

是一个函数式接口, 设计了一个无返回值的accept方法,用来对传入的参数进行某种操作

2.2 Function接口

@FunctionalInterface
public interface Function<T, R> {

    /**
     * Applies this function to the given argument.
     *
     * @param t the function argument
     * @return the function result
     */
    R apply(T t);

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

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

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

是一个函数式接口,实现了一个有返回值的apply方法,用来对传入的参数进行操作并返回一个操作的结果

  • 创建实现Function接口的lamda对象

    //普通匿名内部类
    Function<String, String> fun3 = new Function<String, String>() {
                @Override
                public String apply(String s) {
                    return s.toUpperCase();
                }
    };
    
    //lambda表达式的第一个参数一定是调用toUpperCase的字符串对象
    //String为返回值
    Function<String, String> func = String::toUpperCase;
    
    //lambda表达式
    Function<String, String> fun2 = str -> {
    return str.toUpperCase();
    };
    
  • 测试案例

    public int compute(int a, Function<Integer, Integer> fun1, Function<Integer, Integer> fun2) {
            return fun1.compose(fun2).apply(a);
        }
    
        public int compute2(int a, Function<Integer, Integer> fun1, Function<Integer, Integer> fun2) {
            return fun1.andThen(fun2).apply(a);
        }
    
    
     public static void main(String[] args) {
    
            Lambda lambda = new Lambda();
            // 使用匿名内部类
            // 实际上就是传递了一个Function接口的实现对象,重写了apply方法
            //因此如果用lambda表达式,看起来是传递了一种行为,即apply里面重写的行为
            lambda.compute(1, new Function<Integer, Integer>() {
                @Override
                public Integer apply(Integer integer) {
                    return integer * 2;
                }
            });
            int compute = lambda.compute(1, value -> value * 2);
            System.out.println(compute);
    
            System.out.println(lambda.compute(2, value -> value * 3, value -> value * value));
            System.out.println(lambda.compute2(2, value -> value * 3, value -> value * value));
    
    
        }
    

2.3 BiFunction接口

类似于Function,是Function的一种特例,接受两个参数,返回一个值

@FunctionalInterface
public interface BiFunction<T, U, R> {
   
    R apply(T t, U u);

    default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t, U u) -> after.apply(apply(t, u));
    }
}

2.4 Predicate接口

根据输入的参数,返回true或者false

/**
     * Evaluates this predicate on the given argument.
     *
     * @param t the input argument
     * @return {@code true} if the input argument matches the predicate,
     * otherwise {@code false}
     */
    boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }

相当于逻辑与

default Predicate<T> negate() {
        return (t) -> !test(t);
    }

相当于逻辑非

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

相当于逻辑或

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

判断两个obj的equals

2.5 Supplier接口

不接受任何参数,返回一个结果

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

2.6 BinaryOperator接口

接受两个参数,返回一个结果。参数和结果的类型相同

是BiFunction的一种特例

public static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator) {
        Objects.requireNonNull(comparator);
        return (a, b) -> comparator.compare(a, b) <= 0 ? a : b;
    }

求两个参数的较小者

举例如下:

System.out.println(bot.getShort("mhn", "niuhonghong", (a, b) -> a.length() - b.length()));

public String getShort(String str1, String str2, Comparator<String> comparator){
		return BinaryOperator.minBy(comparator).apply(str1, str2);
	}

三. Optional类

是一个包装的容器类,可以方便的处理空和非空的情况

通常作为方法的返回值,用来规避空指针异常

			Optional<String> optional = Optional.of("hello");
        //不推荐使用这种
        if (optional.isPresent()){
            System.out.println(optional.get());
        }
        //推荐使用
        //如果为空不输出,不为空输出
        optional.ifPresent(item -> System.out.println(item));
        //如果为空输出other
        optional.orElse("world");
        //如果为空输出
        optional.orElseGet(() -> "world");
  • 测试案例

    class Company{
        private List<String> persons;
    
        /**
         * @return the persons
         */
        public List<String> getPersons() {
            return persons;
        }
    
        /**
         * @param persons the persons to set
         */
        public void setPersons(List<String> persons) {
            this.persons = persons;
        }
    
    }
    
    
    public class OptionalTest {
        public static void main(String[] args) {
            List<String> list = Arrays.asList("mhn", "nhh");
            Company company = new Company();
            company.setPersons(list);
            Optional<Company> cOptional = Optional.ofNullable(company);
            System.out.println(cOptional.map(c -> c.getPersons()).orElse(Collections.EMPTY_LIST));
        }
    }
    

    如果不为空返回集合,为空返回空集合,一句代码完成

四. 默认方法

jdk8以后,接口中可以定义两种方法的实现,一种是default修饰的默认方法,另一种静态方法

为了向前兼容,引入了默认方法,在扩充接口功能的前提下,不影响之前版本的代码

4.1 情况一

package java8;

/**
 * @Author: M˚Haonan
 * @Date: 2019-05-15 10:53
 * @Description: 默认方法测试
 */
interface MyInterface1{
    default void myMethod(){
        System.out.println("MyInterface1");
    }
}

interface MyInterface2{
    default void myMethod(){
        System.out.println("MyInterface2");
    }
}

public class DefaultMethodTest implements MyInterface1, MyInterface2{

    /**
     * 如果既实现了interface1,又实现了interface2,里面重名的方法必须重写
     */
    @Override
    public void myMethod() {
        MyInterface2.super.myMethod();
    }

    public static void main(String[] args) {
        DefaultMethodTest test = new DefaultMethodTest();
        test.myMethod();
    }
}

4.2 情况二

interface MyInterface1{
    default void myMethod(){
        System.out.println("MyInterface1");
    }
}

class impl implements MyInterface1{
    public void myMethod(){
        System.out.println("impl1");
    }
}

interface MyInterface2{
    default void myMethod(){
        System.out.println("MyInterface2");
    }
}

public class DefaultMethodTest extends impl implements MyInterface2{


    public static void main(String[] args) {
        DefaultMethodTest test = new DefaultMethodTest();
        test.myMethod();
    }
}

继承和实现上,优先选择继承,因为相当于自己内部重写了接口的方法

如果父类不重写MyMethod方法,也会报错,冲突的方法名,因此重写之后相当于子类重写了MyMethod

五. Stream流

5.1 基本概念

  • 流由三部分构成

    • 零个或多个中间操作
    • 终止操作
  • 流操作的分类

    • 惰性求值
    • 及早求值

    流的执行是lazy的,只有终止操作的方法被调用,前面的惰性方法才会执行,否则不执行

    中间操作都会返回一个Stream对象

    终止操作则不会返回Stream对象

  • 流的三种创建方式

    public static void main(String[] args) {
            //通过静态方法of创建Stream
            Stream stream1 = Stream.of("hello", "world", "hello world");
            String[] arr = new String[]{"hello", "world", "hello world"};
            Stream stream2 = Stream.of(arr);
            //通过Arrays的stream方法
            Stream stream3 = Arrays.stream(arr);
            //通过集合创建
            List<String> list = Arrays.asList(arr);
            Stream stream4 = list.stream();
        }
    
  • 流和集合

    • 集合关注的是数据与数据存储本身
    • 流关注的是数据的计算
    • 流无法重复使用或消费
  • 并行流

    流的操作是多线程的

  • 流的短路

    List<String> list1 = Arrays.asList("hello", "world", "hello world");
    list1.stream().mapToInt(String::length).filter(length -> length == 5).findFirst().ifPresent(System.out::println);
    list1.stream().mapToInt(item -> {
    		int length = item.length();
    		System.out.println(item);
      	return length;
    }).filter(length -> length == 5).findFirst().ifPresent(System.out::println);
    

    输出结果为hello 5

    findFirst()为一个短路的操作,既一个元素满足条件后,后面的元素就不会执行

5.2 方法剖析

  • stream转成数组

    Stream<String> stream5 = Stream.of("hello", "world", "helloWorld");
    String[] strings = stream5.toArray(length -> new String[length]);
    Arrays.asList(strings).forEach(System.out:: println);
    String[] strings2 = stream5.toArray(String[]::new);
    
  • stream转成list

     List<String> list3 = stream5.collect(Collectors.toList());
     //原始方式转list
     //第一个为返回的结果,是一个list
     //第二个参数为BiConsumer,接受两个参数,第一个参数为list,第二个参数为list中元素的类型,无返回值。相当于每次往list中添加元素
     //第三个参数为BiConsumer,两个参数均为list,把第二步添加的list中的元素汇总
     List<String> list4 = stream5.collect(() -> new ArrayList<>(), (thisList, ele) -> thisList.add(ele),
     (thisList1, thisList2) -> thisList1.addAll(thisList2));
     //使用方法引用
     List<String> lsit5 = stream5.collect(ArrayList:: new, ArrayList:: add, ArrayList:: addAll);
    
  • stream拼接字符串

    Stream<String> stream5 = Stream.of("hello", "world", "helloWorld");
    String str = stream5.collect(StringBuilder:: new, StringBuilder:: append, StringBuilder:: append).toString();
    System.out.println(str);
    String str2 = stream5.collect(Collectors.joining());
    System.out.println(str2);
    
  • flatMap方法

    扁平化的一种手段

    Stream<List<Integer>> stream6 = Stream.of(Arrays.asList(1), Arrays.asList(2, 3), Arrays.asList(4, 5, 6));
            stream6.flatMap(thisList -> thisList.stream()).map(i -> i * i).forEach(System.out:: println);
    
  • generate()

    生成stream流的方法

    Stream<String> stream7 = Stream.generate(UUID.randomUUID():: toString);
    stream7.findFirst().ifPresent(System.out:: println);
    
  • iterate

    生成一个无线的串行流,配合limit使用

    Stream.iterate(1, i -> i + 2).limit(6).forEach(System.out:: println);
    
    int sum1 = Stream.iterate(1, i -> i + 2).limit(6).map(i -> i * 2).skip(2).limit(2).reduce(0, Integer:: sum);
            System.out.println(sum1);
    
  • IntSummaryStatistics

    存取中间结果,便于下一步分开操作

    IntSummaryStatistics summaryStatistics = Stream.iterate(1, i -> i + 2).limit(6).mapToInt(i -> i * 2).skip(2).limit(2).summaryStatistics();
            int max = summaryStatistics.getMax();
            System.out.println(max);
    
  • distinct

    对元素进行去重,根据对象的equals和hashcode方法

    因此需要重写equals和hashcode

    Stream<Teacher> stream = Stream.of(new Teacher("mhn", 23), new Teacher("mhn", 24));
            stream.distinct().forEach(item -> System.out.println(item.getName()));
    
  • collect

    • 分组

      //分组统计单词数量(wordcount)
      List<String> list3 = Arrays.asList("hello welcome", "hello world", "hello world hello", "hello welcome");
      
      //保存统计结果
      Map<String, Integer> result = new HashMap<>();
      
      //依次进行切分,扁平化,分组,统计数量的操作
      //flatMap会将多个stream对象变为一个stream对象
      list3.stream().flatMap(str -> Arrays.stream(str.split(" "))).collect(Collectors.groupingBy(s -> s)).
                      forEach((k, v) -> result.put(k, v.size()));
      
      System.out.println(result);
      
    
      > ```java
      > public static <T, K> Collector<T, ?, Map<K, List<T>>>
      > groupingBy(Function<? super T, ? extends K> classifier) {
      >  return groupingBy(classifier, toList());
      > }
      > 
      > <R, A> R collect(Collector<? super T, A, R> collector);
      > ```
      >
      > 按Function的返回值进行分组
      >
    > 接受一个Function对象,Function的参数类型为流中的类型,返回值自定义,
      >
      > 那么最后返回的类型为R,这里即为Map,k是Function的返回值(即分组条件),V为List<Function参数类型,即流类型>
      >
      > 理解起来就是,建立一个Map,k为分组条件,v为符合条件的元素的list
    
    ```java
      Map<String, Long> collect = list3.stream().flatMap(str -> Arrays.stream(str.split(" "))).
                      collect(Collectors.groupingBy(s -> s, Collectors.counting()));
              System.out.println(collect);
    

    更加简便的写法,借用了groupBy的重载方法

    public static <T, K, A, D>
        Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier,Collector<? super T, A, D> downstream) {
            return groupingBy(classifier, HashMap::new, downstream);
      }
    

    第二个参数为一个Collector对象

    然后调用Collectors.counting(),统计每个分组的数量

    • 分区

      是分组的一种特例,只会分成2组,true或者false

      List<String> list4 = Arrays.asList("hi", "hello", "你好");
      Map<Boolean, List<String>> collect1 = list4.stream().collect(Collectors.partitioningBy(str -> str.length() > 2));
              System.out.println(collect1);
      

5.3 Collector接口

是collect方法的参数类型

A mutable reduction operation that accumulates input elements into a mutable result container.
  • 是一个可变的汇聚操作,将输入元素累计到一个可变的结果容器中。它会在所有输入元素都处理完毕之后,将累积的结果转换为一个最终的表示(可选操作)。支持并行和串行两种方式。

  • 有四个关键函数

    • Supplier<A> supplier();

      生成一个可变结果容器

      故A指结果容器的类型

    • BiConsumer<A, T> accumulator();

      累加器。将每个元素添加到结果容器中

    • BinaryOperator<A> combiner();

      并行流中,用于聚合结果,相当于map-reduce的reduce

      只有用在并行流中,每次返回一个新的结果容器,或者将其中一个折叠到另一个中返回

    • Function<A, R> finisher();

      将中间操作转变为最终结果

    详细看官方文档

  • 为了保证串行与并行操作结果的等价性,Collector函数需要满足两个条件:identity(同一性)与associativity(结合性)

  • 同一性

    The identity constraint says that for any partially accumulated result,
    combining it with an empty result container must produce an equivalent
    result.  That is, for a partially accumulated result {@code a} that is the
    result of any series of accumulator and combiner invocations, {@code a} must
    be equivalent to {@code combiner.apply(a, supplier.get())}.
    
    • a == combiner.apply(a, supplier.get())

    a代表每次的结果

    supplier.get()创建一个新的空的结果容器,然后将a添加进去,就相当是a本身

    (List<String> list1, List<String> list2) -> {list2.addAll(list1); return list1;}

  • 结合性

    * <p>The associativity constraint says that splitting the computation must
     * produce an equivalent result.  That is, for any input elements {@code t1}
     * and {@code t2}, the results {@code r1} and {@code r2} in the computation
     * below must be equivalent:
     * <pre>{@code
     *     A a1 = supplier.get();
     *     accumulator.accept(a1, t1);
     *     accumulator.accept(a1, t2);
     *     R r1 = finisher.apply(a1);  // result without splitting
     *
     *     A a2 = supplier.get();
     *     accumulator.accept(a2, t1);
     *     A a3 = supplier.get();
     *     accumulator.accept(a3, t2);
     *     R r2 = finisher.apply(combiner.apply(a2, a3));  // result with splitting
     * } </pre>
    

    accumulator.accept(a1, t1);

    a1是每次累加操作的中间结果,t1代表流中待处理的下一个元素

  • 泛型分析

    public interface Collector<T, A, R> {}
    

    T: 进行汇聚操作的输入元素的类型,即流中每一个元素的类型

    A: 汇聚操作的可变的累加的类型,即中间操作生成的结果的类型

    R: 结果类型,即最后生成结果的类型

    R container = collector.supplier().get();
    for (T t : data)
          collector.accumulator().accept(container, t);
    return collector.finisher().apply(container);
    

六. Comparator


标题:07-java8剖析(一)
作者:mahaonan
地址:https://mahaonan.fun/articles/2022/07/18/1658147076614.html