Lambda表达式

作用
  • 避免匿名内部类定义过多
  • 可以让你的代码看起来很简洁
  • 去掉了一堆没有意义的代码,只留下核心的逻辑
  • 其实质属于函数式编程的概念
函数式接口

定义:

  • 任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口
//例如;
public interface Runnable{
    public abstract void run();//默认都是抽象的,可以不写public abstract
}
  • 对于函数式接口,我们可以通过lambda表达式来创建该接口对象
  • 四大核心内置接口
Consumer<T> : 消费型接口(无返回值,有去无回)
         void accept(T t);
Supplier<T> : 供给型接口
     T get();

Function<T,R> : 函数型接口
    R apply(T t);

Predicate<T> : 断言型接口
    boolean test(T t);
        
//这四大核心接口在之后Stream中常用

//以上四大核心内置接口是我们日常开发中经常要用到的,同时,它们还有一些变种,如:
//BiConsumer,Consumer的增强版,接受两个参数:
@FunctionalInterface
public interface BiConsumer<T, U> {
   void accept(T t, U u);
}
//BiFunction类似,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));
  }
}
使用
//推导lambda表达式
public class TestLambda {
    //3.静态内部类
    static class Like2 implements ILike{
        @Override
        public void lambda() {
            System.out.println("i like lambda2");
        }
    }
    public static void main(String[] args) {
        ILike like =new Like();
        like.lambda();
        like =new Like2();
        like.lambda();
        //4.局部内部类
        class Like3 implements ILike{
            @Override
            public void lambda() {
                System.out.println("i like lambda3");
            }
        }
        like =new Like3();
        like.lambda();
        //5.匿名内部类,没有类的名称,必须借助接口或者父类
        like =new ILike() {
            @Override
            public void lambda() {
                System.out.println("i like lambda4");
            }
        };
        like.lambda();
        //6.用lambda简化 因为只有一个接口所以省去类和接口名字重写那部分,只留下方式的具体实现部分
        like = ()->{
            System.out.println("i like lambda5");
        };
        like.lambda();
    }
}
//1.定义一个函数式接口
interface ILike{
    void lambda();
}
//2.实现类
class Like implements ILike{
    @Override
    public void lambda() {
        System.out.println("i like lambda");
    }
}
简化

简化规则与总结:

  1. lambda表达式只能有一行代码的情况下才能简化成为一行,若果有多行,那么就用代码块。(由{}包裹)
  2. 前提是接口为函数式接口(只有一个抽象方法)
  3. 多个参数也可以去掉参数类型,要去掉就都去掉,必须加括号
    如love =(i,j)->{ System.out.println(“i love you too !!”+i+j); };
public class TestLamdba2 {
    public static void main(String[] args) {
        //1.lambda表示简化
        Ilove love =(int i)->{
            System.out.println("i love you !!"+i);
        };
        //简化1.参数类型
        love =(i)->{
            System.out.println("i love you too !!"+i);
        };
        //简化2.简化括号
        love =i->{
            System.out.println("i love you too 1 !!"+i);
        };
        //简化3.去掉花括号
        love =i-> System.out.println("i love you too a !!"+i);
        /*总结:
        1.lambda表达式只能有一行代码的情况下才能简化成为一行,若果有多行,那么就用代码块。(由{}包裹)
        2.前提是接口为函数式接口(只有一个抽象方法)
        3.多个参数也可以去掉参数类型,要去掉就都去掉,必须加括号
        如love =(i,j)->{ System.out.println("i love you too !!"+i+j); };
        */
        love.love(521);
    }
}
interface Ilove{
    void  love(int a);
}
class Love implements Ilove{

    @Override
    public void love(int a) {
        System.out.println("i love you !!");
    }
}

方法引用

说明
  1. 所谓方法引用,是指如果某个方法签名和接口恰好一致,就可以直接传入方法引用。
  2. 方法签名只看参数类型和返回类型,不看方法名称,也不看类的继承关系。
作用
  1. 方法引用可以理解为Lambda表达式的另外一种表现形式。
  2. 方法引用通过方法的名字来指向一个方法。
  3. 方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
  4. 方法引用使用一对冒号 ::
方法的分类

类型

语法

对应的Lambda表达式

静态方法引用

类名::staticMethod

(args) -> 类名.staticMethod(args)

实例方法引用

inst::instMethod

(args) -> inst.instMethod(args)

对象方法引用

类名::instMethod

(inst,args) -> 类名.instMethod(args)

构建方法引用

类名::new

(args) -> new 类名(args)

方法引用举例
  1. 静态方法引用
