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");
}
}
简化
简化规则与总结:
-
lambda表达式只能有一行代码的情况下才能简化成为一行,若果有多行,那么就用代码块。(由{}包裹)
-
前提是接口为函数式接口(只有一个抽象方法)
-
多个参数也可以去掉参数类型,要去掉就都去掉,必须加括号
如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 !!");
}
}
方法引用
说明
- 所谓方法引用,是指如果某个方法签名和接口恰好一致,就可以直接传入方法引用。
- 方法签名只看参数类型和返回类型,不看方法名称,也不看类的继承关系。
作用
- 方法引用可以理解为Lambda表达式的另外一种表现形式。
- 方法引用通过方法的名字来指向一个方法。
- 方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
- 方法引用使用一对冒号 ::
方法的分类
类型 | 语法 | 对应的Lambda表达式 |
---|---|---|
静态方法引用 | 类名::staticMethod | (args) -> 类名.staticMethod(args) |
实例方法引用 | inst::instMethod | (args) -> inst.instMethod(args) |
对象方法引用 | 类名::instMethod | (inst,args) -> 类名.instMethod(args) |
构建方法引用 | 类名::new | (args) -> new 类名(args) |
方法引用举例
-
静态方法引用
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); } }
-
实例方法引用
@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()); } }
-
特定类的任意对象的方法引用
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 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
介绍:
- Stream为全新的流式API:Stream API。它位于
java.util.stream
包中。这个Stream
不同于java.io
的InputStream
和OutputStream
,它代表的是任意Java对象的序列。 - 这个
Stream
和List
也不一样,List
存储的每个元素都是已经存储在内存中的某个Java对象,而Stream
输出的元素可能并没有预先存储在内存中,而是实时计算出来的。 - Stream API支持函数式编程和链式操作;
- Stream可以表示无限序列,并且大多数情况下是惰性求值的。
操作步骤:
- 创建Stream----从一个数据源,如集合、数组中获取流。
- 中间操作----一个操作的中间链,对数据源的数据进行操作。
- 终止(聚合)操作----一个终止操作,执行中间操作链,并产生结果。
**注:**一般只有到聚合操作的时候才会触发计算,中间操作都是惰性计算并不是真正的计算
创建:
-
使用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); }
-
基于数组或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。
-
基于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.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);
-
基本类型
//基本类型无法使用泛型,使用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);
中间操作:
-
使用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); // 打印 }
-
-
使用filter–重要
-
所谓
filter()
操作,就是对一个Stream
的所有元素一一进行测试,过滤掉不满足的形成新的StreamIntStream.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();
-
-
其他中间操作
-
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>
-
终止操作:
-
使用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); }); } }
-
-
输出集合
-
输出为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
-
-
其他终止操作
-
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
-