public class Main {
    public static void main(String[] args) {
        String[] array = new String[] { "Apple", "Orange", "Banana", "Lemon" };
        Arrays.sort(array, Main::cmp);//Comparator<String>接口定义的方法是int compare(String, String),和静态方法int cmp(String, String)相比,除了方法名外,方法参数一致,返回类型相同,可以直接把方法名作为Lambda表达式传入
        System.out.println(String.join(", ", array));
    }

    static int cmp(String s1, String s2) {
        return s1.compareTo(s2);
    }
}
  1. 实例方法引用
@Data
class User {
    private String name;
    private Integer age;

    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
    
    public getName(){
        return name;
    }
}
public class MethodReference {
    public static void main(String[] args) {
        User user = new User("巴巴",32);
        Supplier<String> supplier = () -> user.getName();
        System.out.println("Lambda表达式输出结果:" + supplier.get());

        Supplier<String> supplier2 = user::getName;
        System.out.println("实例方法引用输出结果:" + supplier2.get());
    }
}
  1. 特定类的任意对象的方法引用
public class Main {
    public static void main(String[] args) {
        String[] array = new String[] { "Apple", "Orange", "Banana", "Lemon" };
        Arrays.sort(array, String::compareTo);
        System.out.println(String.join(", ", array));
    }
}

其中compareTo的方法定义为:

public int compareTo(String o) {
    ...
}

因为实例方法有一个隐含的this参数,String类的compareTo()方法在实际调用的时候,第一个隐含参数总是传入this,相当于静态方法:

public static int compareTo(this, String o);

也可以理解为第一个参数是实例方法的参数调用者,而第二个参数是实例方法的参数。x.compareTo(y);x为参数调用者,y为实例方法的参数。所以,String.compareTo()

方法也可作为方法引用传入。

  1. 构造方法引用
1 Supplier<List<User>> userSupplier = () -> new ArrayList<>();
2 List<User> user = userSupplier.get();
3 
4 Supplier<List<User>> userSupplier2 = ArrayList<User>::new;    // 构造方法引用写法
5 List<User> user2 = userSupplier.get();

Stream

介绍:
  1. Stream为全新的流式API:Stream API。它位于java.util.stream包中。这个Stream不同于java.ioInputStreamOutputStream,它代表的是任意Java对象的序列。
  2. 这个StreamList也不一样,List存储的每个元素都是已经存储在内存中的某个Java对象,而Stream输出的元素可能并没有预先存储在内存中,而是实时计算出来的。
  3. Stream API支持函数式编程和链式操作;
  4. Stream可以表示无限序列,并且大多数情况下是惰性求值的。
操作步骤:
  1. 创建Stream----从一个数据源,如集合、数组中获取流。
  2. 中间操作----一个操作的中间链,对数据源的数据进行操作。
  3. 终止(聚合)操作----一个终止操作,执行中间操作链,并产生结果。

**注:**一般只有到聚合操作的时候才会触发计算,中间操作都是惰性计算并不是真正的计算

创建:
  1. 使用Stream.of()创建----元素固定
public static void main(String[] args) {
    Stream<String> stream = Stream.of("A", "B", "C", "D");
    // forEach()方法相当于内部循环调用,
    // 可传入符合Consumer接口的void accept(T t)的方法引用:
    stream.forEach(System.out::println);
}
  1. 基于数组或Collection----元素固定
public static void main(String[] args) {
    Stream<String> stream1 = Arrays.stream(new String[] { "A", "B", "C" });
    Stream<String> stream2 = List.of("X", "Y", "Z").stream();
    stream1.forEach(System.out::println);
    stream2.forEach(System.out::println);
}
//对于Collection(List、Set、Queue等),直接调用stream()方法就可以获得Stream。
  1. 基于Supplier----可以表示无限序列
public static void main(String[] args) {
    Stream<Integer> natual = Stream.generate(new NatualSupplier());//Stream.generate(Supplier<String> sp);该方法需要传入一个Supplier对象
    // 注意:无限序列必须先变成有限序列再打印:
    natual.limit(20).forEach(System.out::println);
}
}
class NatualSupplier implements Supplier<Integer> {
int n = 0;
public Integer get() {
    n++;
    return n;
}
  1. 其他方法
//1.Files类的lines()方法可以把一个文件变成一个Stream,每个元素代表文件的一行内容:
try (Stream<String> lines = Files.lines(Paths.get("/path/to/file.txt"))) {
    ...   
}
//2.正则表达式的Pattern对象有一个splitAsStream()方法,可以直接把一个长字符串分割成Stream序列而不是数组:
Pattern p = Pattern.compile("\\s+");
Stream<String> s = p.splitAsStream("The quick brown fox jumps over the lazy dog");
s.forEach(System.out::println);
  1. 基本类型
//基本类型无法使用泛型,使用Integer会频繁装箱拆箱,Java标准库提供了IntStream、LongStream和DoubleStream这三种使用基本类型的Stream

// 将int[]数组变为IntStream:
IntStream is = Arrays.stream(new int[] { 1, 2, 3 });
// 将Stream<String>转换为LongStream:
LongStream ls = List.of("1", "2", "3").stream().mapToLong(Long::parseLong);
中间操作:
  1. 使用map–重要
  • 所谓map操作,就是把一种操作运算,映射到一个序列的每一个元素上。它把一个Stream转换为另一个Stream
Stream<Integer> s = Stream.of(1, 2, 3, 4, 5);
Stream<Integer> s2 = s.map(n -> n * n);//相当于每个元素变为原来的平方
  • map()方法接收的对象是Function接口对象,它定义了一个apply()方法,负责把一个T类型转换成R类型:
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
//Function为前面提到的四大函数式核心接口--函数型接口
@FunctionalInterface
public interface Function<T, R> {
    // 将T类型转换为R:
    R apply(T t);
}
  • 例子:字符串操作
public static void main(String[] args) {
    List.of("  Apple ", " pear ", " ORANGE", " BaNaNa ")
            .stream()
            .map(String::trim) // 去空格
            .map(String::toLowerCase) // 变小写
            .forEach(System.out::println); // 打印
}
  1. 使用filter–重要
  • 所谓filter()操作,就是对一个Stream的所有元素一一进行测试,过滤掉不满足的形成新的Stream
IntStream.of(1, 2, 3, 4, 5, 6, 7, 8, 9)
                .filter(n -> n % 2 != 0) //过滤掉原Stream中的偶数
                .forEach(System.out::println);
  • filter()方法接收的对象是Predicate接口对象,它定义了一个test()方法,负责判断元素是否符合条件:
Stream<T> filter(Predicate<? super T> predicate)
//Predicate为前面提到的四大函数式核心接口--断言型接口
@FunctionalInterface
public interface Predicate<T> {
    // 判断元素t是否符合条件:
    boolean test(T t);
}
  • 例子:获取空字符串的数量
List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
// 获取空字符串的数量
long count = strings.stream().filter(string -> string.isEmpty()).count();
  1. 其他中间操作
  • limit举例
IntStream.of(1, 2, 3, 4, 5, 6, 7, 8, 9)
                .limit(5) //只要前面5元素
                .forEach(System.out::println);
//打印 1 2 3 4 5
  • skip举例
IntStream.of(1, 2, 3, 4, 5, 6, 7, 8, 9)
                .skip(4) //前面4个元素不要
                .forEach(System.out::println);
//打印 5 6 7 8 9
  • distinct去重举例
IntStream.of(1, 2, 3, 4, 5, 6, 6, 7, 7, 7)
                .distinct() //去掉重复的元素
                .forEach(System.out::println);
//打印 1 2 3 4 5 6 7
  • sort排序举例
//此方法需要Stream的每个元素必须实现Comparable接口;
List<String> list = List.of("orange", "apple", "banana")
            .stream()
            .sorted()
            .collect(Collectors.toList());
        System.out.println(list);
//打印[apple,banana,orange]
  • concat合并举例
Stream<String> s1 = List.of("A", "B", "C").stream();
Stream<String> s2 = List.of("D", "E").stream();
// 合并:
Stream<String> s = Stream.concat(s1, s2);
System.out.println(s.collect(Collectors.toList())); 
//打印[A, B, C, D, E]
  • flatMap举例
//所谓flatMap(),是指把Stream的每个元素(这里是List)映射为Stream,然后合并成一个新的Stream:
Stream<List<Integer>> s = Stream.of(
        Arrays.asList(1, 2, 3),
        Arrays.asList(4, 5, 6),
        Arrays.asList(7, 8, 9));
Stream<Integer> i = s.flatMap(list -> list.stream());//通过flatMap把元素是集合的Stream转换为Stream<Integer>
终止操作:
  1. 使用reduce–重要
  • Stream.reduce()Stream的一个聚合方法,它可以把一个Stream的所有元素按照聚合函数聚合成一个结果。
int sum = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9).reduce(0, (acc, n) -> acc + n);//如果不初始化,返回值为Optional<Integer>,因为Stream元素可能是0个,需要使用isPresent()进一步判断
System.out.println(sum); // 45
//--------上述代码解释-----------
Stream<Integer> stream = ...
int acc = 0;//初始化结果为指定值(这里是0)
for (n : stream) {
    acc = (acc, n) -> acc + n;
}
sum=acc
  • reduce()方法传入的对象是BinaryOperator接口,它定义了一个apply()方法,负责把上次累加的结果和本次的元素 进行运算,并返回累加的结果:
T reduce(T identity, BinaryOpeartor<T> accumulator)
    
@FunctionalInterface
public interface BinaryOperator<T> {
    // Bi操作:两个输入,一个输出
    T apply(T t, T u);
}
  • 例子:将配置文件的每一行配置通过map()reduce()操作聚合成一个Map<String, String>
public class Main {
    public static void main(String[] args) {
        // 按行读取配置文件:
        List<String> props = List.of("profile=native", "debug=true", "logging=warn", "interval=500");
        Map<String, String> map = props.stream()
                // 把k=v转换为Map[k]=v:
                .map(kv -> {
                    String[] ss = kv.split("\\=", 2);
                    return Map.of(ss[0], ss[1]);
                })
                // 把所有Map聚合到一个Map:
                .reduce(new HashMap<String, String>(), (m, kv) -> {
                    m.putAll(kv);
                    return m;
                });
        // 打印结果:
        map.forEach((k, v) -> {
            System.out.println(k + " = " + v);
        });
    }
}
  1. 输出集合
  • 输出为List:把Stream变为List不是一个转换操作,而是一个聚合操作,它会强制Stream输出每个元素。
Stream<String> stream = Stream.of("Apple", "", null, "Pear", "  ", "Orange");
List<String> list = stream.filter(s -> s != null && !s.isBlank()).collect(Collectors.toList());//传入Collectors.toSet()可转为Set
System.out.println(list);
//调用collect()并传入Collectors.toList()对象,它实际上是一个Collector实例,通过类似reduce()的操作,把每个元素添加到一个收集器中
  • 输出为数组:调用toArray()方法,并传入数组的“构造方法”
List<String> list = List.of("Apple", "Banana", "Orange");
String[] array = list.stream().toArray(String[]::new);
  • 输出为Map
public static void main(String[] args) {
    Stream<String> stream = Stream.of("APPL:Apple", "MSFT:Microsoft");
    Map<String, String> map = stream
            .collect(Collectors.toMap(
                    // 把元素s映射为key:
                    s -> s.substring(0, s.indexOf(':')),
                    // 把元素s映射为value:
                    s -> s.substring(s.indexOf(':') + 1)));
    System.out.println(map);
}
//对于每个元素,添加到Map时需要key和value,因此,我们要指定两个映射函数,分别把元素映射为key和value
  • 分组输出
public static void main(String[] args) {
    List<String> list = List.of("Apple", "Banana", "Blackberry", "Coconut", "Avocado", "Cherry", "Apricots");
    Map<String, List<String>> groups = list.stream()
            .collect(Collectors.groupingBy(s -> s.substring(0, 1), Collectors.toList()));
    System.out.println(groups);
}
//分组输出使用Collectors.groupingBy(),它需要提供两个函数:一个是分组的key,这里使用s -> s.substring(0, 1),表示只要首字母相同的String分到一组,第二个是分组的value,这里直接使用Collectors.toList(),表示输出为List
  1. 其他终止操作
  • allMatch举例
//判断是否都为正数
final boolean positive =Stream.of(1,2,3,-1,-4,5,-4).allMatch(n->n>=0);
System.out.println(positive);
//打印false
  • anyMatch举例
//判断是否有正数
final boolean positive =Stream.of(1,2,3,-1,-4,5,-4).anyMatch(n->n>=0);
System.out.println(positive);
//打印true
  • max min 举例
final Optional<Integer> maxNum=Stream.of(1,2,3,-1,-4,5,-4).max(Integer::compareTO);
System.out.println(“最大值: ”+maxNum.get());
//打印 最大值: 6
final Optional<Integer> minNum=Stream.of(1,2,3,-1,-4,5,-3).min(Integer::compareTO);
System.out.println(“最小值: ”+minNum.get());
//打印 最小值: -4
  • average、sum、max、min 配合collect()使用
Double averageN = Stream.of(5, 5, 5, 5, 5).collect(Collectors.averagingInt(n -> n));
System.out.println(averageN);
//打印 5
Optional<Integer> maxN = Stream.of(9, 6, 5, 3, 2).collect(Collectors.maxBy(Integer::compareTo));
System.out.println(maxN.get());
//打印 9
Optional<Integer> minN = Stream.of(9, 6, 5, 3, 2).collect(Collectors.minBy(Integer::compareTo));
System.out.println(minN.get());
//打印 2